linked 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|