linked 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linked
4
+ # Expects the list to implement `#list_head` and `#list_tail`.
5
+ module ListEnumerable
6
+ include Enumerable
7
+
8
+ # Iterates over each item in the list If a block is not given an enumerator
9
+ # is returned.
10
+ def each_item
11
+ return to_enum(__method__) { count } unless block_given?
12
+ return if empty?
13
+
14
+ item = list_head
15
+ loop do
16
+ yield item
17
+ item = item.next
18
+ end
19
+ end
20
+
21
+ alias each each_item
22
+
23
+ # Iterates over each item in the list in reverse order. If a block is not
24
+ # given an enumerator is returned.
25
+ def reverse_each_item
26
+ return to_enum(__method__) { count } unless block_given?
27
+ return if empty?
28
+
29
+ item = list_tail
30
+ loop do
31
+ yield item
32
+ item = item.prev
33
+ end
34
+ end
35
+
36
+ alias reverse_each reverse_each_item
37
+
38
+ # Access the first n item(s) in the list.
39
+ #
40
+ # n - the number of items to return.
41
+ #
42
+ # Returns, for different values of n:
43
+ # n = nil) an item if the list contains one, or nil.
44
+ # n >= 0) an array of between 0 and n items, depending on how many are in.
45
+ # the list.
46
+ def first(n = nil)
47
+ return list_head unless n
48
+ raise ArgumentError, 'n cannot be negative' if n.negative?
49
+
50
+ return [] if n.zero? || empty?
51
+
52
+ list_head.take n
53
+ end
54
+
55
+ # Access the first n item(s) in the list.
56
+ #
57
+ # n - the number of items to return.
58
+ #
59
+ # Returns, for different values of n:
60
+ # n = nil) an item if the list contains one, or nil.
61
+ # n >= 0) an array of between 0 and n items, depending on how many are in.
62
+ # the list. The order is preserved.
63
+ def last(n = nil)
64
+ return empty? ? nil : list_tail unless n
65
+ raise ArgumentError, 'n cannot be negative' if n.negative?
66
+
67
+ return [] if n.zero? || empty?
68
+
69
+ list_tail.take(-n)
70
+ end
71
+
72
+ # Overrides the Enumerable#count method when given no argument to provide a
73
+ # fast item count. Instead of iterating over each item, the internal item
74
+ # count is returned.
75
+ #
76
+ # @param args [Array<Object>] see Enumerable#count
77
+ # @return [Integer] the number of items counted.
78
+ def count(*args)
79
+ if args.empty? && !block_given?
80
+ empty? ? 0 : list_head.chain_length
81
+ else
82
+ super
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,47 +1,57 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Linked
2
- # Listable
3
- # List (optional)
4
- # +----^-----+
5
- # prev <- | Listable | -> next
6
- # +----------+
4
+ # The listable item is the foundational element of the linked list. Each link
5
+ # in the chain knows what comes both before and after, as well as which
6
+ # elements are in the beginning and end of the chain. This information can be
7
+ # used to iterate over the chained elements.
8
+ #
9
+ # Internally each listable item stores three pointers: one to the head of the
10
+ # chain and two for the previous and next items respectivly. The head of the
11
+ # chain uses the head pointer to store how many elements are currently in the
12
+ # chain, for fast access. Furthermore it uses its pointer to the previous
13
+ # element to keep track of the last element of the chain.
14
+ #
15
+ # In pracitce this means that some operations are fast, or computationally
16
+ # cheap, while other are more expensive. The follwing actions are fast:
7
17
  #
8
- # This module implements doubly linked list items, designed to work both on
9
- # their own in a chain, and as children of list.
18
+ # 1) Accessing the previous and next item.
19
+ # 2) Accessing the first and last element of the chain.
20
+ # 3) Calculating the length of the chain.
21
+ # 4) Appending items.
22
+ # 5) Deleting any item but the first one.
10
23
  #
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.
24
+ # On the flip side, the following are the expensive operations:
15
25
  #
