Module: Hamster::List

Includes:
Enumerable
Included in:
Cons, LazyList, Realizable
Defined in:
lib/hamster/list.rb

Overview

A `List` can be constructed with List[], or Enumerable#to_list. It consists of a head (the first element) and a tail (which itself is also a `List`, containing all the remaining elements).

This is a singly linked list. Prepending to the list with #add runs in constant time. Traversing the list from front to back is efficient, however, indexed access runs in linear time because the list needs to be traversed to find the element.

Constant Summary collapse

CADR =
/^c([ad]+)r$/

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

#<=>, #==, #compact, #each_index, #grep, #grep_v, #join, #product, #reject, #sum, #to_set

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object, List (private)

Perform compositions of `car` and `cdr` operations (traditional shorthand for `head` and `tail` respectively). Their names consist of a `c`, followed by at least one `a` or `d`, and finally an `r`. The series of `a`s and `d`s in the method name identify the series of `car` and `cdr` operations performed, in inverse order.

Examples:

l = Hamster::List[nil, Hamster::List[1]]
l.car   # => nil
l.cdr   # => Hamster::List[Hamster::List[1]]
l.cadr  # => Hamster::List[1]
l.caadr # => 1

Returns:


1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
# File 'lib/hamster/list.rb', line 1249

def method_missing(name, *args, &block)
  if name.to_s.match(CADR)
    code = "def #{name}; self."
    code << Regexp.last_match[1].reverse.chars.map do |char|
      {'a' => 'head', 'd' => 'tail'}[char]
    end.join('.')
    code << '; end'
    List.class_eval(code)
    send(name, *args, &block)
  else
    super
  end
end

Class Method Details

.[](*items) ⇒ List

Create a new `List` populated with the given items.

Examples:

list = Hamster::List[:a, :b, :c]
# => Hamster::List[:a, :b, :c]

Returns:


132
133
134
# File 'lib/hamster/list.rb', line 132

def self.[](*items)
  from_enum(items)
end

.emptyList

Return an empty `List`.

Returns:


139
140
141
# File 'lib/hamster/list.rb', line 139

def self.empty
  EmptyList
end

.from_enum(items) ⇒ Object

This method exists distinct from `.[]` since it is ~30% faster than splatting the argument.

Marking as private only because it was introduced for an internal refactoring. It could potentially be made public with a good name.


150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/hamster/list.rb', line 150

def self.from_enum(items)
  # use destructive operations to build up a new list, like Common Lisp's NCONC
  # this is a very fast way to build up a linked list
  list = tail = Hamster::Cons.allocate
  items.each do |item|
    new_node = Hamster::Cons.allocate
    new_node.instance_variable_set(:@head, item)
    tail.instance_variable_set(:@tail, new_node)
    tail = new_node
  end
  tail.instance_variable_set(:@tail, Hamster::EmptyList)
  list.tail
end

Instance Method Details

#<<(item) ⇒ List

Create a new `List` with `item` added at the end. This is much less efficient than adding items at the front.

Examples:

Hamster::List[:a, :b] << :c
# => Hamster::List[:a, :b, :c]

Parameters:

  • item (Object)

    The item to add

Returns:


203
204
205
# File 'lib/hamster/list.rb', line 203

def <<(item)
  append(List[item])
end

#add(item) ⇒ List Also known as: cons

Create a new `List` with `item` added at the front. This is a constant time operation.

Examples:

Hamster::List[:b, :c].add(:a)
# => Hamster::List[:a, :b, :c]

Parameters:

  • item (Object)

    The item to add

Returns:


189
190
191
# File 'lib/hamster/list.rb', line 189

def add(item)
  Cons.new(item, self)
end

#append(other) ⇒ List Also known as: concat, +

Return a `List` with all items from this `List`, followed by all items from `other`.

Examples:

Hamster::List[1, 2, 3].append(Hamster::List[4, 5])
# => Hamster::List[1, 2, 3, 4, 5]

Parameters:

  • other (List)

    The list to add onto the end of this one

Returns:


377
378
379
380
381
382
# File 'lib/hamster/list.rb', line 377

def append(other)
  LazyList.new do
    next other if empty?
    Cons.new(head, tail.append(other))
  end
end

#at(index) ⇒ Object

Retrieve the item at `index`. Negative indices count back from the end of the list (-1 is the last item). If `index` is invalid (either too high or too low), return `nil`.

Parameters:

  • index (Integer)

    The index to retrieve

Returns:

  • (Object)

787
788
789
790
791
792
793
794
795
796
# File 'lib/hamster/list.rb', line 787

def at(index)
  index += size if index < 0
  return nil if index < 0
  node = self
  while index > 0
    node = node.tail
    index -= 1
  end
  node.head
end

#break {|item| ... } ⇒ Array

Return two `List`s, one up to (but not including) the first item for which the block returns true, and another of all the remaining items.

Examples:

