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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 05e94a861ba2e494ea20dc84572c5dac578421eb
4
- data.tar.gz: 849a8b134e95bcd6d444782c167f83ff6f982be4
2
+ SHA256:
3
+ metadata.gz: 44eaaab09ac6111dd607d3cd52e35d2d072315bbc4614b7805254c8a7bab311a
4
+ data.tar.gz: 4672f6d56f0dde64a6fe12ecaefdf82d39795a957d97ccb55eb0558ffe23285d
5
5
  SHA512:
6
- metadata.gz: 255a18d48ba2d395e12394aa8a6a72880100a1c2677ad3f5e3005c7412ea74011c4b902947b390b85077675c6a0447c112b6bed36ba4da8bf03dcd5769bf7f73
7
- data.tar.gz: e2e9b8f9a72c06074c1aa3af9cbecff9c3fbb8adc2bc6674f50f86d3a089abec934ca5ee91815e9156f4647c374ec3bea87fec583e80236a5b010c9939c0cbf8
6
+ metadata.gz: c94e42bee0a6717053ff3c33de9f36c03ab4ba24d35ddb3b387ed78a3e4a54898269eb62237117a05db4cf4c96b5555c376cc507cb2d8cc02aed1a80059f69a3
7
+ data.tar.gz: 479b9a24c6dc2a785993072e620695bebe35e9599cfb9f8842fd1a767d6783c5b0838f6ccd0abce247a155e416930fa76cc6c164cb6ae85e2e9aac23c7948c9f
data/.rubocop.yml ADDED
@@ -0,0 +1,71 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'vendor/**/*'
4
+ - 'tmp/**/*'
5
+ TargetRubyVersion: 2.3
6
+
7
+ Layout/EndOfLine:
8
+ EnforcedStyle: lf
9
+
10
+ Layout/ClassStructure:
11
+ Enabled: true
12
+ Categories:
13
+ module_inclusion:
14
+ - include
15
+ - prepend
16
+ - extend
17
+ ExpectedOrder:
18
+ - module_inclusion
19
+ - constants
20
+ - public_class_methods
21
+ - initializer
22
+ - instance_methods
23
+ - protected_methods
24
+ - private_methods
25
+
26
+ Layout/IndentHeredoc:
27
+ EnforcedStyle: squiggly
28
+
29
+ Lint/AmbiguousBlockAssociation:
30
+ Exclude:
31
+ - 'test/**/*.rb'
32
+
33
+ Lint/InterpolationCheck:
34
+ Exclude:
35
+ - 'test/**/*.rb'
36
+
37
+ Metrics/BlockLength:
38
+ Exclude:
39
+ - 'Rakefile'
40
+ - '**/*.rake'
41
+ - 'test/**/*.rb'
42
+
43
+ Metrics/ModuleLength:
44
+ Exclude:
45
+ - 'test/**/*.rb'
46
+
47
+ Metrics/ParameterLists:
48
+ CountKeywordArgs: false
49
+
50
+ Naming/UncommunicativeMethodParamName:
51
+ AllowedNames:
52
+ - "_"
53
+ - x
54
+ - y
55
+ - i
56
+ - p
57
+ - n
58
+ - t
59
+ - r
60
+ - g
61
+ - b
62
+ - to
63
+
64
+ Style/FrozenStringLiteralComment:
65
+ EnforcedStyle: always
66
+
67
+ Style/FormatStringToken:
68
+ Enabled: false
69
+
70
+ Style/MultipleComparison:
71
+ Enabled: false
data/.travis.yml CHANGED
@@ -3,3 +3,4 @@ language: ruby
3
3
  rvm:
4
4
  - 2.3.0
5
5
  before_install: gem install bundler -v 1.12.5
6
+ script: bundle exec rake test
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in linked.gemspec
data/Rakefile CHANGED
@@ -1,10 +1,23 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+ require 'yard'
3
7
 
4
8
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
9
+ t.libs << 'test'
10
+ t.libs << 'lib'
7
11
  t.test_files = FileList['test/**/*_test.rb']
