linked 0.4.0 → 0.5.1

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.
@@ -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