Skip to content

Commit

Permalink
Move Enumerable#chunk to mruby-enumerator
Browse files Browse the repository at this point in the history
  • Loading branch information
hoshiumiarata committed Jan 22, 2024
1 parent d001974 commit ab21813
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
68 changes: 68 additions & 0 deletions mrbgems/mruby-enumerator/mrblib/enumerator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,72 @@ def zip(*args, &block)

result
end

##
# call-seq:
# enum.chunk -> enumerator
# enum.chunk { |arr| block } -> enumerator
#
# Each element in the returned enumerator is a 2-element array consisting of:
#
# - A value returned by the block.
# - An array ("chunk") containing the element for which that value was returned,
# and all following elements for which the block returned the same value:
#
# So that:
#
# - Each block return value that is different from its predecessor
# begins a new chunk.
# - Each block return value that is the same as its predecessor
# continues the same chunk.
#
# Example:
#
# e = (0..10).chunk {|i| (i / 3).floor } # => #<Enumerator: ...>
# # The enumerator elements.
# e.next # => [0, [0, 1, 2]]
# e.next # => [1, [3, 4, 5]]
# e.next # => [2, [6, 7, 8]]
# e.next # => [3, [9, 10]]
#
# You can use the special symbol <tt>:_alone</tt> to force an element
# into its own separate chuck:
#
# a = [0, 0, 1, 1]
# e = a.chunk{|i| i.even? ? :_alone : true }
# e.to_a # => [[:_alone, [0]], [:_alone, [0]], [true, [1, 1]]]
#
# You can use the special symbol <tt>:_separator</tt> or +nil+
# to force an element to be ignored (not included in any chunk):
#
# a = [0, 0, -1, 1, 1]
# e = a.chunk{|i| i < 0 ? :_separator : true }
# e.to_a # => [[true, [0, 0]], [true, [1, 1]]]
def chunk(&block)
return to_enum :chunk unless block

enum = self
Enumerator.new do |y|
last_value, arr = nil, []
enum.each do |element|
value = block.call(element)
case value
when :_alone
y.yield [last_value, arr] if arr.size > 0
y.yield [value, [element]]
last_value, arr = nil, []
when :_separator, nil
y.yield [last_value, arr] if arr.size > 0
last_value, arr = nil, []
when last_value
arr << element
else
raise 'symbols beginning with an underscore are reserved' if value.is_a?(Symbol) && value.to_s[0] == '_'
y.yield [last_value, arr] if arr.size > 0
last_value, arr = value, [element]
end
end
y.yield [last_value, arr] if arr.size > 0
end
end
end
49 changes: 49 additions & 0 deletions mrbgems/mruby-enumerator/test/enumerator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -598,3 +598,52 @@ def (o = Object.new).each
], enum.to_a
}
end

assert("Enumerable#chunk") do
chunk = [1, 2, 3, 1, 2].chunk
assert_equal Enumerator, chunk.class
result = chunk.with_index { |elt, i| elt - i }.to_a
assert_equal [[1, [1, 2, 3]], [-2, [1, 2]]], result

assert_equal Enumerator, [].chunk {}.class

e = [1, 2, 3]
recorded = []
e.chunk { |x| recorded << x }.to_a
assert_equal [1, 2, 3], recorded

e = [1, 2, 3, 2, 3, 2, 1]
result = e.chunk { |x| x < 3 && 1 || 0 }.to_a
assert_equal [[1, [1, 2]], [0, [3]], [1, [2]], [0, [3]], [1, [2, 1]]], result

e = [1, 2, 3]
assert_equal [[1, 2], [3]], e.chunk { |x| x > 2 }.map(&:last)

e = [1, 2, 3, 2, 1]
result = e.chunk { |x| x < 2 && :_alone }.to_a
assert_equal [[:_alone, [1]], [false, [2, 3, 2]], [:_alone, [1]]], result

e = [[1, 2]]
inner_value = []
e.chunk { |*x| inner_value << x }.to_a
assert_equal [[[1, 2]]], inner_value

e = [1, 2, 3, 3, 2, 1]
result = e.chunk { |x| x == 2 ? :_separator : 1 }.to_a
assert_equal [[1, [1]], [1, [3, 3]], [1, [1]]], result

e = [1, 2, 3, 2, 1]
result = e.chunk { |x| x == 2 ? nil : 1 }.to_a
assert_equal [[1, [1]], [1, [3]], [1, [1]]], result


e = [1, 2, 3, 2, 1]
assert_raise(RuntimeError) { e.chunk { |x| :_arbitrary }.to_a }

e = [1, 2, 3]
assert_raise(ArgumentError) { e.chunk(1) {} }

e = [1, 2, 3, 2, 1]
enum = e.chunk { |x| true }
assert_nil enum.size
end

0 comments on commit ab21813

Please sign in to comment.