8
12
  end
9
13
 
10
- task :default => :test
14
+ RuboCop::RakeTask.new(:rubocop)
15
+
16
+ YARD::Rake::YardocTask.new(:yard) do |t|
17
+ t.stats_options = %w[--list-undoc]
18
+ end
19
+
20
+ desc 'Generate Ruby documentation'
21
+ task doc: %w[yard]
22
+
23
+ task default: %w[test rubocop:auto_correct]
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "linked"
4
+ require 'bundler/setup'
5
+ require 'linked'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "linked"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
data/lib/linked.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'linked/version'
4
+ require 'linked/util'
4
5
  require 'linked/listable'
5
6
  require 'linked/item'
6
- require 'linked/list/eol'
7
+ require 'linked/list_enumerable'
7
8
  require 'linked/list'
8
9
 
10
+ # Linked List implementation.
9
11
  module Linked
10
-
12
+ private_constant :Util, :ListEnumerable
11
13
  end
data/lib/linked/item.rb CHANGED
@@ -1,37 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linked
4
- # Item
4
+ # This is the default implementation of a listable object
5
5
  #
6
6
  # This class implements a listable value object that wraps an arbitrary value
7
- # an can be stored in a list.
8
-
7
+ # an can be with other listable items.
9
8
  class Item
10
9
  include Listable
11
10
 
12
11
  # The Item can hold an arbitrary object as its value and it will stay with
13
12
  # the item.
14
-
13
+ # @return [Object] any object that is stored in the item.
15
14
  attr_accessor :value
16
15
 
17
16
  # Creates a new item. If a list is given the item will be considered a part
18
17
  # of that list and appended to the end of it.
19
18
  #
20
- # value - an arbitrary object to store with the item.
21
- # list - an object responding to #head and #tail.
22
- #
23
- # Returns a new Item.
24
-
19
+ # @param value [Object] an arbitrary object to store with the item.
25
20
  def initialize(value = nil)
26
21
  @value = value
27
22
  super()
28
23
  end
29
24
 
30
25
  # Calling #dup on an item returns a copy that is no longer connected to the
31
- # original item chain, or the list. The value will also be copied.
26
+ # original item chain. The value will also be copied.
32
27
  #
33
- # Returns a new Item.
34
-
28
+ # @param source [Item] the item to copy.
29
+ # @return [item] a new Item.
35
30
  def initialize_dup(source)
36
31
  @value = begin
37
32
  source.value.dup
@@ -45,10 +40,9 @@ module Linked
45
40
  # responds to #value, and its value is equal (#==) to this value, the
46
41
  # objects are considered equal.
47
42
  #
48
- # other - any object.
49
- #
50
- # Returns true if the objects are considered equal.
51
-
43
+ # @param other [#value, Object] any object.
44
+ # @return [true, false] true if the value of the given object is equal to
45
+ # the item value.
52
46
  def ==(other)
53
47
  return false unless other.respond_to? :value
54
48
  value == other.value
@@ -58,28 +52,27 @@ module Linked
58
52
 
59
53
  # Uses the hash value of the item value.
60
54
  #
61
- # Returns a fixnum that can be used by Hash to identify the item.
62
-
55
+ # @return [Integer] a fixnum that can be used by Hash to identify the item.
63
56
  def hash
64
57
  value.hash
65
58
  end
66
59
 
67
- # Freezes the value, as well as making the list item itself immutable.
68
-
60
+ # Freezes the value, as well as making the item itself immutable.
61
+ #
62
+ # @return [self]
69
63
  def freeze
70
64
  value.freeze
71
65
  super
72
66
  end
73
67
 
74
68
  # The default #inspect method becomes very cluttered the moment you start
75
- # liking objects together. This implementation fixes that and only shows the
76
- # class name, object id and value (if set).
77
-
69
+ # linking objects together. This implementation fixes that and only shows
70
+ # the class name, object id and value (if set).
71
+ #
72
+ # @return [String] a string representation of the list item.
78
73
  def inspect
