Enumerable#tally with the resulting hash [Feature #17744]

This commit is contained in:
Nobuyoshi Nakada 2021-03-26 16:29:21 +09:00 коммит произвёл GitHub
Родитель 6a352e275b
Коммит 9143d21b1b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 64 добавлений и 6 удалений

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

@ -23,6 +23,8 @@ Outstanding ones only.
* Enumerable#compact is added. [[Feature #17312]]
* Enumerable#tally now accepts an optional hash to count. [[Feature #17744]]
* Enumerator::Lazy
* Enumerator::Lazy#compact is added. [[Feature #17312]]
@ -99,3 +101,4 @@ Excluding feature bug fixes.
[Feature #17411]: https://bugs.ruby-lang.org/issues/17411
[Bug #17423]: https://bugs.ruby-lang.org/issues/17423
[Feature #17479]: https://bugs.ruby-lang.org/issues/17479
[Feature #17744]: https://bugs.ruby-lang.org/issues/17744

28
enum.c
Просмотреть файл

@ -688,13 +688,18 @@ enum_to_a(int argc, VALUE *argv, VALUE obj)
}
static VALUE
enum_hashify(VALUE obj, int argc, const VALUE *argv, rb_block_call_func *iter)
enum_hashify_into(VALUE obj, int argc, const VALUE *argv, rb_block_call_func *iter, VALUE hash)
{
VALUE hash = rb_hash_new();
rb_block_call(obj, id_each, argc, argv, iter, hash);
return hash;
}
static VALUE
enum_hashify(VALUE obj, int argc, const VALUE *argv, rb_block_call_func *iter)
{
return enum_hashify_into(obj, argc, argv, iter, rb_hash_new());
}
static VALUE
enum_to_h_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
{
@ -1020,6 +1025,7 @@ tally_up(st_data_t *group, st_data_t *value, st_data_t arg, int existing)
tally += INT2FIX(1) & ~FIXNUM_FLAG;
}
else {
Check_Type(tally, T_BIGNUM);
tally = rb_big_plus(tally, INT2FIX(1));
RB_OBJ_WRITTEN(hash, Qundef, tally);
}
@ -1045,19 +1051,29 @@ tally_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
/*
* call-seq:
* enum.tally -> a_hash
* enum.tally -> a_hash
* enum.tally(a_hash) -> a_hash
*
* Tallies the collection, i.e., counts the occurrences of each element.
* Returns a hash with the elements of the collection as keys and the
* corresponding counts as values.
*
* ["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}
*
* If a hash is given, the number of occurrences is added to each value
* in the hash, and the hash is returned. The value corresponding to
* each element must be an integer.
*/
static VALUE
enum_tally(VALUE obj)
enum_tally(int argc, VALUE *argv, VALUE obj)
{
return enum_hashify(obj, 0, 0, tally_i);
VALUE hash;
if (rb_check_arity(argc, 0, 1))
hash = rb_check_hash_type(argv[0]);
else
hash = rb_hash_new();
return enum_hashify_into(obj, 0, 0, tally_i, hash);
}
NORETURN(static VALUE first_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, params)));
@ -4393,7 +4409,7 @@ Init_Enumerable(void)
rb_define_method(rb_mEnumerable, "reduce", enum_inject, -1);
rb_define_method(rb_mEnumerable, "partition", enum_partition, 0);
rb_define_method(rb_mEnumerable, "group_by", enum_group_by, 0);
rb_define_method(rb_mEnumerable, "tally", enum_tally, 0);
rb_define_method(rb_mEnumerable, "tally", enum_tally, -1);
rb_define_method(rb_mEnumerable, "first", enum_first, -1);
rb_define_method(rb_mEnumerable, "all?", enum_all, -1);
rb_define_method(rb_mEnumerable, "any?", enum_any, -1);

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

@ -33,3 +33,31 @@ ruby_version_is "2.7" do
end
end
end
ruby_version_is "3.1" do
describe "Enumerable#tally with a hash" do
before :each do
ScratchPad.record []
end
it "returns a hash with counts according to the value" do
enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
enum.tally({ 'foo' => 1 }).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1}
end
it "ignores the default value" do
enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
enum.tally(Hash.new(100)).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1}
end
it "ignores the default proc" do
enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
enum.tally(Hash.new {100}).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1}
end
it "needs the values counting each elements to be an integer" do
enum = EnumerableSpecs::Numerous.new('foo')
-> { enum.tally({ 'foo' => 'bar' }) }.should raise_error(TypeError)
end
end
end

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

@ -394,6 +394,17 @@ class TestEnumerable < Test::Unit::TestCase
def test_tally
h = {1 => 2, 2 => 2, 3 => 1}
assert_equal(h, @obj.tally)
h = {1 => 5, 2 => 2, 3 => 1, 4 => "x"}
assert_equal(h, @obj.tally({1 => 3, 4 => "x"}))
assert_raise(TypeError) do
@obj.tally({1 => ""})
end
h = {1 => 2, 2 => 2, 3 => 1}
assert_equal(h, @obj.tally(Hash.new(100)))
assert_equal(h, @obj.tally(Hash.new {100}))
end
def test_first