16
- # Notation
17
- # --------
26
+ # 1) Prepending items.
27
+ # 2) Deleting the first item.
28
+ # 3) Splitting the chain.
18
29
  #
30
+ # === Notation
19
31
  # Some methods operate on chains of items, and to describe the effects of an
20
32
  # operation the following syntax is used.
21
33
  #
22
- # A ( A <> B ) [ A <> B ]
23
- # (i) (ii) (iii)
34
+ # A A <> B
35
+ # (i) (ii)
24
36
  #
25
37
  # 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
-
38
+ # as multiple connected items (ii).
29
39
  module Listable
30
- # Creates a new item. Always make a call to super whenever implementing this
31
- # method in a subclass.
40
+ include Util
32
41
 
42
+ # Creates a new item. Always make a call to super whenever overriding this
43
+ # method in an including class.
33
44
  def initialize(*)
34
- @_next = @_prev = @_list = nil
45
+ reset_item
35
46
  super
36
47
  end
37
48
 
38
49
  # Calling #dup on an item returns a copy that is no longer connected to the
39
50
  # original item chain, or the list. The value will also be copied.
40
51
  #
41
- # Returns a new Item.
42
-
52
+ # @return [Listable] a new Listable.
43
53
  def initialize_copy(*)
44
- @_next = @_prev = @_list = nil
54
+ reset_item
45
55
  super
46
56
  end
47
57
 
@@ -49,212 +59,233 @@ module Linked
49
59
  # and allows other methods that work on Item objects to easily and
50
60
  # interchangebly accept both lists and items as arguments.
51
61
  #
52
- # Returns the item itself.
53
-
62
+ # @return [Listable] the item itself.
54
63
  def item
55
64
  self
56
65
  end
57
66
 
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.
67
+ # Returns true if no item come before this one. Note that the implementation
68
+ # of this method is protected and publicly accessible through its alias
69
+ # #first?.
62
70
  #
63
- # Returns the list that the item is part of.
64
-
65
- def list
66
- raise NoMethodError unless @_list
67
- @_list
71
+ # @return [true] if the item is the head of the chain.
72
+ # @return [false] otherwise.
73
+ def chain_head?
74
+ # p @_chain_head.is_a? Numeric
75
+ # @_chain_head.is_a? Numeric
76
+ @_prev.chain_tail?
68
77
  end
69
78
 
70
- # Check it the item is part of a list.
71
- #
72
- # Returns true if the item is in a list.
79
+ alias first? chain_head?
80
+ protected :chain_head?
73
81
 
74
- def in_list?
75
- @_list ? true : false
82
+ # Returns true if no item come after this one. Note that the implementation
83
+ # of this method is protected and publicly accessible through its alias
84
+ # #last?.
85
+ #
86
+ # @return [true] if the item is the tail of the chain.
87
+ # @return [false] otherwise.
88
+ def chain_tail?
89
+ @_next.nil?
76
90
  end
77
91
 
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.
92
+ alias last? chain_tail?
93
+ protected :chain_tail?
82
94
 
83
- def first?
84
- @_prev.nil?
95
+ # Returns the first item in the chain. Note that the implementation of this
96
+ # method is protected and publicly accessible through its aliases
97
+ # #first_in_chain and #chain.
98
+ #
99
+ # @return [Listable] the first item in the chain.
100
+ def chain_head
101
+ chain_head? ? self : chain_head!
85
102
  end
86
103
 
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.
104
+ alias first_in_chain chain_head
105
+ alias chain chain_head
106
+ protected :chain_head
91
107
 
92
- def last?
93
- @_next.nil?
108
+ # Returns the last item in the chain. Note that the implementation of this
109
+ # method is protected and publicly accessible through its aliases
110
+ # #last_in_chain.
111
+ #
112
+ # @return [Listable] the last item in the chain.
113
+ def chain_tail
114
+ chain_tail? ? self : chain_head.prev!
94
115
  end
