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.
- checksums.yaml +5 -5
- data/.rubocop.yml +71 -0
- data/.travis.yml +1 -0
- data/Gemfile +2 -0
- data/Rakefile +18 -5
- data/bin/console +4 -3
- data/lib/linked.rb +4 -2
- data/lib/linked/item.rb +20 -27
- data/lib/linked/list.rb +95 -272
- data/lib/linked/list_enumerable.rb +86 -0
- data/lib/linked/listable.rb +421 -258
- data/lib/linked/util.rb +10 -0
- data/lib/linked/version.rb +2 -1
- data/linked.gemspec +28 -16
- metadata +56 -11
- data/lib/linked/list/eol.rb +0 -84
@@ -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
|
data/lib/linked/listable.rb
CHANGED
@@ -1,47 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Linked
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
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
|
-
#
|
9
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
23
|
-
#
|
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).
|
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
|
-
|
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
|
-
|
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
|
-
#
|
42
|
-
|
52
|
+
# @return [Listable] a new Listable.
|
43
53
|
def initialize_copy(*)
|
44
|
-
|
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
|
-
#
|
53
|
-
|
62
|
+
# @return [Listable] the item itself.
|
54
63
|
def item
|
55
64
|
self
|
56
65
|
end
|
57
66
|
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
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
|
-
#
|
64
|
-
|
65
|
-
def
|
66
|
-
|
67
|
-
@
|
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
|
-
|
71
|
-
|
72
|
-
# Returns true if the item is in a list.
|
79
|
+
alias first? chain_head?
|
80
|
+
protected :chain_head?
|
73
81
|
|
74
|
-
|
75
|
-
|
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
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
# Returns true if no item come before this one.
|
92
|
+
alias last? chain_tail?
|
93
|
+
protected :chain_tail?
|
82
94
|
|
83
|
-
|
84
|
-
|
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
|
-
|
88
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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
|
-
#
|
122
|
+
# @return [Integer] the number of items in the chain.
|
123
|
+
def chain_length
|
124
|
+
chain_head.chain_head!
|
125
|
+
end
|
101
126
|
|
102
|
-
|
103
|
-
|
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
|
-
|
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
|
-
#
|
142
|
+
# === Usage
|
110
143
|
# loop do
|
111
144
|
# item = item.next
|
112
145
|
# end
|
113
146
|
#
|
114
|
-
#
|
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
|
151
|
+
raise StopIteration if chain_tail?
|
118
152
|
@_next
|
119
153
|
end
|
120
154
|
|
121
|
-
# Access the previous item in the
|
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
|
-
#
|
159
|
+
# === Usage
|
126
160
|
# loop do
|
127
161
|
# item = item.prev
|
128
162
|
# end
|
129
163
|
#
|
130
|
-
#
|
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
|
168
|
+
raise StopIteration if chain_head?
|
134
169
|
@_prev
|
135
170
|
end
|
136
171
|
|
137
172
|
alias previous prev
|
138
173
|
|
139
|
-
#
|
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
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
166
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
206
|
+
sub_chain_b_tail
|
179
207
|
end
|
180
208
|
|
181
|
-
#
|
182
|
-
|
183
|
-
#
|
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
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
233
|
+
sub_chain_a_head
|
221
234
|
end
|
222
235
|
|
223
|
-
#
|
224
|
-
|
225
|
-
#
|
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
|
-
#
|
243
|
+
# === Usage
|
244
|
+
# Example using the chain A <> B <> C
|
228
245
|
#
|
229
|
-
#
|
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
|
-
|
233
|
-
|
234
|
-
|
252
|
+
if chain_head?
|
253
|
+
split_after_and_insert
|
254
|
+
else
|
255
|
+
shrink
|
235
256
|
|
236
|
-
|
237
|
-
|
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
|
-
#
|
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
|
-
|
276
|
+
prev!.split_after_and_insert unless chain_head?
|
248
277
|
end
|
249
278
|
|
250
|
-
# Remove all items after this one in the chain.
|
251
|
-
# list they will be removed from it.
|
279
|
+
# Remove all items after this one in the chain.
|
252
280
|
#
|
253
|
-
#
|
254
|
-
#
|
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
|
-
|
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
|
298
|
+
return if chain_head?
|
269
299
|
|
270
|
-
item =
|
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
|
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
|
-
#
|
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
|
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
|
-
#
|
320
|
-
|
321
|
-
|
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
|
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
|
329
|
-
|
330
|
-
|
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
|
-
#
|
335
|
-
|
336
|
-
|
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
|
-
#
|
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
|
-
|
343
|
-
@
|
436
|
+
def chain_head=(other)
|
437
|
+
@_chain_head = other
|
344
438
|
end
|
345
439
|
|
346
|
-
#
|
347
|
-
#
|
440
|
+
# Never call this method directly since it may corrupt the chain. Grow the
|
441
|
+
# chain with n items.
|
348
442
|
#
|
349
|
-
#
|
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
|
-
#
|
354
|
-
|
355
|
-
|
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
|
-
#
|
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
|
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
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
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
|
-
#
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
-
#
|
390
|
-
|
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
|
-
#
|
393
|
-
|
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
|
-
|
532
|
+
chain_b_tail
|
396
533
|
end
|
397
534
|
|
398
|
-
#
|
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
|
-
|
409
|
-
|
410
|
-
#
|
411
|
-
|
412
|
-
|
413
|
-
|
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
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
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
|
-
#
|
433
|
-
|
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
|
-
|
436
|
-
|
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
|
-
|
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
|