DSA 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -0
- data/lib/DSA.rb +1 -0
- data/lib/DSA/binary_search_tree.rb +1 -0
- data/lib/DSA/list.rb +2 -2
- data/lib/DSA/skip_list.rb +358 -0
- data/lib/DSA/version.rb +1 -1
- data/test/binary_search_tree_test.rb +5 -0
- data/test/skip_list_test.rb +115 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cf45aaf41acd85971600055fd5dfac1825e539b
|
4
|
+
data.tar.gz: 8ab7c55725ae11725a9e9ef7bff86c6d9416a1a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1085cb96b51a4b9dd4f168454cb9218bfa8cc473c2050a8cf662cfecc6feabd002876da7a6005bc28db323bd6a8f9f9a338dff5e6930cd20b4044b11a51051d1
|
7
|
+
data.tar.gz: bd64fc623b6c397346f355cc8b6e7cf289e7bc3e4e69138313c985cfb17830f7b2d466eec48df623f5cd217ac07ad771b611412803e6509523c952e954465845
|
data/README.md
CHANGED
@@ -87,6 +87,34 @@ A help method tried to print a tree, not quite pretty, but may helps test
|
|
87
87
|
Enumerable is included, all those method such as 'each' are all available, since other methods are based on each,
|
88
88
|
the performance might not be the best, use only when a full traversal is inevitable.
|
89
89
|
|
90
|
+
### SkipList
|
91
|
+
An ordered map, storing key/value pairs, duplicate keys are allowed, value can be omitted, preserves an order and provides range search, implemented as a skip list.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
sl = DSA::SkipList.new
|
95
|
+
sl.add key, value
|
96
|
+
sl.add key # value will be nil
|
97
|
+
sl.delete key # all those pairs with the key will be deleted
|
98
|
+
sl.delete key, value # the pair has same key/value pair will be deleted
|
99
|
+
```
|
100
|
+
|
101
|
+
And special methods related to orders, those methods yield key/value pairs to block, if no block, enumerator is returned.
|
102
|
+
```ruby
|
103
|
+
sl.find key
|
104
|
+
sl.each # in-order traversal
|
105
|
+
sl.gt(key) # key/value pairs for keys greater than key
|
106
|
+
sl.ge(key)
|
107
|
+
sl.lt(key)
|
108
|
+
sl.le(key)
|
109
|
+
```
|
110
|
+
A help method prints the skip list
|
111
|
+
```ruby
|
112
|
+
sl.print_me
|
113
|
+
sl.print_me width # width, the length of evert key/value pair, default to 10
|
114
|
+
```
|
115
|
+
Enumerable is included, all those method such as 'each' are all available, since other methods are based on each,
|
116
|
+
the performance might not be the best, use only when a full traversal is inevitable.
|
117
|
+
|
90
118
|
|
91
119
|
### PriorityQueue
|
92
120
|
An array based heap, priority is a number, the smaller it is, higher priority it has
|
data/lib/DSA.rb
CHANGED
data/lib/DSA/list.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module DSA
|
2
2
|
# Exception
|
3
|
-
class
|
3
|
+
class ListIndexError < StandardError
|
4
4
|
end
|
5
5
|
class ListRemovalError < StandardError
|
6
6
|
end
|
@@ -156,7 +156,7 @@ module DSA
|
|
156
156
|
private
|
157
157
|
def get_node(index)
|
158
158
|
index += @length if index < 0
|
159
|
-
raise(
|
159
|
+
raise(ListIndexError, 'Index out of bound: ' + index.to_s) if index > @length - 1 || index < 0
|
160
160
|
if index > @length / 2
|
161
161
|
node = @tail.prev
|
162
162
|
current_index = @length - 1
|
@@ -0,0 +1,358 @@
|
|
1
|
+
module DSA
|
2
|
+
class SkipListNode
|
3
|
+
attr_accessor :key, :value, :prev, :next, :up, :down
|
4
|
+
def initialize(key, value = nil)
|
5
|
+
@key = key
|
6
|
+
@value = value
|
7
|
+
@prev = nil
|
8
|
+
@next = nil
|
9
|
+
@up = nil
|
10
|
+
@down = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def is_sentinel?
|
14
|
+
@key.equal? SkipListLevel::SENTINEL
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SkipListLevel
|
19
|
+
attr_accessor :head, :tail
|
20
|
+
SENTINEL = Object.new
|
21
|
+
def initialize
|
22
|
+
@head = SkipListNode.new(SENTINEL, 'Sentinel')
|
23
|
+
@tail = SkipListNode.new(SENTINEL, 'Sentinel')
|
24
|
+
@head.next = @tail
|
25
|
+
@tail.prev = @head
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class SkipList
|
30
|
+
attr_reader :length, :height
|
31
|
+
include Enumerable
|
32
|
+
def initialize
|
33
|
+
@levels = [SkipListLevel.new]
|
34
|
+
@length = 0
|
35
|
+
@height = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def find(key)
|
39
|
+
nodes = find_nodes key
|
40
|
+
|
41
|
+
if block_given?
|
42
|
+
return unless nodes
|
43
|
+
nodes.each do |node|
|
44
|
+
yield [node.key, node.value]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
Enumerator.new do |y|
|
48
|
+
find(key) do |key, value|
|
49
|
+
y << [key, value]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def each
|
57
|
+
walk = @levels.first.head
|
58
|
+
if block_given?
|
59
|
+
walk = walk.next
|
60
|
+
until walk.is_sentinel?
|
61
|
+
yield [ walk.key, walk.value ]
|
62
|
+
walk = walk.next
|
63
|
+
end
|
64
|
+
else
|
65
|
+
Enumerator.new do |y|
|
66
|
+
each do |key, value|
|
67
|
+
y << [key, value]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def gt(key)
|
74
|
+
nodes = find_nodes(key, true, false)
|
75
|
+
|
76
|
+
if block_given?
|
77
|
+
return unless nodes
|
78
|
+
walk = nodes.last.next
|
79
|
+
until walk.is_sentinel?
|
80
|
+
yield [ walk.key, walk.value ]
|
81
|
+
walk = walk.next
|
82
|
+
end
|
83
|
+
else
|
84
|
+
Enumerator.new do |y|
|
85
|
+
gt(key) do |key, value|
|
86
|
+
y << [key, value]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def ge(key)
|
93
|
+
nodes = find_nodes(key, true, false)
|
94
|
+
|
95
|
+
if block_given?
|
96
|
+
return unless nodes
|
97
|
+
if nodes.first.key == key
|
98
|
+
walk = nodes.first
|
99
|
+
else
|
100
|
+
walk = nodes.first.next
|
101
|
+
end
|
102
|
+
until walk.is_sentinel?
|
103
|
+
yield [ walk.key, walk.value ]
|
104
|
+
walk = walk.next
|
105
|
+
end
|
106
|
+
else
|
107
|
+
Enumerator.new do |y|
|
108
|
+
ge(key) do |key, value|
|
109
|
+
y << [key, value]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def lt(key)
|
116
|
+
nodes = find_nodes(key, false, true)
|
117
|
+
|
118
|
+
if block_given?
|
119
|
+
return unless nodes
|
120
|
+
walk = nodes.first.prev
|
121
|
+
until walk.is_sentinel?
|
122
|
+
yield [ walk.key, walk.value ]
|
123
|
+
walk = walk.prev
|
124
|
+
end
|
125
|
+
else
|
126
|
+
Enumerator.new do |y|
|
127
|
+
lt(key) do |key, value|
|
128
|
+
y << [key, value]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def le(key)
|
135
|
+
nodes = find_nodes(key, false, true)
|
136
|
+
|
137
|
+
if block_given?
|
138
|
+
return unless nodes
|
139
|
+
if nodes.first.key == key
|
140
|
+
walk = nodes.last
|
141
|
+
else
|
142
|
+
walk = nodes.last.prev
|
143
|
+
end
|
144
|
+
until walk.is_sentinel?
|
145
|
+
yield [ walk.key, walk.value ]
|
146
|
+
walk = walk.prev
|
147
|
+
end
|
148
|
+
else
|
149
|
+
Enumerator.new do |y|
|
150
|
+
le(key) do |key, value|
|
151
|
+
y << [key, value]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
# if value provided, the nodes have the same key/value pairs will be deleted,
|
159
|
+
# otherwise, all nodes have the same key are deleted
|
160
|
+
# return the value of last deleted node
|
161
|
+
def delete(key, value = nil)
|
162
|
+
nodes = find_nodes(key)
|
163
|
+
return false unless nodes
|
164
|
+
|
165
|
+
return_value = false
|
166
|
+
nodes.each do |node|
|
167
|
+
if !value || value == node.value
|
168
|
+
return_value = remove_tower node
|
169
|
+
@length -= 1
|
170
|
+
remove_empty_level
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
return_value
|
175
|
+
end
|
176
|
+
|
177
|
+
def add(key, value = nil)
|
178
|
+
potential_place = []
|
179
|
+
walk = start_node
|
180
|
+
while 1
|
181
|
+
if walk.next.is_sentinel? || key <= walk.next.key
|
182
|
+
if walk.down
|
183
|
+
potential_place.push walk
|
184
|
+
walk = walk.down
|
185
|
+
next
|
186
|
+
else
|
187
|
+
insert_node_between walk, walk.next, SkipListNode.new(key, value)
|
188
|
+
@length += 1
|
189
|
+
build_tower walk.next, potential_place, key, value
|
190
|
+
break
|
191
|
+
end
|
192
|
+
else
|
193
|
+
walk = walk.next
|
194
|
+
next
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def print_me(width = 10)
|
200
|
+
rec = Hash.new
|
201
|
+
walk = @levels.first.head
|
202
|
+
i = 0
|
203
|
+
j = 0
|
204
|
+
walk = walk.next
|
205
|
+
while ! walk.is_sentinel?
|
206
|
+
rec[ [i, j] ] = "#{walk.key},#{walk.value}"
|
207
|
+
up_walk = walk
|
208
|
+
while up_walk.up
|
209
|
+
up_walk = up_walk.up
|
210
|
+
j += 1
|
211
|
+
rec[ [i, j] ] ="#{up_walk.key},#{up_walk.value}"
|
212
|
+
end
|
213
|
+
walk = walk.next
|
214
|
+
i += 1
|
215
|
+
j = 0
|
216
|
+
end
|
217
|
+
|
218
|
+
# print
|
219
|
+
puts '=' * 100
|
220
|
+
(height+1).times.reverse_each do |j|
|
221
|
+
printf 'Sentinel--'
|
222
|
+
(length).times do |i|
|
223
|
+
if rec[ [i, j] ]
|
224
|
+
printf ">%#{width+2}s", "#{rec[ [i, j] ]}--"
|
225
|
+
else
|
226
|
+
printf '-' + '-' * width + '--'
|
227
|
+
end
|
228
|
+
end
|
229
|
+
puts '>Sentinel'
|
230
|
+
end
|
231
|
+
puts '=' * 100
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
def remove_empty_level
|
236
|
+
return if @length < 2
|
237
|
+
one_to_last = @levels[-2]
|
238
|
+
if one_to_last.head.next.is_sentinel?
|
239
|
+
@levels.pop
|
240
|
+
@height -= 1
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# if not found, two more options are provided
|
245
|
+
def find_nodes(key, return_previous = false, return_next = false )
|
246
|
+
walk = start_node
|
247
|
+
while 1
|
248
|
+
if !walk.is_sentinel? && key == walk.key
|
249
|
+
break
|
250
|
+
elsif walk.next.is_sentinel? || key < walk.next.key
|
251
|
+
if walk.down
|
252
|
+
walk = walk.down
|
253
|
+
next
|
254
|
+
else
|
255
|
+
if return_previous
|
256
|
+
return [walk]
|
257
|
+
elsif return_next
|
258
|
+
return [walk.next]
|
259
|
+
else
|
260
|
+
return nil
|
261
|
+
end
|
262
|
+
end
|
263
|
+
else
|
264
|
+
walk = walk.next
|
265
|
+
next
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
nodes = []
|
270
|
+
while !walk.is_sentinel? && walk.down
|
271
|
+
walk = walk.down
|
272
|
+
end
|
273
|
+
|
274
|
+
nodes.push walk
|
275
|
+
|
276
|
+
# to find the duplicates
|
277
|
+
other_node = walk.next
|
278
|
+
while !other_node.is_sentinel? && other_node.key == key
|
279
|
+
nodes.push other_node
|
280
|
+
other_node = other_node.next
|
281
|
+
end
|
282
|
+
|
283
|
+
other_node = walk.prev
|
284
|
+
while !other_node.is_sentinel? && other_node.key == key
|
285
|
+
nodes.unshift other_node
|
286
|
+
other_node = other_node.prev
|
287
|
+
end
|
288
|
+
|
289
|
+
nodes
|
290
|
+
end
|
291
|
+
|
292
|
+
def remove_tower(base)
|
293
|
+
remove_node base
|
294
|
+
remove_tower base.up if base.up
|
295
|
+
base.value
|
296
|
+
end
|
297
|
+
|
298
|
+
def build_tower(base, tower, key, value)
|
299
|
+
up_stair_previous = nil
|
300
|
+
tower.reverse_each do |node|
|
301
|
+
return unless go_up?
|
302
|
+
up_stair_previous = node
|
303
|
+
new_node = SkipListNode.new(key, value)
|
304
|
+
insert_node_between up_stair_previous, up_stair_previous.next, new_node
|
305
|
+
vertical_link_node new_node, base
|
306
|
+
base = new_node
|
307
|
+
end
|
308
|
+
# add new level
|
309
|
+
if go_up?
|
310
|
+
add_level
|
311
|
+
@height += 1
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
def add_level
|
317
|
+
new_level = SkipListLevel.new
|
318
|
+
vertical_link_node new_level.head, @levels.last.head
|
319
|
+
vertical_link_node new_level.tail, @levels.last.tail
|
320
|
+
@levels.push new_level
|
321
|
+
end
|
322
|
+
|
323
|
+
def vertical_link_node(above, below)
|
324
|
+
above.down = below
|
325
|
+
below.up = above
|
326
|
+
end
|
327
|
+
|
328
|
+
def go_up?
|
329
|
+
[true, false].sample
|
330
|
+
end
|
331
|
+
|
332
|
+
def insert_node_between(a, b, new_one)
|
333
|
+
a.next = new_one
|
334
|
+
new_one.prev = a
|
335
|
+
new_one.next = b
|
336
|
+
b.prev = new_one
|
337
|
+
end
|
338
|
+
|
339
|
+
# remove a node
|
340
|
+
def remove_node(node)
|
341
|
+
before = node.prev
|
342
|
+
after = node.next
|
343
|
+
before.next = after
|
344
|
+
after.prev = before
|
345
|
+
node
|
346
|
+
end
|
347
|
+
|
348
|
+
def start_node
|
349
|
+
@levels.last.head
|
350
|
+
end
|
351
|
+
|
352
|
+
def end_node
|
353
|
+
@levels.first.tail
|
354
|
+
end
|
355
|
+
|
356
|
+
|
357
|
+
end
|
358
|
+
end
|
data/lib/DSA/version.rb
CHANGED
@@ -270,6 +270,7 @@ class MyTest < Test::Unit::TestCase
|
|
270
270
|
def test_performance
|
271
271
|
puts
|
272
272
|
rb = DSA::RedBlackTree.new
|
273
|
+
sl = DSA::SkipList.new
|
273
274
|
hash = Hash.new
|
274
275
|
value = 10**5
|
275
276
|
Benchmark.bm(20) do |x|
|
@@ -277,6 +278,10 @@ class MyTest < Test::Unit::TestCase
|
|
277
278
|
x.report('RedBlack Find') { value.times { rb[Random.rand(value)] } }
|
278
279
|
x.report('RedBlack Find gt') { value.times { rb.gt(Random.rand(value)) } }
|
279
280
|
x.report('RedBlack Deletion') { (value/2).times { rb.delete Random.rand(value) } }
|
281
|
+
x.report('SkipList') { value.times { sl.add Random.rand(value), 'whatever' } }
|
282
|
+
x.report('SkipList Find') { value.times { sl.find Random.rand(value) } }
|
283
|
+
x.report('SkipList Find gt') { value.times { sl.gt Random.rand(value) } }
|
284
|
+
x.report('SkipList Deletion') { (value/2).times { sl.delete Random.rand(value) } }
|
280
285
|
x.report('Built-In Hash') { value.times { hash[Random.rand(value)] = 'whatever' } }
|
281
286
|
x.report('Built-In Hash Find') { value.times { hash[Random.rand(value)] } }
|
282
287
|
x.report('Built-In Hash Deletion') { (value/2).times { hash.delete Random.rand(value) } }
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'DSA'
|
3
|
+
|
4
|
+
class MyTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
# Called before every test method runs. Can be used
|
7
|
+
# to set up fixture information.
|
8
|
+
def setup
|
9
|
+
# Do nothing
|
10
|
+
end
|
11
|
+
|
12
|
+
# Called after every test method runs. Can be used to tear
|
13
|
+
# down fixture information.
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
# Do nothing
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_skip_list
|
20
|
+
sl = DSA::SkipList.new
|
21
|
+
value = 20
|
22
|
+
value.times { sl.add Random.rand(value/3), Random.rand(value*2) }
|
23
|
+
|
24
|
+
sl.print_me
|
25
|
+
|
26
|
+
sl.find 5 do |key, value|
|
27
|
+
puts "(#{key}, #{value})"
|
28
|
+
end
|
29
|
+
|
30
|
+
e = sl.find 5
|
31
|
+
loop do
|
32
|
+
key, value = e.next
|
33
|
+
puts "(#{key}, #{value})"
|
34
|
+
end
|
35
|
+
|
36
|
+
sl.add 5, 100
|
37
|
+
sl.add 5, 200
|
38
|
+
sl.print_me
|
39
|
+
assert_equal 200, sl.delete(5, 200), 'delete failed'
|
40
|
+
sl.print_me
|
41
|
+
puts sl.delete 5
|
42
|
+
sl.print_me
|
43
|
+
|
44
|
+
value = 20
|
45
|
+
value.times { sl.add Random.rand(value), Random.rand(value*2) }
|
46
|
+
sl.print_me
|
47
|
+
value.times { sl.delete Random.rand(value) }
|
48
|
+
sl.print_me 5
|
49
|
+
|
50
|
+
sl.each do |key, value|
|
51
|
+
printf "(#{key}, #{value})"
|
52
|
+
end
|
53
|
+
puts
|
54
|
+
|
55
|
+
e = sl.each
|
56
|
+
loop do
|
57
|
+
key, value = e.next
|
58
|
+
printf "(#{key}, #{value})"
|
59
|
+
end
|
60
|
+
puts
|
61
|
+
|
62
|
+
sl.gt(10) do |key, value|
|
63
|
+
printf "(#{key}, #{value})"
|
64
|
+
end
|
65
|
+
puts
|
66
|
+
|
67
|
+
e = sl.gt(10)
|
68
|
+
loop do
|
69
|
+
key, value = e.next
|
70
|
+
printf "(#{key}, #{value})"
|
71
|
+
end
|
72
|
+
puts
|
73
|
+
|
74
|
+
sl.ge(10) do |key, value|
|
75
|
+
printf "(#{key}, #{value})"
|
76
|
+
end
|
77
|
+
puts
|
78
|
+
|
79
|
+
e = sl.ge(10)
|
80
|
+
loop do
|
81
|
+
key, value = e.next
|
82
|
+
printf "(#{key}, #{value})"
|
83
|
+
end
|
84
|
+
puts
|
85
|
+
|
86
|
+
sl.lt(10) do |key, value|
|
87
|
+
printf "(#{key}, #{value})"
|
88
|
+
end
|
89
|
+
puts
|
90
|
+
|
91
|
+
e = sl.lt(10)
|
92
|
+
loop do
|
93
|
+
key, value = e.next
|
94
|
+
printf "(#{key}, #{value})"
|
95
|
+
end
|
96
|
+
puts
|
97
|
+
|
98
|
+
sl.le(10) do |key, value|
|
99
|
+
printf "(#{key}, #{value})"
|
100
|
+
end
|
101
|
+
puts
|
102
|
+
|
103
|
+
e = sl.le(10)
|
104
|
+
loop do
|
105
|
+
key, value = e.next
|
106
|
+
printf "(#{key}, #{value})"
|
107
|
+
end
|
108
|
+
puts
|
109
|
+
|
110
|
+
value = 10 ** 3
|
111
|
+
value.times { sl.add Random.rand(value), Random.rand(value*2) }
|
112
|
+
sl.print_me
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: DSA
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- lusaisai
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -57,12 +57,14 @@ files:
|
|
57
57
|
- lib/DSA/binary_search_tree.rb
|
58
58
|
- lib/DSA/list.rb
|
59
59
|
- lib/DSA/priority_queue.rb
|
60
|
+
- lib/DSA/skip_list.rb
|
60
61
|
- lib/DSA/stack_and_queue.rb
|
61
62
|
- lib/DSA/version.rb
|
62
63
|
- test/algorithms_test.rb
|
63
64
|
- test/binary_search_tree_test.rb
|
64
65
|
- test/list_test.rb
|
65
66
|
- test/priority_queue_test.rb
|
67
|
+
- test/skip_list_test.rb
|
66
68
|
- test/stack_and_queue_test.rb
|
67
69
|
homepage: https://github.com/lusaisai/DSA
|
68
70
|
licenses:
|
@@ -93,4 +95,5 @@ test_files:
|
|
93
95
|
- test/binary_search_tree_test.rb
|
94
96
|
- test/list_test.rb
|
95
97
|
- test/priority_queue_test.rb
|
98
|
+
- test/skip_list_test.rb
|
96
99
|
- test/stack_and_queue_test.rb
|