diff options
author | Akinori MUSHA <knu@idaemons.org> | 2022-12-21 18:19:19 +0900 |
---|---|---|
committer | Akinori MUSHA <knu@idaemons.org> | 2022-12-21 18:19:19 +0900 |
commit | 308ccbaeb2c1c0e78d59c0411ddbeede8d2324f0 (patch) | |
tree | 13b386470281b15138139cd48c033feb213e5d75 | |
parent | 684fa46ee68dd7f1f07d4f7f65861d6875736122 (diff) | |
download | ruby-308ccbaeb2c1c0e78d59c0411ddbeede8d2324f0.tar.gz |
Make product consistently yield an array of N elements instead of N arguments
Inconsistency pointed out by @mame:
```
>> Enumerator.product([1], [2], [3]).to_a
=> [[1, 2, 3]]
>> Enumerator.product([1], [2]).to_a
=> [[1, 2]]
>> Enumerator.product([1]).to_a
=> [1]
>> Enumerator.product().to_a
=> [nil]
```
Got fixed as follows:
```
>> Enumerator.product([1], [2], [3]).to_a
=> [[1, 2, 3]]
>> Enumerator.product([1], [2]).to_a
=> [[1, 2]]
>> Enumerator.product([1]).to_a
=> [[1]]
>> Enumerator.product().to_a
=> [[]]
```
This was due to the nature of the N-argument funcall in Ruby.
-rw-r--r-- | enumerator.c | 4 | ||||
-rw-r--r-- | test/ruby/test_enumerator.rb | 44 |
2 files changed, 42 insertions, 6 deletions
diff --git a/enumerator.c b/enumerator.c index 4e0b700f1d..eb402804eb 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3434,7 +3434,7 @@ enumerator_plus(VALUE obj, VALUE eobj) * * The method used against each enumerable object is `each_entry` * instead of `each` so that the product of N enumerable objects - * yields exactly N arguments in each iteration. + * yields an array of exactly N elements in each iteration. * * When no enumerator is given, it calls a given block once yielding * an empty argument list. @@ -3627,7 +3627,7 @@ product_each(VALUE obj, struct product_state *pstate) rb_block_call(eobj, id_each_entry, 0, NULL, product_each_i, (VALUE)pstate); } else { - rb_funcallv(pstate->block, id_call, pstate->argc, pstate->argv); + rb_funcall(pstate->block, id_call, 1, rb_ary_new_from_values(pstate->argc, pstate->argv)); } return obj; diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index d448d62bd5..c9ffd95977 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -908,46 +908,82 @@ class TestEnumerator < Test::Unit::TestCase end def test_product + ## + ## Enumerator::Product + ## + + # 0-dimensional e = Enumerator::Product.new assert_instance_of(Enumerator::Product, e) assert_kind_of(Enumerator, e) assert_equal(1, e.size) elts = [] - e.each { |*x| elts << x } + e.each { |x| elts << x } assert_equal [[]], elts + assert_equal elts, e.to_a + heads = [] + e.each { |x,| heads << x } + assert_equal [nil], heads + # 1-dimensional + e = Enumerator::Product.new(1..3) + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(3, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[1], [2], [3]], elts + assert_equal elts, e.to_a + + # 2-dimensional e = Enumerator::Product.new(1..3, %w[a b]) assert_instance_of(Enumerator::Product, e) assert_kind_of(Enumerator, e) assert_equal(3 * 2, e.size) elts = [] - e.each { |*x| elts << x } + e.each { |x| elts << x } assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts + assert_equal elts, e.to_a + heads = [] + e.each { |x,| heads << x } + assert_equal [1, 1, 2, 2, 3, 3], heads + # Reject keyword arguments assert_raise(ArgumentError) { Enumerator::Product.new(1..3, foo: 1, bar: 2) } + ## + ## Enumerator.product + ## + + # without a block e = Enumerator.product(1..3, %w[a b]) assert_instance_of(Enumerator::Product, e) + # with a block elts = [] - ret = Enumerator.product(1..3, %w[a b]) { |*x| elts << x } + ret = Enumerator.product(1..3) { |x| elts << x } assert_instance_of(Enumerator::Product, ret) - assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts + assert_equal [[1], [2], [3]], elts + assert_equal elts, Enumerator.product(1..3).to_a + # an infinite enumerator and a finite enumerable e = Enumerator.product(1.., 'a'..'c') assert_equal(Float::INFINITY, e.size) assert_equal [[1, "a"], [1, "b"], [1, "c"], [2, "a"]], e.take(4) + # an infinite enumerator and an unknown enumerator e = Enumerator.product(1.., Enumerator.new { |y| y << 'a' << 'b' }) assert_equal(Float::INFINITY, e.size) assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + # an infinite enumerator and an unknown enumerator e = Enumerator.product(1..3, Enumerator.new { |y| y << 'a' << 'b' }) assert_equal(nil, e.size) assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + # Reject keyword arguments assert_raise(ArgumentError) { Enumerator.product(1..3, foo: 1, bar: 2) } |