Hamster::List[1, 3, 4, 2, 5].break { |x| x > 3 }
# => [Hamster::List[1, 3], Hamster::List[4, 2, 5]]

Yields:

  • (item)

Returns:

  • (Array)

525
526
527
528
# File 'lib/hamster/list.rb', line 525

def break(&block)
  return span unless block_given?
  span { |item| !yield(item) }
end

#cached_size?Integer

Return `true` if the size of this list can be obtained in constant time (without traversing the list).

Returns:

  • (Integer)

1230
1231
1232
# File 'lib/hamster/list.rb', line 1230

def cached_size?
  false
end

#chunk(number) ⇒ List

Split the items in this list in groups of `number`. Return a list of lists.

Examples:

("a".."o").to_list.chunk(5)
# => Hamster::List[
#      Hamster::List["a", "b", "c", "d", "e"],
#      Hamster::List["f", "g", "h", "i", "j"],
#      Hamster::List["k", "l", "m", "n", "o"]]

Returns:


726
727
728
729
730
731
732
# File 'lib/hamster/list.rb', line 726

def chunk(number)
  LazyList.new do
    next self if empty?
    first, remainder = split_at(number)
    Cons.new(first, remainder.chunk(number))
  end
end

#clearList

Return an empty `List`. If used on a subclass, returns an empty instance of that class.

Returns:


534
535
536
# File 'lib/hamster/list.rb', line 534

def clear
  EmptyList
end

#combination(n) ⇒ List

Return a `List` of all combinations of length `n` of items from this `List`.

Examples:

Hamster::List[1,2,3].combination(2)
# => Hamster::List[
#      Hamster::List[1, 2],
#      Hamster::List[1, 3],
#      Hamster::List[2, 3]]

Returns:


708
709
710
711
712
713
714
# File 'lib/hamster/list.rb', line 708

def combination(n)
  return Cons.new(EmptyList) if n == 0
  LazyList.new do
    next self if empty?
    tail.combination(n - 1).map { |list| list.cons(head) }.append(tail.combination(n))
  end
end

#cycleList

Concatenate an infinite series of copies of this `List` together into a new `List`. Or, if empty, just return an empty list.

Examples:

Hamster::List[1, 2, 3].cycle.take(10)
# => Hamster::List[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]

Returns:


458
459
460
461
462
463
# File 'lib/hamster/list.rb', line 458

def cycle
  LazyList.new do
    next self if empty?
    Cons.new(head, tail.append(cycle))
  end
end

#delete(obj) ⇒ List

Return a `List` with all elements equal to `obj` removed. `#==` is used for testing equality.

Examples:

Hamster::List[:a, :b, :a, :a, :c].delete(:a) # => Hamster::List[:b, :c]

Parameters:

  • obj (Object)

    The object to remove.

Returns:


994
995
996
997
998
999
# File 'lib/hamster/list.rb', line 994

def delete(obj)
  list = self
  list = list.tail while list.head == obj && !list.empty?
  return EmptyList if list.empty?
  LazyList.new { Cons.new(list.head, list.tail.delete(obj)) }
end

#delete_at(index) ⇒ List

Return a `List` containing the same items, minus the one at `index`. If `index` is negative, it counts back from the end of the list.

Examples:

Hamster::List[1, 2, 3].delete_at(1)  # => Hamster::List[1, 3]
Hamster::List[1, 2, 3].delete_at(-1) # => Hamster::List[1, 2]

Parameters:

  • index (Integer)

    The index of the item to remove

Returns:


1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
# File 'lib/hamster/list.rb', line 1010

def delete_at(index)
  if index == 0
    tail
  elsif index < 0
    index += size if index < 0
    return self if index < 0
    delete_at(index)
  else
    LazyList.new { Cons.new(head, tail.delete_at(index - 1)) }
  end
end

#drop(number) ⇒ List

Return a `List` containing all items after the first `number` items from this `List`.

Examples:

Hamster::List[1, 3, 5, 7, 6, 4, 2].drop(3)
# => Hamster::List[7, 6, 4, 2]

Parameters:

  • number (Integer)

    The number of items to skip over

Returns:


357
358
359
360
361
362
363
364
365
366
# File 'lib/hamster/list.rb', line 357

def drop(number)
  LazyList.new do
    list = self
    while !list.empty? && number > 0
      number -= 1
      list = list.tail
    end
    list
  end
end

#drop_while {|item| ... } ⇒ List, Enumerator

Return a `List` which contains all elements starting from the first element for which the block returns `nil` or `false`.

Examples:

Hamster::List[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 }
# => Hamster::List[5, 7, 6, 4, 2]

Yields:

  • (item)

Returns:

  • (List, Enumerator)

308
309
310
311
312
313
314
315
# File 'lib/hamster/list.rb', line 308

def drop_while(&block)
  return enum_for(:drop_while) unless block_given?
  LazyList.new do
    list = self
    list = list.tail while !list.empty? && yield(list.head)
    list
  end
end

#dupList Also known as: clone