95
116
 
96
- # Check if the item is in the given list.
97
- #
98
- # list - any object.
117
+ alias last_in_chain chain_tail
118
+ protected :chain_tail
119
+
120
+ # Returns the number of items in the current chain.
99
121
  #
100
- # Returns true if the item is part of the given list.
122
+ # @return [Integer] the number of items in the chain.
123
+ def chain_length
124
+ chain_head.chain_head!
125
+ end
101
126
 
102
- def in?(list)
103
- @_list.equal? list
127
+ # Check if this object is in a chain.
128
+ #
129
+ # @param other [Object] the object to check.
130
+ # @return [true] if this object is in the same chain as the given one.
131
+ # @return [false] otherwise.
132
+ def in_chain?(other)
133
+ return false unless other.is_a? Listable
134
+ chain_head.equal? other.chain_head
104
135
  end
105
136
 
106
- # Access the next item in the list. If this is the last one a StopIteration
137
+ alias === in_chain?
138
+
139
+ # Access the next item in the chain. If this is the last one a StopIteration
107
140
  # will be raised, so that items may be iterated over safely in a loop.
108
141
  #
109
- # Example
142
+ # === Usage
110
143
  # loop do
111
144
  # item = item.next
112
145
  # end
113
146
  #
114
- # Returns the item that come after this.
115
-
147
+ # @raise [StopIteration] if this item is the last in the chain.
148
+ #
149
+ # @return [Listable] the item that come after this.
116
150
  def next
117
- raise StopIteration if last?
151
+ raise StopIteration if chain_tail?
118
152
  @_next
119
153
  end
120
154
 
121
- # Access the previous item in the list. If this is the first one a
155
+ # Access the previous item in the chain. If this is the first one a
122
156
  # StopIteration will be raised, so that items may be iterated over safely in
123
157
  # a loop.
124
158
  #
125
- # Example
159
+ # === Usage
126
160
  # loop do
127
161
  # item = item.prev
128
162
  # end
129
163
  #
130
- # Returns the item that come before this.
131
-
164
+ # @raise [StopIteration] if this item is the first in the chain.
165
+ #
166
+ # @return [Listable] the item that come before this.
132
167
  def prev
133
- raise StopIteration if first?
168
+ raise StopIteration if chain_head?
134
169
  @_prev
135
170
  end
136
171
 
137
172
  alias previous prev
138
173
 
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.
174
+ # rubocop:disable Metrics/MethodLength
159
175
 
160
176
  def append(object)
161
- if object.respond_to? :item
162
- first_item = object.item
163
- last_item = first_item.send :extract_beginning_with, @_list
177
+ # Assume the given object to be the head of its chain
178
+ # B. If it is not, chain B will be split before the
179
+ # object, and the sub chain in which the object now is
180
+ # the head will be appended.
181
+ sub_chain_b_head = coerce object
182
+
183
+ # Grab the first item in this chain. We will need it
184
+ # later.
185
+ target_chain = chain_head
186
+
187
+ # Split chain B before the given object and grab the
188
+ # tail of that new sub chain.
189
+ sub_chain_b_tail = sub_chain_b_head.split_before_and_insert target_chain
190
+
191
+ # If we are the last item in our chain we need to
192
+ # notify the head that there is a new tail.
193
+ # Otherwise the next item in the list need to be
194
+ # linked up correctly.
195
+ if chain_tail?
196
+ target_chain.prev = sub_chain_b_tail
164
197
  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
198
+ sub_chain_b_tail.next = next!
199
+ next!.prev = sub_chain_b_tail
172
200
  end
173
201
 
174
- first_item.prev = self
175
- @_next.prev = last_item if @_next
176
- @_next, last_item.next = first_item, @_next
202
+ # Connect sub chain B to this item
203
+ sub_chain_b_head.prev = self
204
+ self.next = sub_chain_b_head
177
205
 