79
- return yield self if block_given?
80
-
81
- output = format '%s:0x%0x', self.class.name, object_id
82
- value ? output + " value=#{value.inspect}" : output
74
+ return yield(self).to_s if block_given?
75
+ value ? object_identifier + " value=#{value.inspect}" : object_identifier
83
76
  end
84
77
  end
85
78
  end
data/lib/linked/list.rb CHANGED
@@ -1,70 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linked
4
- # List
4
+ # This class provides a way extend the regular chain of listable items with
5
+ # the concept of an empty chain.
5
6
  #
6
- # This class implements a linked list. Most importantly, the methods #head,
7
- # #tail, #grow, #shrink and #create_item are implemented to comply with the
8
- # requirements defined by Listable.
9
-
7
+ # Lists are ment to behave more like arrays, and respond to many of the same
8
+ # methods.
10
9
  class List
11
- include Enumerable
12
-
13
- # Initializes the list. It is important that this method be called during
14
- # the initialization of the including class, and that the instance variables
15
- # @_item_count and @_eol never be accessed directly.
10
+ include ListEnumerable
11
+ include Util
16
12
 
13
+ # Initializes the list.
17
14
  def initialize
18
- @_eol = EOL.new self
19
- @_item_count = 0
20
-
15
+ reset_list
21
16
  super
22
17
  end
23
18
 
24
19
  # When copying a list its entire item chain needs to be copied as well.
25
20
  # Therefore #dup will be called on each of the original lists items, making
26
21
  # this operation quite expensive.
27
-
22
+ #
23
+ # @param source [List] the list to copy.
28
24
  def initialize_dup(source)
29
- @_eol = EOL.new self
30
- @_item_count = 0
31
-
32
- source.each_item { |item| push item.dup }
25
+ reset_list
26
+ source.each_item { |item| push item.dup }
33
27
 
34
28
  super
35
29
  end
36
30
 
37
- # Identity method that simply return the list. This method mirrors Item#list
38
- # and allows other methods that work on List objects to easily and
39
- # interchangebly accept both lists and items as arguments.
40
- #
41
- # Returns the list itself.
42
-
43
- def list
44
- self
45
- end
46
-
47
31
  # Access the first item in the list. If the list is empty a NoMethodError
48
32
  # will be raised. This mirrors the behaviour of Item#item and allows other
49
33
  # methods that work on List objects to easily and interchangeably accept
50
34
  # both lists and items as arguments.
51
35
  #
52
- # Returns the first item in the list.
53
-
36
+ # @return [Listable] the first item in the list.
54
37
  def item
55
38
  raise NoMethodError if empty?
56
- head.next
39
+ @_chain
57
40
  end
58
41
 
59
42
  # Two lists are considered equal if the n:th item from each list are equal.
60
43
  #
61
- # other - any object.
62
- #
63
- # Returns true if the given object is a list and the items are equal.
64
-
44
+ # @param other [Object] the object to compare with.
45
+ # @return [true] if the given object is a list and the items are equal.
46
+ # @return [false] otherwise.
65
47
  def ==(other)
66
48
  return false unless other.is_a? self.class
67
- return false unless other.count == @_item_count
49
+ return false unless other.count == count
68
50
 
69
51
  other_items = other.each_item
70
52
  each_item.all? { |item| item == other_items.next }
@@ -72,86 +54,10 @@ module Linked
72
54
 
73
55
  alias eql? ==
74
56
 