Return `self`. Since this is an immutable object duplicates are equivalent.

Returns:


1187
1188
1189
# File 'lib/hamster/list.rb', line 1187

def dup
  self
end

#each {|item| ... } ⇒ self

Call the given block once for each item in the list, passing each item from first to last successively to the block. If no block is given, returns an `Enumerator`.

Yields:

  • (item)

Returns:

  • (self)

213
214
215
216
217
218
219
220
# File 'lib/hamster/list.rb', line 213

def each
  return to_enum unless block_given?
  list = self
  until list.empty?
    yield(list.head)
    list = list.tail
  end
end

#each_chunk(number) {|list| ... } ⇒ self, Enumerator Also known as: each_slice

Split the items in this list in groups of `number`, and yield each group to the block (as a `List`). If no block is given, returns an `Enumerator`.

Yields:

  • (list)

    Once for each chunk.

Returns:

  • (self, Enumerator)

740
741
742
743
744
# File 'lib/hamster/list.rb', line 740

def each_chunk(number, &block)
  return enum_for(:each_chunk, number) unless block_given?
  chunk(number).each(&block)
  self
end

#eql?(other) ⇒ Boolean

Return true if `other` has the same type and contents as this `Hash`.

Parameters:

  • other (Object)

    The collection to compare with

Returns:

  • (Boolean)

1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
# File 'lib/hamster/list.rb', line 1165

def eql?(other)
  list = self
  loop do
    return true if other.equal?(list)
    return false unless other.is_a?(List)
    return other.empty? if list.empty?
    return false if other.empty?
    return false unless other.head.eql?(list.head)
    list = list.tail
    other = other.tail
  end
end

#fill(object) ⇒ List #fill(object, index) ⇒ List #fill(object, index, length) ⇒ List

Replace a range of indexes with the given object.

