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.
This commit is contained in:
Akinori MUSHA 2022-12-21 18:19:19 +09:00
Родитель 684fa46ee6
Коммит 308ccbaeb2
2 изменённых файлов: 42 добавлений и 6 удалений

Просмотреть файл

@ -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;

Просмотреть файл

@ -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)
}