178
- last_item
206
+ sub_chain_b_tail
179
207
  end
180
208
 
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.
209
+ # rubocop:enable Metrics/MethodLength
210
+
211
+ # rubocop:disable Metrics/MethodLength
201
212
 
202
213
  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
+ sub_chain_a_tail = coerce object
215
+
216
+ if chain_head?
217
+ sub_chain_a_tail.split_after_and_insert
218
+ sub_chain_a_tail.append self
219
+
220
+ return chain_head
214
221
  end
215
222
 
216
- last_item.next = self
217
- @_prev.next = first_item if @_prev
218
- @_prev, first_item.prev = last_item, @_prev
223
+ target_chain = chain_head
224
+
225
+ sub_chain_a_head = sub_chain_a_tail.split_after_and_insert target_chain
226
+
227
+ prev!.next = sub_chain_a_head
228
+ sub_chain_a_head.prev = prev!
229
+
230
+ sub_chain_a_tail.next = self
231
+ self.prev = sub_chain_a_tail
219
232
 
220
- first_item
233
+ sub_chain_a_head
221
234
  end
222
235
 
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.
236
+ # rubocop:enable Metrics/MethodLength
237
+
238
+ # rubocop:disable Metrics/MethodLength
239
+
240
+ # Remove an item from the chain. Note that calling #delete on the first item
241
+ # in a chain causes all subsequent items to be moved to a new chain.
226
242
  #
227
- # If this item is part of a list #shrink will be called on it.
243
+ # === Usage
244
+ # Example using the chain A <> B <> C
228
245
  #
229
- # Returns self.
230
-
246
+ # A.delete # => A | B <> C
247
+ # B.delete # => B | A <> C
248
+ # C.delete # => C | A <> B
249
+ #
250
+ # @return [self]
231
251
  def delete
232
- @_next.prev = @_prev if @_next
233
- @_prev.next = @_next if @_prev
234
- @_list.send :shrink if @_list
252
+ if chain_head?
253
+ split_after_and_insert
254
+ else
255
+ shrink
235
256
 
236
- @_next = @_prev = @_list = nil
237
- self
257
+ if chain_tail?
258
+ chain_head.prev = @_prev
259
+ else
260
+ @_next.prev = @_prev
261
+ end
262
+
263
+ @_prev.next = @_next
264
+ end
265
+
266
+ reset_item
238
267
  end
239
268
 
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.
269
+ # rubocop:enable Metrics/MethodLength
245
270
 
271
+ # Remove all items before this one in the chain.
272
+ #
273
+ # @return [nil] if this is the first item.
274
+ # @return [Listable] the first item in the chain that was just deleted.
246
275
  def delete_before
247
- @_prev.send :extract_ending_with unless first?
276
+ prev!.split_after_and_insert unless chain_head?
248
277
  end
249
278
 
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.
279
+ # Remove all items after this one in the chain.
252
280
  #
253
- # Returns the last item in the chain that was just deleted, or nil if this
254
- # is the first item.
255
-
281
+ # @return [nil] if this is the lst item.
282
+ # @return [Listable] the first item in the chain that was just deleted.
256
283
  def delete_after
257
- @_next.send :extract_beginning_with unless last?
284
+ return nil if chain_tail?
285
+
286
+ item = next!
287
+ item.split_before_and_insert
288
+ item
258
289
  end
259
290
 
260
291
  # Iterates over each item before this, in reverse order. If a block is not
@@ -262,12 +293,11 @@ module Linked
262
293
  #
263
294
  # Note that raising a StopIteraion inside the block will cause the loop to
264
295
  # silently stop the iteration early.
265
-
266
296
  def before
267
297
  return to_enum(__callee__) unless block_given?
268
- return if first?
298
+ return if chain_head?
269
299
 
270
- item = self.prev
300
+ item = prev
271
301
 
272
302
  loop do
273
303
  yield item