Overloads:

  • #fill(object) ⇒ List

    Return a new `List` of the same size, with every index set to `object`.

    Examples:

    Hamster::List["A", "B", "C", "D", "E", "F"].fill("Z")
    # => Hamster::List["Z", "Z", "Z", "Z", "Z", "Z"]

    Parameters:

    • object (Object)

      Fill value.

  • #fill(object, index) ⇒ List

    Return a new `List` with all indexes from `index` to the end of the vector set to `obj`.

    Examples:

    Hamster::List["A", "B", "C", "D", "E", "F"].fill("Z", 3)
    # => Hamster::List["A", "B", "C", "Z", "Z", "Z"]

    Parameters:

    • object (Object)

      Fill value.

    • index (Integer)

      Starting index. May be negative.

  • #fill(object, index, length) ⇒ List

    Return a new `List` with `length` indexes, beginning from `index`, set to `obj`. Expands the `List` if `length` would extend beyond the current length.

    Examples:

    Hamster::List["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2)
    # => Hamster::List["A", "B", "C", "Z", "Z", "F"]
    Hamster::List["A", "B"].fill("Z", 1, 5)
    # => Hamster::List["A", "Z", "Z", "Z", "Z", "Z"]

    Parameters:

    • object (Object)

      Fill value.

    • index (Integer)

      Starting index. May be negative.

    • length (Integer)

Returns:

Raises:

  • (IndexError)

    if index is out of negative range.


1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
# File 'lib/hamster/list.rb', line 1058

def fill(obj, index = 0, length = nil)
  if index == 0
    length ||= size
    if length > 0
      LazyList.new do
        Cons.new(obj, tail.fill(obj, 0, length-1))
      end
    else
      self
    end
  elsif index > 0
    LazyList.new do
      Cons.new(head, tail.fill(obj, index-1, length))
    end
  else
    raise IndexError if index < -size
    fill(obj, index + size, length)
  end
end

#flat_map(&block) ⇒ List

Return a `List` which is realized by transforming each item into a `List`, and flattening the resulting lists.

Examples:

Hamster::List[1, 2, 3].flat_map { |x| Hamster::List[x, 100] }
# => Hamster::List[1, 100, 2, 100, 3, 100]

Returns:


248
249
250
251
252
253
254
255
256
# File 'lib/hamster/list.rb', line 248

def flat_map(&block)
  return enum_for(:flat_map) unless block_given?
  LazyList.new do
    next self if empty?
    head_list = List.from_enum(yield(head))
    next tail.flat_map(&block) if head_list.empty?
    Cons.new(head_list.first, head_list.drop(1).append(tail.flat_map(&block)))
  end
end

#flattenList

Return a new `List` with all nested lists recursively “flattened out”, that is, their elements inserted into the new `List` in the place where the nested list originally was.

Examples:

Hamster::List[Hamster::List[1, 2], Hamster::List[3, 4]].flatten
# => Hamster::List[1, 2, 3, 4]

Returns:


756
757
758
759
760
761
762
# File 'lib/hamster/list.rb', line 756

def flatten
  LazyList.new do
    next self if empty?
    next head.append(tail.flatten) if head.is_a?(List)
    Cons.new(head, tail.flatten)
  end
end

#group_by {|item| ... } ⇒ Hash Also known as: group

Passes each item to the block, and gathers them into a Hash where the keys are return values from the block, and the values are `List`s of items for which the block returned that value.

Examples:

Hamster::List["a", "b", "ab"].group_by { |e| e.size }
# Hamster::Hash[
#   1 => Hamster::List["b", "a"],
#   2 => Hamster::List["ab"]
# ]

Yields:

  • (item)

Returns:


776
777
778
# File 'lib/hamster/list.rb', line 776

def group_by(&block)
  group_by_with(EmptyList, &block)
end

#hashInteger

See `Object#hash`

Returns:

  • (Integer)

1180
1181
1182
# File 'lib/hamster/list.rb', line 1180

def hash
  reduce(0) { |hash, item| (hash << 5) - hash + item.hash }
end

#indices(object) ⇒ List #indices {|item| ... } ⇒ List

Return a `List` of indices of matching objects.

Overloads:

  • #indices(object) ⇒ List

    Return a `List` of indices where `object` is found. Use `#==` for testing equality.

    Examples:

    Hamster::List[1, 2, 3, 4].indices(2)
    # => Hamster::List[1]
  • #indices {|item| ... } ⇒ List

    Pass each item successively to the block. Return a list of indices where the block returns true.

    Examples:

    Hamster::List[1, 2, 3, 4].indices { |e| e.even? }
    # => Hamster::List[1, 3]

    Yields:

    • (item)

Returns:


893
894
895
896
897
898
899
900
901
902
903
904
905
# File 'lib/hamster/list.rb', line 893

def indices(object = Undefined, i = 0, &block)
  return indices { |item| item == object } if not block_given?
  return EmptyList if empty?
  LazyList.new do
    node = self
    while true
      break Cons.new(i, node.tail.indices(Undefined, i + 1, &block)) if yield(node.head)
      node = node.tail
      break EmptyList if node.empty?
      i += 1
    end
  end
end

#initList

Return a `List` with all elements except the last one.

Examples:

Hamster::List["a", "b", "c"].init # => Hamster::List["a", "b"]

Returns:


651
652
653
654
# File 'lib/hamster/list.rb', line 651

def init
  return EmptyList if tail.empty?
  LazyList.new { Cons.new(head, tail.init) }
end

#initsList

Return a `List` of all prefixes of this list.

Examples:

Hamster::List[1,2,3].inits
# => Hamster::List[
#      Hamster::List[1],
#      Hamster::List[1, 2],
#      Hamster::List[1, 2, 3]]

Returns:


691
692
693
694
695
696
# File 'lib/hamster/list.rb', line 691

def inits
  LazyList.new do
    next self if empty?
    Cons.new(List[head], tail.inits.map { |list| list.cons(head) })
  end
end

#insert(index, *items) ⇒ List

Return a new `List` with the given items inserted before the item at `index`.

Examples:

Hamster::List["A", "D", "E"].insert(1, "B", "C") # => Hamster::List["A", "B", "C", "D", "E"]

Parameters:

  • index (Integer)

    The index where the new items should go

  • items (Array)

    The items to add

Returns:


973
974
975
976
977
978
979
980
981
982
983
984
# File 'lib/hamster/list.rb', line 973

def insert(index, *items)
  if index == 0
    return List.from_enum(items).append(self)
  elsif index > 0
    LazyList.new do
      Cons.new(head, tail.insert(index-1, *items))
    end
  else
    raise IndexError if index < -size
    insert(index + size, *items)
  end
end

#inspectString

Return the contents of this `List` as a programmer-readable `String`. If all the items in the list are serializable as Ruby literal strings, the returned string can be passed to `eval` to reconstitute an equivalent `List`.

Returns:

  • (String)

1203
1204
1205
1206
1207
# File 'lib/hamster/list.rb', line 1203

def inspect
  result = "Hamster::List["
  each_with_index { |obj, i| result << ', ' if i > 0; result << obj.inspect }
  result << "]"
end

#intersperse(sep) ⇒ List

Return a new `List` with `sep` inserted between each of the existing elements.

Examples:

Hamster::List["one", "two", "three"].intersperse(" ")
# => Hamster::List["one", " ", "two", " ", "three"]

Returns:


587
588
589
590
591
592
# File 'lib/hamster/list.rb', line 587

def intersperse(sep)
  LazyList.new do
    next self if tail.empty?
    Cons.new(head, Cons.new(sep, tail.intersperse(sep)))
  end
end

#lastObject

Return the last item in this list.

Returns:

  • (Object)

658
659
660
661
662
# File 'lib/hamster/list.rb', line 658

def last
  list = self
  list = list.tail until list.tail.empty?
  list.head
end

#map {|item| ... } ⇒ List, Enumerator Also known as: collect

Return a `List` in which each element is derived from the corresponding element in this `List`, transformed through the given block. If no block is given, returns an `Enumerator`.

Examples:

Hamster::List[3, 2, 1].map { |e| e * e } # => Hamster::List[9, 4, 1]

Yields:

  • (item)

Returns:

  • (List, Enumerator)

231
232
233
234
235
236
237
# File 'lib/hamster/list.rb', line 231

def map(&block)
  return enum_for(:map) unless block_given?
  LazyList.new do
    next self if empty?
    Cons.new(yield(head), tail.map(&block))
  end
end

#merge {|a, b| ... } ⇒ List

Merge all the nested lists into a single list, using the given comparator block to determine the order which items should be shifted out of the nested lists and into the output list.

Examples:

list_1 = Hamster::List[1, -3, -5]
list_2 = Hamster::List[-2, 4, 6]
Hamster::List[list_1, list_2].merge { |a,b| a.abs <=> b.abs }
# => Hamster::List[1, -2, -3, 4, -5, 6]

Yields:

  • (a, b)

    Pairs of items from matching indices in each list.

Yield Returns:

  • (Integer)

    Negative if the first element should be selected first, positive if the latter element, or zero if either.

Returns:


922
923
924
925
926
927
928
929
930
931
# File 'lib/hamster/list.rb', line 922

def merge(&comparator)
  return merge_by unless block_given?
  LazyList.new do
    sorted = reject(&:empty?).sort do |a, b|
      yield(a.head, b.head)
    end
    next EmptyList if sorted.empty?
    Cons.new(sorted.head.head, sorted.tail.cons(sorted.head.tail).merge(&comparator))
  end
end

#merge_by {|item| ... } ⇒ List

Merge all the nested lists into a single list, using sort keys generated by mapping the items in the nested lists through the given block to determine the order which items should be shifted out of the nested lists and into the output list. Whichever nested list's `#head` has the “lowest” sort key (according to their natural order) will be the first in the merged `List`.

Examples:

list_1 = Hamster::List[1, -3, -5]
list_2 = Hamster::List[-2, 4, 6]
Hamster::List[list_1, list_2].merge_by { |x| x.abs }
# => Hamster::List[1, -2, -3, 4, -5, 6]

Yields:

  • (item)

    Once for each item in either list.

Yield Returns:

  • (Object)

    A sort key for the element.

Returns:


948
949
950
951
952
953
954
955
956
957
# File 'lib/hamster/list.rb', line 948

def merge_by(&transformer)
  return merge_by { |item| item } unless block_given?
  LazyList.new do
    sorted = reject(&:empty?).sort_by do |list|
      yield(list.head)
    end
    next EmptyList if sorted.empty?
    Cons.new(sorted.head.head, sorted.tail.cons(sorted.head.tail).merge_by(&transformer))
  end
end

#partition {|item| ... } ⇒ List

Return two `List`s, the first containing all the elements for which the block evaluates to true, the second containing the rest.

Examples:

Hamster::List[1, 2, 3, 4, 5, 6].partition { |x| x.even? }
# => [Hamster::List[2, 4, 6], Hamster::List[1, 3, 5]]

Yields:

  • (item)

    Once for each item.

Returns:


1153
1154
1155
1156
1157
1158
1159
# File 'lib/hamster/list.rb', line 1153

def partition(&block)
  return enum_for(:partition) if not block_given?
  partitioner = Partitioner.new(self, block)
  mutex = Mutex.new
  [Partitioned.new(partitioner, partitioner.left, mutex),
   Partitioned.new(partitioner, partitioner.right, mutex)].freeze
end

#permutation(length = size) {|list| ... } ⇒ self, Enumerator

Yields all permutations of length `n` of the items in the list, and then returns `self`. If no length `n` is specified, permutations of the entire list will be yielded.

There is no guarantee about which order the permutations will be yielded in.

If no block is given, an `Enumerator` is returned instead.

Examples:

Hamster::List[1, 2, 3].permutation.to_a
# => [Hamster::List[1, 2, 3],
#     Hamster::List[2, 1, 3],
#     Hamster::List[2, 3, 1],
#     Hamster::List[1, 3, 2],
#     Hamster::List[3, 1, 2],
#     Hamster::List[3, 2, 1]]

Yields:

  • (list)

    Once for each permutation.

Returns:

  • (self, Enumerator)

1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
# File 'lib/hamster/list.rb', line 1097

def permutation(length = size, &block)
  return enum_for(:permutation, length) if not block_given?
  if length == 0
    yield EmptyList
  elsif length == 1
    each { |obj| yield Cons.new(obj, EmptyList) }
  elsif not empty?
    if length < size
      tail.permutation(length, &block)
    end

    tail.permutation(length-1) do |p|
      0.upto(length-1) do |i|
        left,right = p.split_at(i)
        yield left.append(right.cons(head))
      end
    end
  end
  self
end

#popList

Return a `List` containing all but the last item from this `List`.

Examples:

Hamster::List["A", "B", "C"].pop  # => Hamster::List["A", "B"]

Returns:


339
340
341
342
343
344
345
346
# File 'lib/hamster/list.rb', line 339

def pop
  LazyList.new do
    next self if empty?
    new_size = size - 1
    next Cons.new(head, tail.take(new_size - 1)) if new_size >= 1
    EmptyList
  end
end

#pretty_print(pp) ⇒ Object

Allows this `List` to be printed at the `pry` console, or using `pp` (from the Ruby standard library), in a way which takes the amount of horizontal space on the screen into account, and which indents nested structures to make them easier to read.


1215
1216
1217
1218
1219
1220
# File 'lib/hamster/list.rb', line 1215

def pretty_print(pp)
  pp.group(1, "Hamster::List[", "]") do
    pp.breakable ''
    pp.seplist(self) { |obj| obj.pretty_print(pp) }
  end
end

#respond_to?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)

1223
1224
1225
# File 'lib/hamster/list.rb', line 1223

def respond_to?(name, include_private = false)
  super || !!name.to_s.match(CADR)
end

#reverseList

Return a `List` with the same items, but in reverse order.

Examples:

Hamster::List["A", "B", "C"].reverse # => Hamster::List["C", "B", "A"]

Returns:


392
393
394
# File 'lib/hamster/list.rb', line 392

def reverse
  LazyList.new { reduce(EmptyList) { |list, item| list.cons(item) }}
end

#rotate(count = 1) ⇒ Vector

Return a new `List` with the same elements, but rotated so that the one at index `count` is the first element of the new list. If `count` is positive, the elements will be shifted left, and those shifted past the lowest position will be moved to the end. If `count` is negative, the elements will be shifted right, and those shifted past the last position will be moved to the beginning.

Examples:

l = Hamster::List["A", "B", "C", "D", "E", "F"]
l.rotate(2)   # => Hamster::List["C", "D", "E", "F", "A", "B"]
l.rotate(-1)  # => Hamster::List["F", "A", "B", "C", "D", "E"]

Parameters:

  • count (Integer) (defaults to: 1)

    The number of positions to shift items by

Returns:

Raises:

  • (TypeError)

    if count is not an integer.


479
480
481
482
483
484
# File 'lib/hamster/list.rb', line 479

def rotate(count = 1)
  raise TypeError, "expected Integer" if not count.is_a?(Integer)
  return self if empty? || (count % size) == 0
  count = (count >= 0) ? count % size : (size - (~count % size) - 1)
  drop(count).append(take(count))
end

#sampleObject

Return a randomly chosen element from this list.

Returns:

  • (Object)

961
962
963
# File 'lib/hamster/list.rb', line 961

def sample
  at(rand(size))
end

#select {|item| ... } ⇒ List Also known as: find_all, keep_if

Return a `List` which contains all the items for which the given block returns true.

Examples:

Hamster::List["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 }
# => Hamster::List["Bird", "Elephant"]

Yields:

  • (item)

    Once for each item.

Returns:


267
268
269
270
271
272
273
274
275
276
277
# File 'lib/hamster/list.rb', line 267

def select(&block)
  return enum_for(:select) unless block_given?
  LazyList.new do
    list = self
    while true
      break list if list.empty?
      break Cons.new(list.head, list.tail.select(&block)) if yield(list.head)
      list = list.tail
    end
  end
end

#sizeInteger Also known as: length

Return the number of items in this `List`.

Returns:

  • (Integer)

166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/hamster/list.rb', line 166

def size
  result, list = 0, self
  until list.empty?
    if list.cached_size?
      return result + list.size
    else
      result += 1
    end
    list = list.tail
  end
  result
end

#list.slice(index) ⇒ Object #list.slice(index, length) ⇒ List #list.slice(index..end) ⇒ Vector Also known as: []

Return specific objects from the `List`. All overloads return `nil` if the starting index is out of range.

Overloads:

  • #list.slice(index) ⇒ Object

    Returns a single object at the given `index`. If `index` is negative, count backwards from the end.

    Examples:

    l = Hamster::List["A", "B", "C", "D", "E", "F"]
    l[2]  # => "C"
    l[-1] # => "F"
    l[6]  # => nil

    Parameters:

    • index (Integer)

      The index to retrieve. May be negative.

    Returns:

    • (Object)
  • #list.slice(index, length) ⇒ List

    Return a sublist starting at `index` and continuing for `length` elements or until the end of the `List`, whichever occurs first.

    Examples:

    l = Hamster::List["A", "B", "C", "D", "E", "F"]
    l[2, 3]  # => Hamster::List["C", "D", "E"]
    l[-2, 3] # => Hamster::List["E", "F"]
    l[20, 1] # => nil

    Parameters:

    • start (Integer)

      The index to start retrieving items from. May be negative.

    • length (Integer)

      The number of items to retrieve.

    Returns:

  • #list.slice(index..end) ⇒ Vector

    Return a sublist starting at `index` and continuing to index `end` or the end of the `List`, whichever occurs first.

    Examples:

    l = Hamster::List["A", "B", "C", "D", "E", "F"]
    l[2..3]    # => Hamster::List["C", "D"]
    l[-2..100] # => Hamster::List["E", "F"]
    l[20..21]  # => nil

    Parameters:

    • range (Range)

      The range of indices to retrieve.

    Returns:


838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/hamster/list.rb', line 838

def slice(arg, length = (missing_length = true))
  if missing_length
    if arg.is_a?(Range)
      from, to = arg.begin, arg.end
      from += size if from < 0
      return nil if from < 0
      to   += size if to < 0
      to   += 1    if !arg.exclude_end?
      length = to - from
      length = 0 if length < 0
      list = self
      while from > 0
        return nil if list.empty?
        list = list.tail
        from -= 1
      end
      list.take(length)
    else
      at(arg)
    end
  else
    return nil if length < 0
    arg += size if arg < 0
    return nil if arg < 0
    list = self
    while arg > 0
      return nil if list.empty?
      list = list.tail
      arg -= 1
    end
    list.take(length)
  end
end

#sortList #sort {|a, b| ... } ⇒ List

Return a new `List` with the same items, but sorted.

Overloads:

  • #sortList

    Compare elements with their natural sort key (`#<=>`).

    Examples:

    Hamster::List["Elephant", "Dog", "Lion"].sort
    # => Hamster::List["Dog", "Elephant", "Lion"]
  • #sort {|a, b| ... } ⇒ List

    Uses the block as a comparator to determine sorted order.

    Examples:

    Hamster::List["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size }
    # => Hamster::List["Dog", "Lion", "Elephant"]

    Yields:

    • (a, b)

      Any number of times with different pairs of elements.

    Yield Returns:

    • (Integer)

      Negative if the first element should be sorted lower, positive if the latter element, or 0 if equal.

Returns:


559
560
561
# File 'lib/hamster/list.rb', line 559

def sort(&comparator)
  LazyList.new { List.from_enum(super(&comparator)) }
end

#sort_by {|element| ... } ⇒ List

Return a new `List` with the same items, but sorted. The sort order is determined by mapping the items through the given block to obtain sort keys, and then sorting the keys according to their natural sort order (`#<=>`).

Examples:

Hamster::List["Elephant", "Dog", "Lion"].sort_by { |e| e.size }
# => Hamster::List["Dog", "Lion", "Elephant"]

Yields:

  • (element)

    Once for each element.

Yield Returns:

  • a sort key object for the yielded element.

Returns:


575
576
577
578
# File 'lib/hamster/list.rb', line 575

def sort_by(&transformer)
  return sort unless block_given?
  LazyList.new { List.from_enum(super(&transformer)) }
end

#span {|item| ... } ⇒ Array

Return two `List`s, one up to (but not including) the first item for which the block returns `nil` or `false`, and another of all the remaining items.

Examples:

Hamster::List[4, 3, 5, 2, 1].span { |x| x > 2 }
# => [Hamster::List[4, 3, 5], Hamster::List[2, 1]]

Yields:

  • (item)

Returns:

  • (Array)

508
509
510
511
512
513
514
# File 'lib/hamster/list.rb', line 508

def span(&block)
  return [self, EmptyList].freeze unless block_given?
  splitter = Splitter.new(self, block)
  mutex = Mutex.new
  [Splitter::Left.new(splitter, splitter.left, mutex),
   Splitter::Right.new(splitter, mutex)].freeze
end

#split_at(number) ⇒ Array

Return two `List`s, one of the first `number` items, and another with the remaining.

Examples:

Hamster::List["a", "b", "c", "d"].split_at(2)
# => [Hamster::List["a", "b"], Hamster::List["c", "d"]]

Parameters:

  • number (Integer)

    The index at which to split this list

Returns:

  • (Array)

495
496
497
# File 'lib/hamster/list.rb', line 495

def split_at(number)
  [take(number), drop(number)].freeze
end

#subsequences {|sublist| ... } ⇒ self

Yield every non-empty sublist to the given block. (The entire `List` also counts as one sublist.)

Examples:

Hamster::List[1, 2, 3].subsequences { |list| p list }
# prints:
# Hamster::List[1]
# Hamster::List[1, 2]
# Hamster::List[1, 2, 3]
# Hamster::List[2]
# Hamster::List[2, 3]
# Hamster::List[3]

Yields:

  • (sublist)

    One or more contiguous elements from this list

Returns:

  • (self)

1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
# File 'lib/hamster/list.rb', line 1133

def subsequences(&block)
  return enum_for(:subsequences) if not block_given?
  if not empty?
    1.upto(size) do |n|
      yield take(n)
    end
    tail.subsequences(&block)
  end
  self
end

#tailsList

Return a `List` of all suffixes of this list.

Examples:

Hamster::List[1,2,3].tails
# => Hamster::List[
#      Hamster::List[1, 2, 3],
#      Hamster::List[2, 3],
#      Hamster::List[3]]

Returns:


674
675
676
677
678
679
# File 'lib/hamster/list.rb', line 674

def tails
  LazyList.new do
    next self if empty?
    Cons.new(self, tail.tails)
  end
end

#take(number) ⇒ List

Return a `List` containing the first `number` items from this `List`.

Examples:

Hamster::List[1, 3, 5, 7, 6, 4, 2].take(3)
# => Hamster::List[1, 3, 5]

Parameters:

  • number (Integer)

    The number of items to retain

Returns:


325
326
327
328
329
330
331
# File 'lib/hamster/list.rb', line 325

def take(number)
  LazyList.new do
    next self if empty?
    next Cons.new(head, tail.take(number - 1)) if number > 0
    EmptyList
  end
end

#take_while {|item| ... } ⇒ List, Enumerator

Return a `List` which contains all elements up to, but not including, the first element for which the block returns `nil` or `false`.

Examples:

Hamster::List[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 }
# => Hamster::List[1, 3]

Yields:

  • (item)

Returns:

  • (List, Enumerator)

290
291
292
293
294
295
296
297
# File 'lib/hamster/list.rb', line 290

def take_while(&block)
  return enum_for(:take_while) unless block_given?
  LazyList.new do
    next self if empty?
    next Cons.new(head, tail.take_while(&block)) if yield(head)
    EmptyList
  end
end

#to_listList

Return `self`.

Returns:


1194
1195
1196
# File 'lib/hamster/list.rb', line 1194

def to_list
  self
end

#transposeList

Gather the first element of each nested list into a new `List`, then the second element of each nested list, then the third, and so on. In other words, if each nested list is a “row”, return a `List` of “columns” instead.

Although the returned list is lazy, each returned nested list (each “column”) is strict. So while each nested list in the input can be infinite, the parent `List` must not be, or trying to realize the first element in the output will cause an infinite loop.

Examples:

# First let's create some infinite lists
list1 = Hamster.iterate(1, &:next)
list2 = Hamster.iterate(2) { |n| n * 2 }
list3 = Hamster.iterate(3) { |n| n * 3 }

# Now we transpose our 3 infinite "rows" into an infinite series of 3-element "columns"
Hamster::List[list1, list2, list3].transpose.take(4)
# => Hamster::List[
#      Hamster::List[1, 2, 3],
#      Hamster::List[2, 4, 9],
#      Hamster::List[3, 8, 27],
#      Hamster::List[4, 16, 81]]

Returns:


440
441
442
443
444
445
446
447
448
# File 'lib/hamster/list.rb', line 440

def transpose
  return EmptyList if empty?
  LazyList.new do
    next EmptyList if any? { |list| list.empty? }
    heads, tails = EmptyList, EmptyList
    reverse_each { |list| heads, tails = heads.cons(list.head), tails.cons(list.tail) }
    Cons.new(heads, tails.transpose)
  end
end

#union(other, items = ::Set.new) ⇒ List Also known as: |

Return a `List` with all the elements from both this list and `other`, with all duplicates removed.

Examples:

Hamster::List[1, 2].union(Hamster::List[2, 3]) # => Hamster::List[1, 2, 3]

Parameters:

  • other (List)

    The list to merge with

Returns:


636
637
638
639
640
641
642
# File 'lib/hamster/list.rb', line 636

def union(other, items = ::Set.new)
  LazyList.new do
    next other._uniq(items) if empty?
    next tail.union(other, items) if items.include?(head)
    Cons.new(head, tail.union(other, items.add(head)))
  end
end

#uniq(&block) ⇒ List

Return a `List` with the same items, but all duplicates removed. Use `#hash` and `#eql?` to determine which items are duplicates.

Examples:

Hamster::List[:a, :b, :a, :c, :b].uniq      # => Hamster::List[:a, :b, :c]
Hamster::List["a", "A", "b"].uniq(&:upcase) # => Hamster::List["a", "b"]

Returns:


602
603
604
# File 'lib/hamster/list.rb', line 602

def uniq(&block)
  _uniq(::Set.new, &block)
end

#zip(others) ⇒ List

Combine two lists by “zipping” them together. The corresponding elements from this `List` and each of `others` (that is, the elements with the same indices) will be gathered into lists.

If `others` contains fewer elements than this list, `nil` will be used for padding.

Examples:

Hamster::List["A", "B", "C"].zip(Hamster::List[1, 2, 3])
# => Hamster::List[Hamster::List["A", 1], Hamster::List["B", 2], Hamster::List["C", 3]]

Parameters:

  • others (List)

    The list to zip together with this one

Returns:


409
410
411
412
413
414
# File 'lib/hamster/list.rb', line 409

def zip(others)
  LazyList.new do
    next self if empty? && others.empty?
    Cons.new(Cons.new(head, Cons.new(others.head)), tail.zip(others.tail))
  end
end