[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]