@@ -280,10 +310,9 @@ module Linked
280
310
  #
281
311
  # Note that raising a StopIteraion inside the block will cause the loop to
282
312
  # silently stop the iteration early.
283
-
284
313
  def after
285
314
  return to_enum(__callee__) unless block_given?
286
- return if last?
315
+ return if chain_tail?
287
316
 
288
317
  item = self.next
289
318
 
@@ -293,6 +322,60 @@ module Linked
293
322
  end
294
323
  end
295
324
 
325
+ # rubocop:disable Metrics/MethodLength
326
+
327
+ # Take n items and put them into a sorted array. If n is positive the array
328
+ # will include this item, as well the n - 1 items following it in the chain.
329
+ # If n is negative the items will be taken from before this item instead.
330
+ #
331
+ # If there are less than n - 1 items before/after this the resulting array
332
+ # will contain less than n items.
333
+ #
334
+ # @raise [ArgumentError] if `n` is not an integer.
335
+ #
336
+ # @param n [Integer] the number of items to take.
337
+ # @return [Array<Listable>] an array containing the taken items.
338
+ def take(n)
339
+ raise ArgumentError, 'n must be an integer' unless n.is_a? Integer
340
+
341
+ # Optimize for the most simple cases
342
+ return [self] if n == 1 || n == -1
343
+ return [] if n.zero?
344
+
345
+ n_abs = n.abs
346
+
347
+ res = Array.new n_abs
348
+
349
+ if n.positive?
350
+ res[0] = self
351
+ enum = after
352
+ iter = 1.upto(n_abs - 1)
353
+ else
354
+ res[n_abs - 1] = self
355
+ enum = before
356
+ iter = (n_abs - 2).downto 0
357
+ end
358
+
359
+ iter.each { |i| res[i] = enum.next }
360
+ res
361
+ rescue StopIteration
362
+ res.compact!
363
+ res
364
+ end
365
+
366
+ # rubocop:enable Metrics/MethodLength
367
+
368
+ # Due to the nature of listable objects the default #inspect method is
369
+ # problematic. This basic replacement includes only the class name and the
370
+ # object id.
371
+ #
372
+ # @return [String] a string representation of the object.
373
+ def inspect
374
+ block_given? ? yield(self) : object_identifier
375
+ end
376
+
377
+ protected
378
+
296
379
  # Protected factory method for creating items compatible with this listable
297
380
  # item. This method is called whenever an arbitrary object is appended or
298
381
  # prepended onto this item and need to be wraped/converted.
@@ -301,141 +384,221 @@ module Linked
301
384
  #
302
385
  # args - any arguments will be passed on to .new.
303
386
  #
304
- # Returns a new Listable object.
305
-
306
- protected def create_item(*args)
387
+ # Must return a new Listable object.
388
+ def create_item(*args)
307
389
  self.class.new(*args)
308
390
  end
309
391
 
310
- # Protected unsafe accessor of the next item in the list. It is preferable
392
+ # Protected unsafe accessor of the next item in the chain. It is preferable
311
393
  # to use #next, possibly in conjunction with #last?.
312
394
  #
313
395
  # Returns the item that come after this, or nil if this is the last one.
314
-
315
- protected def next!
396
+ def next!
316
397
  @_next
317
398
  end
318
399
 
319
- # Calling #next= directly is not recommended since it may corrupt the chain.
320
-
321
- protected def next=(other)
400
+ # Never call this method directly since it may corrupt the chain.
401
+ #
402
+ # Sets the value of the `next` field.
403
+ def next=(other)
322
404
  @_next = other
323
405
  end
324
406
 
325
- # Protected, unsafe accessor of the previous item in the list. It is
407
+ # Protected, unsafe accessor of the previous item in the chain. It is
326
408
  # preferable to use #prev, possibly in conjunction with #first?.
327
409
  #