75
- # Access the first n item(s) in the list. If a block is given each item will
76
- # be yielded to it. The first item, starting from the first in the list, for
77
- # which the block returns true and the n - 1 items directly following it
78
- # will be returned.
79
- #
80
- # n - the number of items to return.
81
- #
82
- # Returns, for different values of n:
83
- # n == 0) nil
84
- # n == 1) an item if the list contains one, or nil
85
- # n > 1) an array of between 0 and n items, depending on how many are in
86
- # the list
87
-
88
- def first(n = 1)
89
- raise ArgumentError, 'n cannot be negative' if n < 0
90
-
91
- return first_item_after head, n, count unless block_given?
92
-
93
- item = head
94
- items_left = count
95
-
96
- items_left.times do
97
- break if yield next_item = item.next
98
- item = next_item
99
- items_left -= 1
100
- end
101
-
102
- first_item_after item, n, items_left
103
- end
104
-
105
- # Access the last n item(s) in the list. The items will retain thier order.
106
- # If a block is given each item, starting with the last in the list, will be
107
- # yielded to it. The first item for which the block returns true and the
108
- # n - 1 items directly preceding it will be returned.
109
- #
110
- # n - the number of items to return.
111
- #
112
- # Returns, for different values of n:
113
- # n == 0) nil
114
- # n == 1) an item if the list contains one, or nil
115
- # n > 1) an array of between 0 and n items, depending on how many are in
116
- # the list
117
-
118
- def last(n = 1)
119
- raise ArgumentError, 'n cannot be negative' if n < 0
120
-
121
- return last_item_before tail, n, count unless block_given?
122
-
123
- item = tail
124
- items_left = count
125
-
126
- items_left.times do
127
- break if yield prev_item = item.prev
128
- item = prev_item
129
- items_left -= 1
130
- end
131
-
132
- last_item_before item, n, items_left
133
- end
134
-
135
- # Overrides the Enumerable#count method when given no argument to provide a
136
- # fast item count. Instead of iterating over each item, the internal item
137
- # count is returned.
138
- #
139
- # args - see Enumerable#count
140
- #
141
- # Returns the number of items counted.
142
-
143
- def count(*args)
144
- if args.empty? && !block_given?
145
- @_item_count
146
- else
147
- super
148
- end
149
- end
150
-
151
- # Returns true if the list does not contain any items.
152
-
57
+ # @return [true] if the list does not contain any items.
58
+ # @return [false] otherwise.
153
59
  def empty?
154
- @_item_count == 0
60
+ nil.eql? @_chain
155
61
  end
156
62
 
157
63
  # Insert an item at the end of the list. If the given object is not an
@@ -160,12 +66,17 @@ module Linked
160
66
  #
161
67
  # See Item#append for more details.
162
68
  #
163
- # object - the item to insert, or an arbitrary object.
164
- #
165
- # Returns self.
166
-
69
+ # @param object [#item, Object] the item to insert, or an arbitrary object.
70
+ # @return [self]
167
71
  def push(object)
168
- tail.append object
72
+ item = coerce_item object
73
+
74
+ if empty?
75
+ @_chain = item
76
+ else
77
+ list_tail.append item
78
+ end
79
+
169
80
  self
170
81
  end
171
82
 
@@ -173,11 +84,18 @@ module Linked
173
84
 
174
85
  # Pop the last item off the list.
175
86
  #
176
- # Returns the last item in the list, or nil if the list is empty.
177
-
87
+ # @return [Listable, nil] the last item in the list, or nil if the list is
88
+ # empty.
178
89
  def pop
179
90
  return nil if empty?
180
- last.delete
91
+
92
+ if list_tail.first?
93
+ item = last
94
+ @_chain = nil
95
+ item
96
+ else
97
+ list_tail.delete
98
+ end
181
99
  end
182
100
 
183
101
  # Insert an item at the beginning of the list. If the given object is not an
@@ -186,67 +104,49 @@ module Linked
186
104
  #
187
105
  # See Item#prepend for more details.
188
106
  #
189
- # object - the item to insert, or an arbitrary object.
190
- #
191
- # Returns self.
192
-
107
+ # @param object [#item, Object] the item to insert, or an arbitrary object.
108
+ # @return [self]
193
109
  def unshift(object)
194
- head.prepend object
110
+ item = coerce_item object
111
+ @_chain = empty? ? item.chain : @_chain.prepend(item)
112
+
195
113
  self
196
114
  end
197
115
 
198
116
  # Shift the first item off the list.
199
117
  #
200
- # Returns the first item in the list, or nil if the list is empty.
201
-
118
+ # @return [Listable, nil] the first item in the list, or nil if the list is
119
+ # empty.
202
120
  def shift
