linked 0.3.1 → 0.4.0
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 +1 -6
- data/lib/linked/item.rb +18 -404
- data/lib/linked/list/eol.rb +29 -11
- data/lib/linked/list.rb +61 -74
- data/lib/linked/listable.rb +441 -0
- data/lib/linked/version.rb +1 -1
- data/lib/linked.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05e94a861ba2e494ea20dc84572c5dac578421eb
|
4
|
+
data.tar.gz: 849a8b134e95bcd6d444782c167f83ff6f982be4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 255a18d48ba2d395e12394aa8a6a72880100a1c2677ad3f5e3005c7412ea74011c4b902947b390b85077675c6a0447c112b6bed36ba4da8bf03dcd5769bf7f73
|
7
|
+
data.tar.gz: e2e9b8f9a72c06074c1aa3af9cbecff9c3fbb8adc2bc6674f50f86d3a089abec934ca5ee91815e9156f4647c374ec3bea87fec583e80236a5b010c9939c0cbf8
|
data/README.md
CHANGED
@@ -36,13 +36,8 @@ A basic use case is show below. For more details, for now, see the docs.
|
|
36
36
|
```ruby
|
37
37
|
require 'linked'
|
38
38
|
|
39
|
-
# Include the List module in a class
|
40
|
-
class ListLike
|
41
|
-
include Linked::List
|
42
|
-
end
|
43
|
-
|
44
39
|
# Create a list
|
45
|
-
list =
|
40
|
+
list = Linked::List.new
|
46
41
|
|
47
42
|
# Append values
|
48
43
|
list << :value
|
data/lib/linked/item.rb
CHANGED
@@ -1,58 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# TODO: Think about moving some of basic functionallity of Item into Item::Base.
|
4
|
-
|
5
3
|
module Linked
|
6
4
|
# Item
|
7
5
|
#
|
8
|
-
# This class implements
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# +- - - + +------+------+ +- - - +
|
12
|
-
# | Head | <--| prev | next |--> ... --> | Tail |
|
13
|
-
# + - - -+ +------+------+ + - - -+
|
14
|
-
# (optional) First Item N Items (optional)
|
15
|
-
#
|
16
|
-
# An object is considered a list if it responds to #head, #tail, #grow and
|
17
|
-
# #shrink. The latter facilitate counting of the items and will be called
|
18
|
-
# everytime an item is appended, prepended or deleted. #head and #tail are
|
19
|
-
# expected to return two objects that, respectivly
|
20
|
-
# a) responds to #next= and #append, or #prev= and #prepend and
|
21
|
-
# b) returns true for #nil?.
|
22
|
-
#
|
23
|
-
# Notation
|
24
|
-
# --------
|
25
|
-
#
|
26
|
-
# Some methods operate on chains of items, and to describe the effects of an
|
27
|
-
# operation the following syntax is used.
|
28
|
-
#
|
29
|
-
# A ( A <> B ) [ A <> B ]
|
30
|
-
# (i) (ii) (iii)
|
31
|
-
#
|
32
|
-
# Single items are denoted with capital letters (i), while chains are written
|
33
|
-
# as multiple connected items (ii). The parenthesis are optional. To show that
|
34
|
-
# one or more nodes are wraped in a list, angle brackets are used (iii).
|
6
|
+
# This class implements a listable value object that wraps an arbitrary value
|
7
|
+
# an can be stored in a list.
|
35
8
|
|
36
9
|
class Item
|
37
|
-
|
38
|
-
# attribute is protected and should be avoided.
|
39
|
-
#
|
40
|
-
# Returns the item's list, or nil
|
41
|
-
|
42
|
-
attr_writer :list
|
43
|
-
protected :list=
|
10
|
+
include Listable
|
44
11
|
|
45
12
|
# The Item can hold an arbitrary object as its value and it will stay with
|
46
13
|
# the item.
|
47
14
|
|
48
15
|
attr_accessor :value
|
49
16
|
|
50
|
-
# Calling either #prev= or #next= directly is not recommended, since can
|
51
|
-
# corrupt the chain.
|
52
|
-
|
53
|
-
attr_writer :prev, :next
|
54
|
-
protected :prev=, :next=
|
55
|
-
|
56
17
|
# Creates a new item. If a list is given the item will be considered a part
|
57
18
|
# of that list and appended to the end of it.
|
58
19
|
#
|
@@ -61,15 +22,9 @@ module Linked
|
|
61
22
|
#
|
62
23
|
# Returns a new Item.
|
63
24
|
|
64
|
-
def initialize(value = nil
|
25
|
+
def initialize(value = nil)
|
65
26
|
@value = value
|
66
|
-
|
67
|
-
if list
|
68
|
-
list.tail.append self
|
69
|
-
else
|
70
|
-
@next = nil
|
71
|
-
@prev = nil
|
72
|
-
end
|
27
|
+
super()
|
73
28
|
end
|
74
29
|
|
75
30
|
# Calling #dup on an item returns a copy that is no longer connected to the
|
@@ -78,7 +33,6 @@ module Linked
|
|
78
33
|
# Returns a new Item.
|
79
34
|
|
80
35
|
def initialize_dup(source)
|
81
|
-
@next = @prev = @list = nil
|
82
36
|
@value = begin
|
83
37
|
source.value.dup
|
84
38
|
rescue TypeError
|
@@ -87,272 +41,27 @@ module Linked
|
|
87
41
|
super
|
88
42
|
end
|
89
43
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
# Returns the item itself.
|
95
|
-
|
96
|
-
def item
|
97
|
-
self
|
98
|
-
end
|
99
|
-
|
100
|
-
# Access the list that the item is part of. If the item is not in a list a
|
101
|
-
# NoMethodError will be raised. This mirrors the behaviour of List#list and
|
102
|
-
# allows other methods that work on List objects to easily and
|
103
|
-
# interchangeably accept both lists and items as arguments.
|
104
|
-
#
|
105
|
-
# Returns the list that the item is part of.
|
106
|
-
|
107
|
-
def list
|
108
|
-
raise NoMethodError unless @list
|
109
|
-
@list
|
110
|
-
end
|
111
|
-
|
112
|
-
# Check it the item is part of a list.
|
113
|
-
#
|
114
|
-
# Returns true if the item is in a list.
|
115
|
-
|
116
|
-
def in_list?
|
117
|
-
@list ? true : false
|
118
|
-
end
|
119
|
-
|
120
|
-
# Check if this is the first item in the list. It is crucial that tail#nil?
|
121
|
-
# returns true for the first item to be identified correctly.
|
122
|
-
#
|
123
|
-
# Returns true if no item come before this one.
|
124
|
-
|
125
|
-
def first?
|
126
|
-
@prev.nil?
|
127
|
-
end
|
128
|
-
|
129
|
-
# Check if this is the last item in the list. It is crucial that head#nil?
|
130
|
-
# returns true for the last item to be identified correctly.
|
131
|
-
#
|
132
|
-
# Returns true if no item come after this one.
|
133
|
-
|
134
|
-
def last?
|
135
|
-
@next.nil?
|
136
|
-
end
|
137
|
-
|
138
|
-
# Check if the item is in the given list.
|
139
|
-
#
|
140
|
-
# list - any object.
|
141
|
-
#
|
142
|
-
# Returns true if the item is part of the given list.
|
143
|
-
|
144
|
-
def in?(list)
|
145
|
-
@list.equal? list
|
146
|
-
end
|
147
|
-
|
148
|
-
# Access the next item in the list. If this is the last one a StopIteration
|
149
|
-
# will be raised, so that items may be iterated over safely in a loop.
|
150
|
-
#
|
151
|
-
# Example
|
152
|
-
# loop do
|
153
|
-
# item = item.next
|
154
|
-
# end
|
155
|
-
#
|
156
|
-
# Returns the item that come after this.
|
157
|
-
|
158
|
-
def next
|
159
|
-
raise StopIteration if last?
|
160
|
-
@next
|
161
|
-
end
|
162
|
-
|
163
|
-
# Unsafe accessor of the next item in the list. It is preferable to use
|
164
|
-
# #next.
|
165
|
-
#
|
166
|
-
# Returns the item that come after this, or nil if this is the last one.
|
167
|
-
|
168
|
-
def next!
|
169
|
-
@next
|
170
|
-
end
|
171
|
-
|
172
|
-
# Access the previous item in the list. If this is the first one a
|
173
|
-
# StopIteration will be raised, so that items may be iterated over safely in
|
174
|
-
# a loop.
|
175
|
-
#
|
176
|
-
# Example
|
177
|
-
# loop do
|
178
|
-
# item = item.prev
|
179
|
-
# end
|
180
|
-
#
|
181
|
-
# Returns the item that come before this.
|
182
|
-
|
183
|
-
def prev
|
184
|
-
raise StopIteration if first?
|
185
|
-
@prev
|
186
|
-
end
|
187
|
-
|
188
|
-
alias previous prev
|
189
|
-
|
190
|
-
# Unsafe accessor of the previous item in the list. It is preferable to use
|
191
|
-
# #prev.
|
192
|
-
#
|
193
|
-
# Returns the item that come before this, or nil if this is the first one.
|
194
|
-
|
195
|
-
def prev!
|
196
|
-
@prev
|
197
|
-
end
|
198
|
-
|
199
|
-
alias previous! prev!
|
200
|
-
|
201
|
-
# Inserts the given item between this one and the one after it (if any). If
|
202
|
-
# the given item is part of a chain, all items following it will be moved to
|
203
|
-
# this one, and added to the list if one is set.
|
204
|
-
#
|
205
|
-
# Example for the chain (A <> C)
|
206
|
-
#
|
207
|
-
# A.append B # => (A <> B <> C)
|
208
|
-
#
|
209
|
-
# Alternativly the argument can be an arbitrary object, in which case a new
|
210
|
-
# item will be created around it.
|
211
|
-
#
|
212
|
-
# If this item is part of a list #grow will be called on it with the
|
213
|
-
# number of added items as an argument. Should it also be the last item
|
214
|
-
# #prev= will be called on the list tail.
|
215
|
-
#
|
216
|
-
# object - the item to append, or an arbitrary object to be wraped in a new
|
217
|
-
# item. If in a list it will be asked to create the new item via
|
218
|
-
# List#create_item.
|
219
|
-
#
|
220
|
-
# Returns the last item that was appended.
|
221
|
-
|
222
|
-
def append(object)
|
223
|
-
if object.respond_to? :item
|
224
|
-
first_item = object.item
|
225
|
-
last_item = first_item.send :extract_beginning_with, @list
|
226
|
-
else
|
227
|
-
if @list
|
228
|
-
first_item = last_item = @list.send :create_item, object
|
229
|
-
first_item.list = @list
|
230
|
-
@list.send :grow
|
231
|
-
else
|
232
|
-
first_item = last_item = self.class.new object
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
first_item.prev = self
|
237
|
-
@next.prev = last_item if @next
|
238
|
-
@next, last_item.next = first_item, @next
|
239
|
-
|
240
|
-
last_item
|
241
|
-
end
|
242
|
-
|
243
|
-
# Inserts the given item between this one and the one before it (if any). If
|
244
|
-
# the given item is part of a chain, all items preceeding it will be moved
|
245
|
-
# to this one, and added to the list if one is set.
|
246
|
-
#
|
247
|
-
# Example for the chain (A <> C)
|
248
|
-
#
|
249
|
-
# C.prepend B # => (A <> B <> C)
|
44
|
+
# Item equality is solely determined by tha value. If the other object
|
45
|
+
# responds to #value, and its value is equal (#==) to this value, the
|
46
|
+
# objects are considered equal.
|
250
47
|
#
|
251
|
-
#
|
252
|
-
# item will be created around it.
|
48
|
+
# other - any object.
|
253
49
|
#
|
254
|
-
#
|
255
|
-
# number of added items as an argument. Should it also be the first item
|
256
|
-
# #next= will be called on the list head.
|
257
|
-
#
|
258
|
-
# object - the item to prepend. or an arbitrary object to be wraped in a
|
259
|
-
# new item. If in a list it will be asked to create the new item
|
260
|
-
# via List#create_item.
|
261
|
-
#
|
262
|
-
# Returns the last item that was prepended.
|
263
|
-
|
264
|
-
def prepend(object)
|
265
|
-
if object.respond_to? :item
|
266
|
-
last_item = object.item
|
267
|
-
first_item = last_item.send :extract_ending_with, @list
|
268
|
-
else
|
269
|
-
if @list
|
270
|
-
first_item = last_item = @list.send :create_item, object
|
271
|
-
first_item.list = @list
|
272
|
-
@list.send :grow
|
273
|
-
else
|
274
|
-
first_item = last_item = self.class.new object
|
275
|
-
end
|
276
|
-
end
|
50
|
+
# Returns true if the objects are considered equal.
|
277
51
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
first_item
|
283
|
-
end
|
284
|
-
|
285
|
-
# Remove an item from the chain. If this item is part of a list and is
|
286
|
-
# either first, last or both in that list, #next= and #prev= will be called
|
287
|
-
# on the list head and tail respectivly.
|
288
|
-
#
|
289
|
-
# If this item is part of a list #shrink will be called on it.
|
290
|
-
#
|
291
|
-
# Returns self.
|
292
|
-
|
293
|
-
def delete
|
294
|
-
@next.prev = @prev if @next
|
295
|
-
@prev.next = @next if @prev
|
296
|
-
@list.send :shrink if @list
|
297
|
-
|
298
|
-
@next = @prev = @list = nil
|
299
|
-
self
|
52
|
+
def ==(other)
|
53
|
+
return false unless other.respond_to? :value
|
54
|
+
value == other.value
|
300
55
|
end
|
301
56
|
|
302
|
-
|
303
|
-
# list they will be removed from it.
|
304
|
-
#
|
305
|
-
# Returns the last item in the chain that was just deleted, or nil if this
|
306
|
-
# is the first item.
|
307
|
-
|
308
|
-
def delete_before
|
309
|
-
@prev.send :extract_ending_with unless first?
|
310
|
-
end
|
57
|
+
alias eql? ==
|
311
58
|
|
312
|
-
#
|
313
|
-
# list they will be removed from it.
|
59
|
+
# Uses the hash value of the item value.
|
314
60
|
#
|
315
|
-
# Returns
|
316
|
-
# is the first item.
|
61
|
+
# Returns a fixnum that can be used by Hash to identify the item.
|
317
62
|
|
318
|
-
def
|
319
|
-
|
320
|
-
end
|
321
|
-
|
322
|
-
# Iterates over each item before this, in reverse order. If a block is not
|
323
|
-
# given an enumerator is returned.
|
324
|
-
#
|
325
|
-
# Note that raising a StopIteraion inside the block will cause the loop to
|
326
|
-
# silently stop the iteration early.
|
327
|
-
|
328
|
-
def before
|
329
|
-
return to_enum(__callee__) unless block_given?
|
330
|
-
return if first?
|
331
|
-
|
332
|
-
item = self.prev
|
333
|
-
|
334
|
-
loop do
|
335
|
-
yield item
|
336
|
-
item = item.prev
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
# Iterates over each item after this. If a block is not given an enumerator
|
341
|
-
# is returned.
|
342
|
-
#
|
343
|
-
# Note that raising a StopIteraion inside the block will cause the loop to
|
344
|
-
# silently stop the iteration early.
|
345
|
-
|
346
|
-
def after
|
347
|
-
return to_enum(__callee__) unless block_given?
|
348
|
-
return if last?
|
349
|
-
|
350
|
-
item = self.next
|
351
|
-
|
352
|
-
loop do
|
353
|
-
yield item
|
354
|
-
item = item.next
|
355
|
-
end
|
63
|
+
def hash
|
64
|
+
value.hash
|
356
65
|
end
|
357
66
|
|
358
67
|
# Freezes the value, as well as making the list item itself immutable.
|
@@ -372,100 +81,5 @@ module Linked
|
|
372
81
|
output = format '%s:0x%0x', self.class.name, object_id
|
373
82
|
value ? output + " value=#{value.inspect}" : output
|
374
83
|
end
|
375
|
-
|
376
|
-
# PRIVATE DANGEROUS METHOD. This method should never be called directly
|
377
|
-
# since it may leave the extracted item chain in an invalid state.
|
378
|
-
#
|
379
|
-
# This method extracts the item, together with the chain following it, from
|
380
|
-
# the list they are in (if any) and optionally facilitates moving them to a
|
381
|
-
# new list.
|
382
|
-
#
|
383
|
-
# Given the two lists
|
384
|
-
# [ A <> B <> C ] [ D ]
|
385
|
-
# (I) (II)
|
386
|
-
#
|
387
|
-
# calling B.extract_beginning_with(II) will result in (B <> C) being removed
|
388
|
-
# from (I), and (II) to be grown by two. (B <> C) will now reference (II)
|
389
|
-
# but they will not yet be linked to any of the items in it. It is therefore
|
390
|
-
# necessary to insert them directly after calling this method, or (II) will
|
391
|
-
# be left in an invalid state.
|
392
|
-
#
|
393
|
-
# Returns the last item of the chain.
|
394
|
-
|
395
|
-
private def extract_beginning_with(new_list = nil)
|
396
|
-
old_list = @list
|
397
|
-
# Count items and move them to the new list
|
398
|
-
last_item = self
|
399
|
-
count = 1 + loop.count do
|
400
|
-
last_item.list = new_list
|
401
|
-
last_item = last_item.next
|
402
|
-
end
|
403
|
-
|
404
|
-
# Make sure the old list is in a valid state
|
405
|
-
if old_list
|
406
|
-
if first?
|
407
|
-
old_list.send :clear
|
408
|
-
else
|
409
|
-
old_list.send :shrink, count
|
410
|
-
# Fix the links within in the list
|
411
|
-
@prev.next = last_item.next!
|
412
|
-
last_item.next!.prev = @prev
|
413
|
-
end
|
414
|
-
else
|
415
|
-
# Disconnect the item directly after the chain
|
416
|
-
@prev.next = nil unless first?
|
417
|
-
end
|
418
|
-
|
419
|
-
# Disconnect the chain from the list
|
420
|
-
@prev = last_item.next = nil
|
421
|
-
|
422
|
-
# Preemptivly tell the new list to grow
|
423
|
-
new_list.send :grow, count if new_list
|
424
|
-
|
425
|
-
last_item
|
426
|
-
end
|
427
|
-
|
428
|
-
# PRIVATE DANGEROUS METHOD. This method should never be called directly
|
429
|
-
# since it may leave the extracted item chain in an invalid state.
|
430
|
-
#
|
431
|
-
# This method extracts the item, together with the chain preceding it, from
|
432
|
-
# the list they are in (if any) and optionally facilitates moving them to a
|
433
|
-
# new list. See #extract_beginning_with for a description of the side
|
434
|
-
# effects from calling this method.
|
435
|
-
#
|
436
|
-
# Returns the first item in the chain.
|
437
|
-
|
438
|
-
private def extract_ending_with(new_list = nil)
|
439
|
-
old_list = @list
|
440
|
-
# Count items and move them to the new list
|
441
|
-
first_item = self
|
442
|
-
count = 1 + loop.count do
|
443
|
-
first_item.list = new_list
|
444
|
-
first_item = first_item.prev
|
445
|
-
end
|
446
|
-
|
447
|
-
# Make sure the old list is in a valid state
|
448
|
-
if old_list
|
449
|
-
if last?
|
450
|
-
old_list.send :clear
|
451
|
-
else
|
452
|
-
old_list.send :shrink, count
|
453
|
-
# Fix the links within in the list
|
454
|
-
@next.prev = first_item.prev!
|
455
|
-
first_item.prev!.next = @next
|
456
|
-
end
|
457
|
-
else
|
458
|
-
# Disconnect the item directly after the chain
|
459
|
-
@next.prev = nil unless last?
|
460
|
-
end
|
461
|
-
|
462
|
-
# Disconnect the chain from the list
|
463
|
-
first_item.prev = @next = nil
|
464
|
-
|
465
|
-
# Preemptivly tell the new list to grow
|
466
|
-
new_list.send :grow, count if new_list
|
467
|
-
|
468
|
-
first_item
|
469
|
-
end
|
470
84
|
end
|
471
85
|
end
|
data/lib/linked/list/eol.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Linked
|
2
|
-
|
2
|
+
class List
|
3
3
|
# End Of List
|
4
4
|
#
|
5
5
|
# This class implements a special list item that is placed at both the end
|
6
|
-
# and the beginning of a chain of
|
6
|
+
# and the beginning of a chain of listable items to form a list. The naming
|
7
7
|
# (end of list) comes from the fact that this object, by returning true for
|
8
8
|
# calls to #nil?, signifies the end of a list of Items. In both directions
|
9
9
|
# as a matter of fact, which is why the head and tail objects defined by
|
10
|
-
# Item
|
10
|
+
# Item are combined into one.
|
11
11
|
#
|
12
12
|
# In a nutshell, the structure looks something like this:
|
13
13
|
#
|
@@ -18,13 +18,23 @@ module Linked
|
|
18
18
|
# +-| prev | next |<- ... ->| prev | next |-+
|
19
19
|
# +------+------+ +------+------+
|
20
20
|
|
21
|
-
class EOL
|
22
|
-
|
21
|
+
class EOL
|
22
|
+
include Listable
|
23
23
|
|
24
|
-
|
24
|
+
# Calling #delete on the EOL is not supported and would break the
|
25
|
+
# connection between the list and its items.
|
26
|
+
|
27
|
+
undef delete
|
28
|
+
|
29
|
+
# Creates a new enf-of-list, as part of a list, with no items yet added to
|
30
|
+
# it.
|
31
|
+
#
|
32
|
+
# list - a List object.
|
33
|
+
|
34
|
+
def initialize(list)
|
25
35
|
super()
|
26
|
-
|
27
|
-
|
36
|
+
self.list = list
|
37
|
+
reset
|
28
38
|
end
|
29
39
|
|
30
40
|
# EOL objects will return true when asked if they are nil. This is
|
@@ -43,7 +53,7 @@ module Linked
|
|
43
53
|
# See Item#append for more details.
|
44
54
|
|
45
55
|
def append(object)
|
46
|
-
empty? ? super :
|
56
|
+
empty? ? super : prev!.append(object)
|
47
57
|
end
|
48
58
|
|
49
59
|
# Inserts one or more items at the beginning of the list.
|
@@ -51,7 +61,15 @@ module Linked
|
|
51
61
|
# See Item#append for more details.
|
52
62
|
|
53
63
|
def prepend(object)
|
54
|
-
empty? ? super :
|
64
|
+
empty? ? super : next!.prepend(object)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Private helper to reset the EOL to its initial state. This method should
|
68
|
+
# never be called directly as it leaves the both the list and the items in
|
69
|
+
# an inconsistant state.
|
70
|
+
|
71
|
+
private def reset
|
72
|
+
self.prev = self.next = self
|
55
73
|
end
|
56
74
|
|
57
75
|
# Private helper to check if the item chain is empty.
|
@@ -59,7 +77,7 @@ module Linked
|
|
59
77
|
# Return true if the chain is empty, otherwise nil.
|
60
78
|
|
61
79
|
private def empty?
|
62
|
-
|
80
|
+
prev!.equal? self
|
63
81
|
end
|
64
82
|
end
|
65
83
|
end
|
data/lib/linked/list.rb
CHANGED
@@ -3,49 +3,20 @@
|
|
3
3
|
module Linked
|
4
4
|
# List
|
5
5
|
#
|
6
|
-
# This
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# Example
|
11
|
-
#
|
12
|
-
# class ListLike
|
13
|
-
# include Linked::List
|
14
|
-
#
|
15
|
-
# def initialize
|
16
|
-
# super
|
17
|
-
# ...
|
18
|
-
#
|
19
|
-
# A key implementation detail is the End-Of-List, or EOL object that sits
|
20
|
-
# between the list and the actual list items. It provides separation between
|
21
|
-
# the list and the actual list items.
|
6
|
+
# This class implements a linked list. Most importantly, the methods #head,
|
7
|
+
# #tail, #grow, #shrink and #create_item are implemented to comply with the
|
8
|
+
# requirements defined by Listable.
|
22
9
|
|
23
|
-
|
10
|
+
class List
|
24
11
|
include Enumerable
|
25
12
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
attr_reader :eol
|
31
|
-
private :eol
|
32
|
-
|
33
|
-
# Returns an object that responds to #next= and #prepend.
|
34
|
-
|
35
|
-
alias head eol
|
36
|
-
|
37
|
-
# Returns an object that responds to #prev= and #append.
|
38
|
-
|
39
|
-
alias tail eol
|
13
|
+
# Initializes the list. It is important that this method be called during
|
14
|
+
# the initialization of the including class, and that the instance variables
|
15
|
+
# @_item_count and @_eol never be accessed directly.
|
40
16
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# directly.
|
45
|
-
|
46
|
-
def initialize(*)
|
47
|
-
@eol = EOL.new list: self
|
48
|
-
@item_count = 0
|
17
|
+
def initialize
|
18
|
+
@_eol = EOL.new self
|
19
|
+
@_item_count = 0
|
49
20
|
|
50
21
|
super
|
51
22
|
end
|
@@ -55,8 +26,8 @@ module Linked
|
|
55
26
|
# this operation quite expensive.
|
56
27
|
|
57
28
|
def initialize_dup(source)
|
58
|
-
@
|
59
|
-
@
|
29
|
+
@_eol = EOL.new self
|
30
|
+
@_item_count = 0
|
60
31
|
|
61
32
|
source.each_item { |item| push item.dup }
|
62
33
|
|
@@ -82,9 +53,25 @@ module Linked
|
|
82
53
|
|
83
54
|
def item
|
84
55
|
raise NoMethodError if empty?
|
85
|
-
|
56
|
+
head.next
|
86
57
|
end
|
87
58
|
|
59
|
+
# Two lists are considered equal if the n:th item from each list are equal.
|
60
|
+
#
|
61
|
+
# other - any object.
|
62
|
+
#
|
63
|
+
# Returns true if the given object is a list and the items are equal.
|
64
|
+
|
65
|
+
def ==(other)
|
66
|
+
return false unless other.is_a? self.class
|
67
|
+
return false unless other.count == @_item_count
|
68
|
+
|
69
|
+
other_items = other.each_item
|
70
|
+
each_item.all? { |item| item == other_items.next }
|
71
|
+
end
|
72
|
+
|
73
|
+
alias eql? ==
|
74
|
+
|
88
75
|
# Access the first n item(s) in the list. If a block is given each item will
|
89
76
|
# be yielded to it. The first item, starting from the first in the list, for
|
90
77
|
# which the block returns true and the n - 1 items directly following it
|
@@ -101,9 +88,9 @@ module Linked
|
|
101
88
|
def first(n = 1)
|
102
89
|
raise ArgumentError, 'n cannot be negative' if n < 0
|
103
90
|
|
104
|
-
return first_item_after
|
91
|
+
return first_item_after head, n, count unless block_given?
|
105
92
|
|
106
|
-
item =
|
93
|
+
item = head
|
107
94
|
items_left = count
|
108
95
|
|
109
96
|
items_left.times do
|
@@ -131,9 +118,9 @@ module Linked
|
|
131
118
|
def last(n = 1)
|
132
119
|
raise ArgumentError, 'n cannot be negative' if n < 0
|
133
120
|
|
134
|
-
return last_item_before
|
121
|
+
return last_item_before tail, n, count unless block_given?
|
135
122
|
|
136
|
-
item =
|
123
|
+
item = tail
|
137
124
|
items_left = count
|
138
125
|
|
139
126
|
items_left.times do
|
@@ -155,7 +142,7 @@ module Linked
|
|
155
142
|
|
156
143
|
def count(*args)
|
157
144
|
if args.empty? && !block_given?
|
158
|
-
@
|
145
|
+
@_item_count
|
159
146
|
else
|
160
147
|
super
|
161
148
|
end
|
@@ -164,7 +151,7 @@ module Linked
|
|
164
151
|
# Returns true if the list does not contain any items.
|
165
152
|
|
166
153
|
def empty?
|
167
|
-
@
|
154
|
+
@_item_count == 0
|
168
155
|
end
|
169
156
|
|
170
157
|
# Insert an item at the end of the list. If the given object is not an
|
@@ -178,7 +165,7 @@ module Linked
|
|
178
165
|
# Returns self.
|
179
166
|
|
180
167
|
def push(object)
|
181
|
-
|
168
|
+
tail.append object
|
182
169
|
self
|
183
170
|
end
|
184
171
|
|
@@ -204,7 +191,7 @@ module Linked
|
|
204
191
|
# Returns self.
|
205
192
|
|
206
193
|
def unshift(object)
|
207
|
-
|
194
|
+
head.prepend object
|
208
195
|
self
|
209
196
|
end
|
210
197
|
|
@@ -236,7 +223,7 @@ module Linked
|
|
236
223
|
return to_enum(__method__) { count } unless block_given?
|
237
224
|
return if empty?
|
238
225
|
|
239
|
-
item =
|
226
|
+
item = head
|
240
227
|
loop { yield item = item.next }
|
241
228
|
end
|
242
229
|
|
@@ -249,7 +236,7 @@ module Linked
|
|
249
236
|
return to_enum(__method__) { count } unless block_given?
|
250
237
|
return if empty?
|
251
238
|
|
252
|
-
item =
|
239
|
+
item = tail
|
253
240
|
loop { yield item = item.prev }
|
254
241
|
end
|
255
242
|
|
@@ -259,7 +246,7 @@ module Linked
|
|
259
246
|
# (eol).
|
260
247
|
|
261
248
|
def freeze
|
262
|
-
|
249
|
+
@_eol.freeze
|
263
250
|
each_item(&:freeze)
|
264
251
|
super
|
265
252
|
end
|
@@ -289,7 +276,7 @@ module Linked
|
|
289
276
|
# method is called whenever an arbitrary object is pushed or unshifted onto
|
290
277
|
# the list and need to be wraped inside an Item.
|
291
278
|
#
|
292
|
-
# This method can be overridden to
|
279
|
+
# This method can be overridden to support different Item types.
|
293
280
|
#
|
294
281
|
# args - any arguments will be passed on to Item.new.
|
295
282
|
#
|
@@ -299,6 +286,16 @@ module Linked
|
|
299
286
|
Item.new(*args)
|
300
287
|
end
|
301
288
|
|
289
|
+
# Returns an object that responds to #next= and #prepend.
|
290
|
+
|
291
|
+
protected def head
|
292
|
+
@_eol
|
293
|
+
end
|
294
|
+
|
295
|
+
# Returns an object that responds to #prev= and #append.
|
296
|
+
|
297
|
+
alias tail head
|
298
|
+
|
302
299
|
# Internal method to grow the list with n elements. Never call this method
|
303
300
|
# without also inserting the n elements.
|
304
301
|
#
|
@@ -307,7 +304,7 @@ module Linked
|
|
307
304
|
# Returns updated the item count.
|
308
305
|
|
309
306
|
private def grow(n = 1)
|
310
|
-
@
|
307
|
+
@_item_count += n
|
311
308
|
end
|
312
309
|
|
313
310
|
# Internal method to shrink the list with n elements. Never call this method
|
@@ -318,7 +315,7 @@ module Linked
|
|
318
315
|
# Returns updated the item count.
|
319
316
|
|
320
317
|
private def shrink(n = 1)
|
321
|
-
@
|
318
|
+
@_item_count -= n
|
322
319
|
end
|
323
320
|
|
324
321
|
# Private method to clear the list. Never call this method without also
|
@@ -328,13 +325,11 @@ module Linked
|
|
328
325
|
# b) clear the `next` pointer of the last item.
|
329
326
|
|
330
327
|
private def clear
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
@item_count = 0
|
328
|
+
@_eol.send :reset
|
329
|
+
@_item_count = 0
|
335
330
|
end
|
336
331
|
|
337
|
-
#
|
332
|
+
# Private helper method that returns the first n items, starting just
|
338
333
|
# after item, given that there are items_left items left. Knowing the exact
|
339
334
|
# number of items left is not cruicial but does impact speed. The number
|
340
335
|
# should not be lower than the actual ammount. The following must
|
@@ -351,10 +346,10 @@ module Linked
|
|
351
346
|
# n == 1) an item if items_left > 0 or nil
|
352
347
|
# n > 1) an array of items if items_left > 0 or an empty array
|
353
348
|
|
354
|
-
|
349
|
+
private def first_item_after(item, n, items_left = @_item_count)
|
355
350
|
# Optimize for these cases
|
356
351
|
return nil if n == 0
|
357
|
-
return n > 1 ? [] : nil if item.
|
352
|
+
return n > 1 ? [] : nil if item.last?
|
358
353
|
return item.next if n == 1
|
359
354
|
|
360
355
|
n = items_left if n > items_left
|
@@ -366,7 +361,7 @@ module Linked
|
|
366
361
|
arr.compact! || arr
|
367
362
|
end
|
368
363
|
|
369
|
-
#
|
364
|
+
# Private helper method that returns the last n items, ending just before
|
370
365
|
# item, given that there are items_left items left. Knowing the exact
|
371
366
|
# number of items left is not cruicial but does impact speed. The number
|
372
367
|
# should not be lower than the actual ammount. The following must hold for
|
@@ -383,10 +378,10 @@ module Linked
|
|
383
378
|
# n == 1) an item if items_left > 0 or nil
|
384
379
|
# n > 1) an array of items if items_left > 0 or an empty array
|
385
380
|
|
386
|
-
|
381
|
+
private def last_item_before(item, n, items_left = @_item_count)
|
387
382
|
# Optimize for these cases
|
388
383
|
return nil if n == 0
|
389
|
-
return n > 1 ? [] : nil if item.
|
384
|
+
return n > 1 ? [] : nil if item.first?
|
390
385
|
return item.prev if n == 1
|
391
386
|
|
392
387
|
n = items_left if n > items_left
|
@@ -397,13 +392,5 @@ module Linked
|
|
397
392
|
rescue StopIteration
|
398
393
|
arr.compact! || arr
|
399
394
|
end
|
400
|
-
|
401
|
-
# This method is called whenever the module is included somewhere. In the
|
402
|
-
# special case when List is included in an Item the #item method must be
|
403
|
-
# changed to return self.
|
404
|
-
|
405
|
-
def self.included(klass)
|
406
|
-
klass.send(:define_method, :item) { self } if klass < Item
|
407
|
-
end
|
408
395
|
end
|
409
396
|
end
|
@@ -0,0 +1,441 @@
|
|
1
|
+
module Linked
|
2
|
+
# Listable
|
3
|
+
# List (optional)
|
4
|
+
# +----^-----+
|
5
|
+
# prev <- | Listable | -> next
|
6
|
+
# +----------+
|
7
|
+
#
|
8
|
+
# This module implements doubly linked list items, designed to work both on
|
9
|
+
# their own in a chain, and as children of list.
|
10
|
+
#
|
11
|
+
# An object is considered a list if it responds to #grow, #shrink and
|
12
|
+
# #create_item. The former two facilitate counting of the items and will be
|
13
|
+
# called everytime an item is appended, prepended or deleted. #create_item is
|
14
|
+
# expected to return a new object that is compatible with the list.
|
15
|
+
#
|
16
|
+
# Notation
|
17
|
+
# --------
|
18
|
+
#
|
19
|
+
# Some methods operate on chains of items, and to describe the effects of an
|
20
|
+
# operation the following syntax is used.
|
21
|
+
#
|
22
|
+
# A ( A <> B ) [ A <> B ]
|
23
|
+
# (i) (ii) (iii)
|
24
|
+
#
|
25
|
+
# Single items are denoted with capital letters (i), while chains are written
|
26
|
+
# as multiple connected items (ii). The parenthesis are optional. To show that
|
27
|
+
# one or more nodes are wraped in a list, angle brackets are used (iii).
|
28
|
+
|
29
|
+
module Listable
|
30
|
+
# Creates a new item. Always make a call to super whenever implementing this
|
31
|
+
# method in a subclass.
|
32
|
+
|
33
|
+
def initialize(*)
|
34
|
+
@_next = @_prev = @_list = nil
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
# Calling #dup on an item returns a copy that is no longer connected to the
|
39
|
+
# original item chain, or the list. The value will also be copied.
|
40
|
+
#
|
41
|
+
# Returns a new Item.
|
42
|
+
|
43
|
+
def initialize_copy(*)
|
44
|
+
@_next = @_prev = @_list = nil
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
# Identity method that simply return the item. This method mirrors List#item
|
49
|
+
# and allows other methods that work on Item objects to easily and
|
50
|
+
# interchangebly accept both lists and items as arguments.
|
51
|
+
#
|
52
|
+
# Returns the item itself.
|
53
|
+
|
54
|
+
def item
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Access the list that the item is part of. If the item is not in a list a
|
59
|
+
# NoMethodError will be raised. This mirrors the behaviour of List#list and
|
60
|
+
# allows other methods that work on List objects to easily and
|
61
|
+
# interchangeably accept both lists and items as arguments.
|
62
|
+
#
|
63
|
+
# Returns the list that the item is part of.
|
64
|
+
|
65
|
+
def list
|
66
|
+
raise NoMethodError unless @_list
|
67
|
+
@_list
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check it the item is part of a list.
|
71
|
+
#
|
72
|
+
# Returns true if the item is in a list.
|
73
|
+
|
74
|
+
def in_list?
|
75
|
+
@_list ? true : false
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check if this is the first item in the list. It is crucial that tail#nil?
|
79
|
+
# returns true for the first item to be identified correctly.
|
80
|
+
#
|
81
|
+
# Returns true if no item come before this one.
|
82
|
+
|
83
|
+
def first?
|
84
|
+
@_prev.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
# Check if this is the last item in the list. It is crucial that head#nil?
|
88
|
+
# returns true for the last item to be identified correctly.
|
89
|
+
#
|
90
|
+
# Returns true if no item come after this one.
|
91
|
+
|
92
|
+
def last?
|
93
|
+
@_next.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check if the item is in the given list.
|
97
|
+
#
|
98
|
+
# list - any object.
|
99
|
+
#
|
100
|
+
# Returns true if the item is part of the given list.
|
101
|
+
|
102
|
+
def in?(list)
|
103
|
+
@_list.equal? list
|
104
|
+
end
|
105
|
+
|
106
|
+
# Access the next item in the list. If this is the last one a StopIteration
|
107
|
+
# will be raised, so that items may be iterated over safely in a loop.
|
108
|
+
#
|
109
|
+
# Example
|
110
|
+
# loop do
|
111
|
+
# item = item.next
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# Returns the item that come after this.
|
115
|
+
|
116
|
+
def next
|
117
|
+
raise StopIteration if last?
|
118
|
+
@_next
|
119
|
+
end
|
120
|
+
|
121
|
+
# Access the previous item in the list. If this is the first one a
|
122
|
+
# StopIteration will be raised, so that items may be iterated over safely in
|
123
|
+
# a loop.
|
124
|
+
#
|
125
|
+
# Example
|
126
|
+
# loop do
|
127
|
+
# item = item.prev
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# Returns the item that come before this.
|
131
|
+
|
132
|
+
def prev
|
133
|
+
raise StopIteration if first?
|
134
|
+
@_prev
|
135
|
+
end
|
136
|
+
|
137
|
+
alias previous prev
|
138
|
+
|
139
|
+
# Inserts the given item between this one and the one after it (if any). If
|
140
|
+
# the given item is part of a chain, all items following it will be moved to
|
141
|
+
# this one, and added to the list if one is set.
|
142
|
+
#
|
143
|
+
# Example for the chain (A <> C)
|
144
|
+
#
|
145
|
+
# A.append B # => (A <> B <> C)
|
146
|
+
#
|
147
|
+
# Alternativly the argument can be an arbitrary object, in which case a new
|
148
|
+
# item will be created around it.
|
149
|
+
#
|
150
|
+
# If this item is part of a list #grow will be called on it with the
|
151
|
+
# number of added items as an argument. Should it also be the last item
|
152
|
+
# #prev= will be called on the list tail.
|
153
|
+
#
|
154
|
+
# object - the item to append, or an arbitrary object to be wraped in a new
|
155
|
+
# item. If in a list it will be asked to create the new item via
|
156
|
+
# List#create_item.
|
157
|
+
#
|
158
|
+
# Returns the last item that was appended.
|
159
|
+
|
160
|
+
def append(object)
|
161
|
+
if object.respond_to? :item
|
162
|
+
first_item = object.item
|
163
|
+
last_item = first_item.send :extract_beginning_with, @_list
|
164
|
+
else
|
165
|
+
if @_list
|
166
|
+
first_item = last_item = @_list.send :create_item, object
|
167
|
+
first_item.list = @_list
|
168
|
+
@_list.send :grow
|
169
|
+
else
|
170
|
+
first_item = last_item = create_item object
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
first_item.prev = self
|
175
|
+
@_next.prev = last_item if @_next
|
176
|
+
@_next, last_item.next = first_item, @_next
|
177
|
+
|
178
|
+
last_item
|
179
|
+
end
|
180
|
+
|
181
|
+
# Inserts the given item between this one and the one before it (if any). If
|
182
|
+
# the given item is part of a chain, all items preceeding it will be moved
|
183
|
+
# to this one, and added to the list if one is set.
|
184
|
+
#
|
185
|
+
# Example for the chain (A <> C)
|
186
|
+
#
|
187
|
+
# C.prepend B # => (A <> B <> C)
|
188
|
+
#
|
189
|
+
# Alternativly the argument can be an arbitrary object, in which case a new
|
190
|
+
# item will be created around it.
|
191
|
+
#
|
192
|
+
# If this item is part of a list #grow will be called on it with the
|
193
|
+
# number of added items as an argument. Should it also be the first item
|
194
|
+
# #next= will be called on the list head.
|
195
|
+
#
|
196
|
+
# object - the item to prepend. or an arbitrary object to be wraped in a
|
197
|
+
# new item. If in a list it will be asked to create the new item
|
198
|
+
# via List#create_item.
|
199
|
+
#
|
200
|
+
# Returns the last item that was prepended.
|
201
|
+
|
202
|
+
def prepend(object)
|
203
|
+
if object.respond_to? :item
|
204
|
+
last_item = object.item
|
205
|
+
first_item = last_item.send :extract_ending_with, @_list
|
206
|
+
else
|
207
|
+
if @_list
|
208
|
+
first_item = last_item = @_list.send :create_item, object
|
209
|
+
first_item.list = @_list
|
210
|
+
@_list.send :grow
|
211
|
+
else
|
212
|
+
first_item = last_item = create_item object
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
last_item.next = self
|
217
|
+
@_prev.next = first_item if @_prev
|
218
|
+
@_prev, first_item.prev = last_item, @_prev
|
219
|
+
|
220
|
+
first_item
|
221
|
+
end
|
222
|
+
|
223
|
+
# Remove an item from the chain. If this item is part of a list and is
|
224
|
+
# either first, last or both in that list, #next= and #prev= will be called
|
225
|
+
# on the list head and tail respectivly.
|
226
|
+
#
|
227
|
+
# If this item is part of a list #shrink will be called on it.
|
228
|
+
#
|
229
|
+
# Returns self.
|
230
|
+
|
231
|
+
def delete
|
232
|
+
@_next.prev = @_prev if @_next
|
233
|
+
@_prev.next = @_next if @_prev
|
234
|
+
@_list.send :shrink if @_list
|
235
|
+
|
236
|
+
@_next = @_prev = @_list = nil
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
# Remove all items before this one in the chain. If the items are part of a
|
241
|
+
# list they will be removed from it.
|
242
|
+
#
|
243
|
+
# Returns the last item in the chain that was just deleted, or nil if this
|
244
|
+
# is the first item.
|
245
|
+
|
246
|
+
def delete_before
|
247
|
+
@_prev.send :extract_ending_with unless first?
|
248
|
+
end
|
249
|
+
|
250
|
+
# Remove all items after this one in the chain. If the items are part of a
|
251
|
+
# list they will be removed from it.
|
252
|
+
#
|
253
|
+
# Returns the last item in the chain that was just deleted, or nil if this
|
254
|
+
# is the first item.
|
255
|
+
|
256
|
+
def delete_after
|
257
|
+
@_next.send :extract_beginning_with unless last?
|
258
|
+
end
|
259
|
+
|
260
|
+
# Iterates over each item before this, in reverse order. If a block is not
|
261
|
+
# given an enumerator is returned.
|
262
|
+
#
|
263
|
+
# Note that raising a StopIteraion inside the block will cause the loop to
|
264
|
+
# silently stop the iteration early.
|
265
|
+
|
266
|
+
def before
|
267
|
+
return to_enum(__callee__) unless block_given?
|
268
|
+
return if first?
|
269
|
+
|
270
|
+
item = self.prev
|
271
|
+
|
272
|
+
loop do
|
273
|
+
yield item
|
274
|
+
item = item.prev
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Iterates over each item after this. If a block is not given an enumerator
|
279
|
+
# is returned.
|
280
|
+
#
|
281
|
+
# Note that raising a StopIteraion inside the block will cause the loop to
|
282
|
+
# silently stop the iteration early.
|
283
|
+
|
284
|
+
def after
|
285
|
+
return to_enum(__callee__) unless block_given?
|
286
|
+
return if last?
|
287
|
+
|
288
|
+
item = self.next
|
289
|
+
|
290
|
+
loop do
|
291
|
+
yield item
|
292
|
+
item = item.next
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Protected factory method for creating items compatible with this listable
|
297
|
+
# item. This method is called whenever an arbitrary object is appended or
|
298
|
+
# prepended onto this item and need to be wraped/converted.
|
299
|
+
#
|
300
|
+
# This method can be overridden to support different behaviours.
|
301
|
+
#
|
302
|
+
# args - any arguments will be passed on to .new.
|
303
|
+
#
|
304
|
+
# Returns a new Listable object.
|
305
|
+
|
306
|
+
protected def create_item(*args)
|
307
|
+
self.class.new(*args)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Protected unsafe accessor of the next item in the list. It is preferable
|
311
|
+
# to use #next, possibly in conjunction with #last?.
|
312
|
+
#
|
313
|
+
# Returns the item that come after this, or nil if this is the last one.
|
314
|
+
|
315
|
+
protected def next!
|
316
|
+
@_next
|
317
|
+
end
|
318
|
+
|
319
|
+
# Calling #next= directly is not recommended since it may corrupt the chain.
|
320
|
+
|
321
|
+
protected def next=(other)
|
322
|
+
@_next = other
|
323
|
+
end
|
324
|
+
|
325
|
+
# Protected, unsafe accessor of the previous item in the list. It is
|
326
|
+
# preferable to use #prev, possibly in conjunction with #first?.
|
327
|
+
#
|
328
|
+
# Returns the item that come before this, or nil if this is the first one.
|
329
|
+
|
330
|
+
protected def prev!
|
331
|
+
@_prev
|
332
|
+
end
|
333
|
+
|
334
|
+
# Calling #prev= directly is not recommended since it may corrupt the chain.
|
335
|
+
|
336
|
+
protected def prev=(other)
|
337
|
+
@_prev = other
|
338
|
+
end
|
339
|
+
|
340
|
+
# Calling #list= directly is not recommended since it may corrupt the list.
|
341
|
+
|
342
|
+
protected def list=(list)
|
343
|
+
@_list = list
|
344
|
+
end
|
345
|
+
|
346
|
+
# PRIVATE DANGEROUS METHOD. This method should never be called directly
|
347
|
+
# since it may leave the extracted item chain in an invalid state.
|
348
|
+
#
|
349
|
+
# This method extracts the item, together with the chain following it, from
|
350
|
+
# the list they are in (if any) and optionally facilitates moving them to a
|
351
|
+
# new list.
|
352
|
+
#
|
353
|
+
# Given the two lists
|
354
|
+
# [ A <> B <> C ] [ D ]
|
355
|
+
# (I) (II)
|
356
|
+
#
|
357
|
+
# calling B.extract_beginning_with(II) will result in (B <> C) being removed
|
358
|
+
# from (I), and (II) to be grown by two. (B <> C) will now reference (II)
|
359
|
+
# but they will not yet be linked to any of the items in it. It is therefore
|
360
|
+
# necessary to insert them directly after calling this method, or (II) will
|
361
|
+
# be left in an invalid state.
|
362
|
+
#
|
363
|
+
# Returns the last item of the chain.
|
364
|
+
|
365
|
+
private def extract_beginning_with(new_list = nil)
|
366
|
+
old_list = @_list
|
367
|
+
# Count items and move them to the new list
|
368
|
+
last_item = self
|
369
|
+
count = 1 + loop.count do
|
370
|
+
last_item.list = new_list
|
371
|
+
last_item = last_item.next
|
372
|
+
end
|
373
|
+
|
374
|
+
# Make sure the old list is in a valid state
|
375
|
+
if old_list
|
376
|
+
if first?
|
377
|
+
old_list.send :clear
|
378
|
+
else
|
379
|
+
old_list.send :shrink, count
|
380
|
+
# Fix the links within in the list
|
381
|
+
@_prev.next = last_item.next!
|
382
|
+
last_item.next!.prev = @_prev
|
383
|
+
end
|
384
|
+
else
|
385
|
+
# Disconnect the item directly after the chain
|
386
|
+
@_prev.next = nil unless first?
|
387
|
+
end
|
388
|
+
|
389
|
+
# Disconnect the chain from the list
|
390
|
+
@_prev = last_item.next = nil
|
391
|
+
|
392
|
+
# Preemptivly tell the new list to grow
|
393
|
+
new_list.send :grow, count if new_list
|
394
|
+
|
395
|
+
last_item
|
396
|
+
end
|
397
|
+
|
398
|
+
# PRIVATE DANGEROUS METHOD. This method should never be called directly
|
399
|
+
# since it may leave the extracted item chain in an invalid state.
|
400
|
+
#
|
401
|
+
# This method extracts the item, together with the chain preceding it, from
|
402
|
+
# the list they are in (if any) and optionally facilitates moving them to a
|
403
|
+
# new list. See #extract_beginning_with for a description of the side
|
404
|
+
# effects from calling this method.
|
405
|
+
#
|
406
|
+
# Returns the first item in the chain.
|
407
|
+
|
408
|
+
private def extract_ending_with(new_list = nil)
|
409
|
+
old_list = @_list
|
410
|
+
# Count items and move them to the new list
|
411
|
+
first_item = self
|
412
|
+
count = 1 + loop.count do
|
413
|
+
first_item.list = new_list
|
414
|
+
first_item = first_item.prev
|
415
|
+
end
|
416
|
+
|
417
|
+
# Make sure the old list is in a valid state
|
418
|
+
if old_list
|
419
|
+
if last?
|
420
|
+
old_list.send :clear
|
421
|
+
else
|
422
|
+
old_list.send :shrink, count
|
423
|
+
# Fix the links within in the list
|
424
|
+
@_next.prev = first_item.prev!
|
425
|
+
first_item.prev!.next = @_next
|
426
|
+
end
|
427
|
+
else
|
428
|
+
# Disconnect the item directly after the chain
|
429
|
+
@_next.prev = nil unless last?
|
430
|
+
end
|
431
|
+
|
432
|
+
# Disconnect the chain from the list
|
433
|
+
first_item.prev = @_next = nil
|
434
|
+
|
435
|
+
# Preemptivly tell the new list to grow
|
436
|
+
new_list.send :grow, count if new_list
|
437
|
+
|
438
|
+
first_item
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
data/lib/linked/version.rb
CHANGED
data/lib/linked.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linked
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Lindberg
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -87,6 +87,7 @@ files:
|
|
87
87
|
- lib/linked/item.rb
|
88
88
|
- lib/linked/list.rb
|
89
89
|
- lib/linked/list/eol.rb
|
90
|
+
- lib/linked/listable.rb
|
90
91
|
- lib/linked/version.rb
|
91
92
|
- linked.gemspec
|
92
93
|
homepage: https://github.com/seblindberg/ruby-linked
|