328
- # Returns the item that come before this, or nil if this is the first one.
329
-
330
- protected def prev!
410
+ # Returns the item that come before this, or the last item in the chain if
411
+ # this is the first one.
412
+ def prev!
331
413
  @_prev
332
414
  end
333
415
 
334
- # Calling #prev= directly is not recommended since it may corrupt the chain.
335
-
336
- protected def prev=(other)
416
+ # Never call this method directly since it may corrupt the chain.
417
+ #
418
+ # Sets the value of the `prev` field.
419
+ def prev=(other)
337
420
  @_prev = other
338
421
  end
339
422
 
340
- # Calling #list= directly is not recommended since it may corrupt the list.
423
+ # Protected, unsafe accessor of the first item in the chain. It is
424
+ # preferable to use #first.
425
+ #
426
+ # Returns the first item in the chain, or the chain item count if this is
427
+ # the first one.
428
+ def chain_head!
429
+ @_chain_head
430
+ end
431
+
432
+ # Never call this method directly since it may corrupt the chain.
433
+ #
434
+ # Sets the value of the `first` field.
341
435
 
342
- protected def list=(list)
343
- @_list = list
436
+ def chain_head=(other)
437
+ @_chain_head = other
344
438
  end
345
439
 
346
- # PRIVATE DANGEROUS METHOD. This method should never be called directly
347
- # since it may leave the extracted item chain in an invalid state.
440
+ # Never call this method directly since it may corrupt the chain. Grow the
441
+ # chain with n items.
348
442
  #
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.
443
+ # n - the number of items to increase the chain count with.
352
444
  #
353
- # Given the two lists
354
- # [ A <> B <> C ] [ D ]
355
- # (I) (II)
445
+ # Returns the updated chain count.
446
+
447
+ def grow(n = 1)
448
+ head = chain_head
449
+ head.chain_head = head.chain_head! + n
450
+ end
451
+
452
+ # Never call this method directly since it may corrupt the chain. Shrink the
453
+ # chain with n items.
356
454
  #
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.
455
+ # n - the number of items to decrease the chain count with.
362
456
  #
363
- # Returns the last item of the chain.
457
+ # Returns the updated chain count.
458
+
459
+ def shrink(n = 1)
460
+ head = chain_head
461
+ head.chain_head = head.chain_head! - n
462
+ end
364
463
 
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
464
+ # Split the chain on this item and insert the latter part into the chain
465
+ # with head as its first item.
466
+ #
467
+ # Calling C.split_before_and_insert(.) yields the two chains (ii) and (iii)
468
+ #
469
+ # A <> B <> C <> D A <> B C <> D
470
+ # (i) (ii) (iii)
471
+ #
472
+ # Chain (ii) is guaranteed to be complete. Chain (iii) will however be left
473
+ # in an inclomplete state unless head_b == self (default). The first item in
474
+ # (iii) must then be connected to the one preceeding it.
475
+ #
476
+ # head_b - the head of a new chain that (iii) will be added to.
477
+ #
478
+ # Returns the last element of (iii).
479
+
480
+ def split_before_and_insert(head_b = self)
481
+ # Get the current chain head. It will remain the head
482
+ # of sub chain a (ii). If this item is the first then
483
+ # chain a will be empty.
484
+ chain_a_head = chain_head? ? nil : chain_head
485
+
486
+ # The head of sub chain b (iii) is self.
487
+ chain_b_head = self
488
+
489
+ # Find the tail of sub chain b by iterating over each
490
+ # item, starting with this one. Set the the new head
491
+ # of these while counting them.
492
+ chain_b_tail = self
493
+ chain_b_length = 1
494
+
495
+ loop do
496
+ chain_b_tail.chain_head = head_b
497
+ chain_b_tail = chain_b_tail.next
498
+ chain_b_length += 1
372
499
  end