203
121
  return nil if empty?
204
- first.delete
122
+
123
+ if list_head.last?
124
+ item = @_chain
125
+ @_chain = nil
126
+ item
127
+ else
128
+ old_head = list_head
129
+ @_chain = list_head.next
130
+ old_head.delete
131
+ end
205
132
  end
206
133
 
207
134
  # Check if an item is in the list.
208
135
  #
209
- # item - Item, or any object that may be in the list.
210
- #
211
- # Returns true if the given item is in the list, otherwise false.
212
-
136
+ # @param item [Object] any object that may be in the list.
137
+ # @return [true] if the given item is in the list.
138
+ # @return [false] otherwise.
213
139
  def include?(item)
214
- item.in? self
215
- rescue NoMethodError
216
- false
217
- end
218
-
219
- # Iterates over each item in the list If a block is not given an enumerator
220
- # is returned.
221
-
222
- def each_item
223
- return to_enum(__method__) { count } unless block_given?
224
- return if empty?
225
-
226
- item = head
227
- loop { yield item = item.next }
228
- end
229
-
230
- alias each each_item
231
-
232
- # Iterates over each item in the list in reverse order. If a block is not
233
- # given an enumerator is returned.
234
-
235
- def reverse_each_item
236
- return to_enum(__method__) { count } unless block_given?
237
- return if empty?
238
-
239
- item = tail
240
- loop { yield item = item.prev }
140
+ return false if empty?
141
+ # TODO: This works fine, but looks wrong.
142
+ @_chain.in_chain? item
241
143
  end
242
144
 
243
- alias reverse_each reverse_each_item
244
-
245
145
  # Calls #freeze on all items in the list, as well as the head and the tail
246
146
  # (eol).
247
-
147
+ #
148
+ # @return [self]
248
149
  def freeze
249
- @_eol.freeze
250
150
  each_item(&:freeze)
251
151
  super
252
152
  end
@@ -257,14 +157,13 @@ module Linked
257
157
  # Importantly this implementation supports nested lists and will return a
258
158
  # tree like structure.
259
159
 
260
- def inspect(&block)
261
- # Get the parents inspect output
262
- res = [super]
160
+ def inspect_list(&block)
161
+ res = [block_given? ? yield(self) : object_identifier]
263
162
 
264
163
  each_item do |item|
265
164
  lines = item.inspect(&block).split "\n"
266
165
 
267
- res.push (item.last? ? '└─╴' : '├─╴') + lines.shift
166
+ res.push((item.last? ? '└─╴' : '├─╴') + lines.shift)
268
167
  padding = item.last? ? '   ' : '│  '
269
168
  lines.each { |line| res.push padding + line }
270
169
  end
@@ -272,125 +171,49 @@ module Linked
272
171
  res.join("\n")
273
172
  end
274
173
 
174
+ alias inspect inspect_list
175
+
275
176
  # Protected factory method for creating items compatible with the list. This
276
177
  # method is called whenever an arbitrary object is pushed or unshifted onto
277
178
  # the list and need to be wraped inside an Item.
278
179
  #
279
180
  # This method can be overridden to support different Item types.
280
181
  #
281
- # args - any arguments will be passed on to Item.new.
282
- #
283
- # Returns a new Item.
284
-
182
+ # @param args [Array<Object>] the arguments that are to be passed on to
183
+ # `Item.new`.
184
+ # @return [Item] a new `Listable` item.
285
185
  protected def create_item(*args)
286
186
  Item.new(*args)
287
187
  end
288
188
 
289
- # Returns an object that responds to #next= and #prepend.
290
-
291
- protected def head
292
- @_eol
293
- end
294
-
295
- # Returns an object that responds to #prev= and #append.
296
-
297
- alias tail head
298
-
299
- # Internal method to grow the list with n elements. Never call this method
300
- # without also inserting the n elements.
189
+ # Takes an arbitrary object and coerces it into an item compliant with the
190
+ # list. If the object is already an item it will be used as is. Otherwise
191
+ # #create_item will be called with the object as an argument.
301
192
  #
