ruby-changes:65660
From: Nobuyoshi <ko1@a...>
Date: Fri, 26 Mar 2021 16:29:42 +0900 (JST)
Subject: [ruby-changes:65660] 9143d21b1b (master): Enumerable#tally with the resulting hash [Feature #17744]
https://git.ruby-lang.org/ruby.git/commit/?id=9143d21b1b From 9143d21b1bf2f16b1e847d569a588510726d8860 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada <nobu@r...> Date: Fri, 26 Mar 2021 16:29:21 +0900 Subject: Enumerable#tally with the resulting hash [Feature #17744] --- NEWS.md | 3 +++ enum.c | 28 ++++++++++++++++++++++------ spec/ruby/core/enumerable/tally_spec.rb | 28 ++++++++++++++++++++++++++++ test/ruby/test_enum.rb | 11 +++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index f8d2179..cf79ab7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,8 @@ Outstanding ones only. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L23 * 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. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L101 [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 diff --git a/enum.c b/enum.c index dab2469..c7828cd 100644 --- a/enum.c +++ b/enum.c @@ -688,14 +688,19 @@ enum_to_a(int argc, VALUE *argv, VALUE obj) https://github.com/ruby/ruby/blob/trunk/enum.c#L688 } 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)) { ENUM_WANT_SVALUE(); @@ -1020,6 +1025,7 @@ tally_up(st_data_t *group, st_data_t *value, st_data_t arg, int existing) https://github.com/ruby/ruby/blob/trunk/enum.c#L1025 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)) https://github.com/ruby/ruby/blob/trunk/enum.c#L1051 /* * 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) https://github.com/ruby/ruby/blob/trunk/enum.c#L4409 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); diff --git a/spec/ruby/core/enumerable/tally_spec.rb b/spec/ruby/core/enumerable/tally_spec.rb index 363b3de..1367453 100644 --- a/spec/ruby/core/enumerable/tally_spec.rb +++ b/spec/ruby/core/enumerable/tally_spec.rb @@ -33,3 +33,31 @@ ruby_version_is "2.7" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/enumerable/tally_spec.rb#L33 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 diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index 3b0e0f7..b6d96f1 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -394,6 +394,17 @@ class TestEnumerable < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L394 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 -- cgit v1.1 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/