373
500
 
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?
501
+ # If sub chain a is not empty it needs to be updated.
502
+ # Shrink its count by the number of items in sub
503
+ # chain b and complete it by connecting the head to
504
+ # the tail.
505
+ if chain_a_head
506
+ chain_a_head.shrink chain_b_length
507
+
508
+ chain_a_tail = chain_b_head.prev
509
+ chain_a_head.prev = chain_a_tail
510
+ chain_a_tail.next = nil
387
511
  end
388
512
 
389
- # Disconnect the chain from the list
390
- @_prev = last_item.next = nil
513
+ # Tell the new chain to grow. If sub chain b is to be
514
+ # the new head we can insert the count directly. We
515
+ # also complete the chain by connecting the head to
516
+ # the tail. The next field of the tail should already
517
+ # be nil.
518
+ if chain_b_head.equal? head_b
519
+ chain_b_head.chain_head = chain_b_length
520
+ chain_b_head.prev = chain_b_tail
521
+ else
522
+ head_b.grow chain_b_length
523
+ end
391
524
 
392
- # Preemptivly tell the new list to grow
393
- new_list.send :grow, count if new_list
525
+ # Chain a is now either empty (nil) or completed.
526
+ # Chain b however is only complete if the given head
527
+ # is equal to self (default). If it is not chain b
528
+ # will need a) the next field of the tail set to the
529
+ # item after, unless nil, and b) the prev field of
530
+ # head set to the item before.
394
531
 
395
- last_item
532
+ chain_b_tail
396
533
  end
397
534
 
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.
535
+ # TODO
407
536
 
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
537
+ def split_after_and_insert(head_a = chain_head)
538
+ # If this is not the last item in the chain, sub chain
539
+ # b will contain items. Use #split_before_and_insert
540
+ # to cut the chain after this one. This will complete
541
+ # chain b and update the item count of chain a.
542
+ next!.split_before_and_insert unless chain_tail?
416
543
 
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?
544
+ chain_a_head = chain_head
545
+
546
+ # If the head of sub chain a is same as the target
547
+ # chain head
548
+ return chain_a_head if chain_a_head.equal? head_a
549
+
550
+ chain_a_length = chain_length
551
+
552
+ # Set the head field of all items, starting with the
553
+ # tail (self), moving backwards.
554
+ item = self
555
+
556
+ # Loop until we hit the first item.
557
+ loop do
558
+ item.chain_head = head_a
559
+ item = item.prev
430
560
  end
431
561
 
432
- # Disconnect the chain from the list
433
- first_item.prev = @_next = nil
562
+ # Tell the target chain to grow with the number of
563
+ # items in sub chain a.
564
+ head_a.grow chain_a_length
565
+
566
+ # Sub chain b is now either empty or complete. Sub
567
+ # chain a however is only complete if the target
568
+ # head is the same as the head of chain a. Otherwise
569
+ # the prev field of head and the next field of tail
570
+ # both need to be set correctly.
571
+ chain_a_head
572
+ end
573
+
574
+ private
434
575
 
435
- # Preemptivly tell the new list to grow
436
- new_list.send :grow, count if new_list
576
+ # Convert the given object to a listable item. If the object responds to
577
+ # #item the result of that call is returned. Otherwise a new item is created
578
+ # using #create_item.
579
+ #
580
+ # @param other [Object] the object to coerce.
581
+ # @return [Listable] a listable item.
582
+ def coerce(other)
583
+ if other.respond_to? :item
584
+ other.item
585
+ else
586
+ create_item other
587
+ end
588
+ end
437
589
 
438
- first_item
590
+ # Reset the fields of the item to their initial state. This leaves the item
591
+ # in a consistent state as a single item chain.
592
+ #
593
+ # Only call this method on items that are disconnected from their siblings.
594
+ # Otherwise the original chain (if any) will be left in an inconsistent
595
+ # state.
596
+ #
597
+ # @return [self]
598
+ def reset_item
599
+ @_chain_head = 1
600
+ @_next = nil
601
+ @_prev = self
439
602
  end
440
603
  end
441
604
  end