302
- # n - the number of items that has been/will be added to the list.
303
- #
304
- # Returns updated the item count.
305
-
306
- private def grow(n = 1)
307
- @_item_count += n
308
- end
309
-
310
- # Internal method to shrink the list with n elements. Never call this method
311
- # without also deleting the n elements.
312
- #
313
- # n - the number of items that has been/will be removed from the list.
314
- #
315
- # Returns updated the item count.
316
-
317
- private def shrink(n = 1)
318
- @_item_count -= n
193
+ # @param [#item, Object] the object to coerce.
194
+ # @return [Listable] see `#create_item`.
195
+ private def coerce_item(object)
196
+ if object.respond_to? :item
197
+ object.item
198
+ else
199
+ create_item object
200
+ end
319
201
  end
320
202
 
321
- # Private method to clear the list. Never call this method without also
322
- # modifying the items in the list, as this operation leavs them in an
323
- # inconsistant state. If the list items are kept, make sure to
324
- # a) clear the `prev` pointer of the first item and
325
- # b) clear the `next` pointer of the last item.
326
-
327
- private def clear
328
- @_eol.send :reset
329
- @_item_count = 0
203
+ # Private method for clearing the list and bringing it to a pristine
204
+ # state.
205
+ private def reset_list
206
+ @_chain = nil
330
207
  end
331
208
 
332
- # Private helper method that returns the first n items, starting just
333
- # after item, given that there are items_left items left. Knowing the exact
334
- # number of items left is not cruicial but does impact speed. The number
335
- # should not be lower than the actual ammount. The following must
336
- # hold for the output to be valid:
337
- # a) n > 0
338
- # b) there are at least items_left items left
339
- #
340
- # item - the Item just before the item to start from.
341
- # n - the number of items to return.
342
- # items_left - the number of items left.
343
- #
344
- # Returns, for different values of n:
345
- # n == 0) nil
346
- # n == 1) an item if items_left > 0 or nil
347
- # n > 1) an array of items if items_left > 0 or an empty array
348
-
349
- private def first_item_after(item, n, items_left = @_item_count)
350
- # Optimize for these cases
351
- return nil if n == 0
352
- return n > 1 ? [] : nil if item.last?
353
- return item.next if n == 1
354
-
355
- n = items_left if n > items_left
356
-
357
- arr = Array.new n
358
- n.times { |i| arr[i] = item = item.next }
359
- arr
360
- rescue StopIteration
361
- arr.compact! || arr
209
+ # Returns the first item item in the list, or nil if empty.
210
+ private def list_head
211
+ @_chain
362
212
  end
363
213
 
364
- # Private helper method that returns the last n items, ending just before
365
- # item, given that there are items_left items left. Knowing the exact
366
- # number of items left is not cruicial but does impact speed. The number
367
- # should not be lower than the actual ammount. The following must hold for
368
- # the output to be valid:
369
- # a) n > 0
370
- # b) there are at least items_left items left
371
- #
372
- # item - the Item just after the item to start from.
373
- # n - the number of items to return.
374
- # items_left - the number of items left.
375
- #
376
- # Returns, for different values of n:
377
- # n == 0) nil
378
- # n == 1) an item if items_left > 0 or nil
379
- # n > 1) an array of items if items_left > 0 or an empty array
380
-
381
- private def last_item_before(item, n, items_left = @_item_count)
382
- # Optimize for these cases
383
- return nil if n == 0
384
- return n > 1 ? [] : nil if item.first?
385
- return item.prev if n == 1
386
-
387
- n = items_left if n > items_left
388
-
389
- arr = Array.new n
390
- (n - 1).downto(0) { |i| arr[i] = item = item.prev }
391
- arr
392
- rescue StopIteration
393
- arr.compact! || arr
214
+ # Returns an the last item in the list, or nil if empty.
215
+ private def list_tail
216
+ @_chain.last_in_chain
394
217
  end
395
218
  end
396
219
  end