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

ruby-changes:52582

From: nobu <ko1@a...>
Date: Fri, 21 Sep 2018 00:07:02 +0900 (JST)
Subject: [ruby-changes:52582] nobu:r64794 (trunk): Enumerable#to_h with block and so on

nobu	2018-09-21 00:06:56 +0900 (Fri, 21 Sep 2018)

  New Revision: 64794

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=64794

  Log:
    Enumerable#to_h with block and so on
    
    [Feature #15143]

  Modified files:
    trunk/NEWS
    trunk/array.c
    trunk/enum.c
    trunk/hash.c
    trunk/internal.h
    trunk/spec/ruby/core/array/to_h_spec.rb
    trunk/spec/ruby/core/enumerable/to_h_spec.rb
    trunk/spec/ruby/core/env/to_h_spec.rb
    trunk/spec/ruby/core/hash/to_h_spec.rb
    trunk/spec/ruby/core/struct/to_h_spec.rb
    trunk/struct.c
    trunk/test/ruby/test_array.rb
    trunk/test/ruby/test_enum.rb
    trunk/test/ruby/test_env.rb
    trunk/test/ruby/test_hash.rb
    trunk/test/ruby/test_struct.rb
Index: test/ruby/test_hash.rb
===================================================================
--- test/ruby/test_hash.rb	(revision 64793)
+++ test/ruby/test_hash.rb	(revision 64794)
@@ -847,6 +847,16 @@ class TestHash < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_hash.rb#L847
     assert_equal("nope42", h[42])
   end
 
+  def test_to_h_block
+    h = @h.to_h {|k, v| [k.to_s, v.to_s]}
+    assert_equal({
+                   "1"=>"one", "2"=>"two", "3"=>"three", to_s=>"self",
+                   "true"=>"true", ""=>"nil", "nil"=>""
+                 },
+                 h)
+    assert_instance_of(Hash, h)
+  end
+
   def test_nil_to_h
     h = nil.to_h
     assert_equal({}, h)
Index: test/ruby/test_env.rb
===================================================================
--- test/ruby/test_env.rb	(revision 64793)
+++ test/ruby/test_env.rb	(revision 64794)
@@ -399,6 +399,8 @@ class TestEnv < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_env.rb#L399
 
   def test_to_h
     assert_equal(ENV.to_hash, ENV.to_h)
+    assert_equal(ENV.map {|k, v| ["$#{k}", v.size]}.to_h,
+                 ENV.to_h {|k, v| ["$#{k}", v.size]})
   end
 
   def test_reject
Index: test/ruby/test_array.rb
===================================================================
--- test/ruby/test_array.rb	(revision 64793)
+++ test/ruby/test_array.rb	(revision 64794)
@@ -1585,15 +1585,17 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L1585
     $, = nil
   end
 
+  StubToH = [
+    [:key, :value],
+    Object.new.tap do |kvp|
+      def kvp.to_ary
+        [:obtained, :via_to_ary]
+      end
+    end,
+  ]
+
   def test_to_h
-    kvp = Object.new
-    def kvp.to_ary
-      [:obtained, :via_to_ary]
-    end
-    array = [
-      [:key, :value],
-      kvp,
-    ]
+    array = StubToH
     assert_equal({key: :value, obtained: :via_to_ary}, array.to_h)
 
     e = assert_raise(TypeError) {
@@ -1607,6 +1609,27 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L1609
     }
     assert_equal "wrong array length at 2 (expected 2, was 1)", e.message
   end
+
+  def test_to_h_block
+    array = StubToH
+    assert_equal({"key" => "value", "obtained" => "via_to_ary"},
+                 array.to_h {|k, v| [k.to_s, v.to_s]})
+
+    assert_equal({first_one: :ok, not_ok: :ng},
+                 [[:first_one, :ok], :not_ok].to_h {|k, v| [k, v || :ng]})
+
+    e = assert_raise(TypeError) {
+      [[:first_one, :ok], :not_ok].to_h {|k, v| v ? [k, v] : k}
+    }
+    assert_equal "wrong element type Symbol at 1 (expected array)", e.message
+    array = [1]
+    k = eval("class C\u{1f5ff}; self; end").new
+    assert_raise_with_message(TypeError, /C\u{1f5ff}/) {array.to_h {k}}
+    e = assert_raise(ArgumentError) {
+      [[:first_one, :ok], [1, 2], [:not_ok]].to_h {|kv| kv}
+    }
+    assert_equal "wrong array length at 2 (expected 2, was 1)", e.message
+  end
 
   def test_min
     assert_equal(1, [1, 2, 3, 1, 2].min)
Index: test/ruby/test_struct.rb
===================================================================
--- test/ruby/test_struct.rb	(revision 64793)
+++ test/ruby/test_struct.rb	(revision 64794)
@@ -362,6 +362,13 @@ module TestStruct https://github.com/ruby/ruby/blob/trunk/test/ruby/test_struct.rb#L362
     assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h)
   end
 
+  def test_to_h_block
+    klass = @Struct.new(:a, :b, :c, :d, :e, :f)
+    o = klass.new(1, 2, 3, 4, 5, 6)
+    assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36},
+                 o.to_h {|k, v| [k.to_s, v*v]})
+  end
+
   def test_question_mark_in_member
     klass = @Struct.new(:a, :b?)
     x = Object.new
Index: test/ruby/test_enum.rb
===================================================================
--- test/ruby/test_enum.rb	(revision 64793)
+++ test/ruby/test_enum.rb	(revision 64794)
@@ -144,8 +144,7 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L144
     assert_equal([], inf.to_a)
   end
 
-  def test_to_h
-    obj = Object.new
+  StubToH = Object.new.tap do |obj|
     def obj.each(*args)
       yield(*args)
       yield [:key, :value]
@@ -157,6 +156,12 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L156
       yield kvp
     end
     obj.extend Enumerable
+    obj.freeze
+  end
+
+  def test_to_h
+    obj = StubToH
+
     assert_equal({
       :hello => :world,
       :key => :value,
@@ -174,6 +179,27 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L179
     }
     assert_equal "element has wrong array length (expected 2, was 1)", e.message
   end
+
+  def test_to_h_block
+    obj = StubToH
+
+    assert_equal({
+      "hello" => "world",
+      "key" => "value",
+      "other_key" => "other_value",
+      "obtained" => "via_to_ary",
+    }, obj.to_h(:hello, :world) {|k, v| [k.to_s, v.to_s]})
+
+    e = assert_raise(TypeError) {
+      obj.to_h {:not_an_array}
+    }
+    assert_equal "wrong element type Symbol (expected array)", e.message
+
+    e = assert_raise(ArgumentError) {
+      obj.to_h {[1]}
+    }
+    assert_equal "element has wrong array length (expected 2, was 1)", e.message
+  end
 
   def test_inject
     assert_equal(12, @obj.inject {|z, x| z * x })
Index: struct.c
===================================================================
--- struct.c	(revision 64793)
+++ struct.c	(revision 64794)
@@ -880,9 +880,14 @@ rb_struct_to_h(VALUE s) https://github.com/ruby/ruby/blob/trunk/struct.c#L880
     VALUE h = rb_hash_new_with_size(RSTRUCT_LEN(s));
     VALUE members = rb_struct_members(s);
     long i;
+    int block_given = rb_block_given_p();
 
     for (i=0; i<RSTRUCT_LEN(s); i++) {
-	rb_hash_aset(h, rb_ary_entry(members, i), RSTRUCT_GET(s, i));
+        VALUE k = rb_ary_entry(members, i), v = RSTRUCT_GET(s, i);
+        if (block_given)
+            rb_hash_set_pair(h, rb_yield_values(2, k, v));
+        else
+            rb_hash_aset(h, k, v);
     }
     return h;
 }
Index: spec/ruby/core/array/to_h_spec.rb
===================================================================
--- spec/ruby/core/array/to_h_spec.rb	(revision 64793)
+++ spec/ruby/core/array/to_h_spec.rb	(revision 64794)
@@ -34,4 +34,11 @@ describe "Array#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/array/to_h_spec.rb#L34
   it "does not accept arguments" do
     lambda { [].to_h(:a, :b) }.should raise_error(ArgumentError)
   end
+
+  ruby_version_is "2.6" do
+    it "converts [key, value] pairs returned by the block to a hash" do
+      i = 0
+      [:a, :b].to_h {|k| [k, i += 1]}.should == { a: 1, b: 2 }
+    end
+  end
 end
Index: spec/ruby/core/enumerable/to_h_spec.rb
===================================================================
--- spec/ruby/core/enumerable/to_h_spec.rb	(revision 64793)
+++ spec/ruby/core/enumerable/to_h_spec.rb	(revision 64794)
@@ -43,4 +43,12 @@ describe "Enumerable#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/enumerable/to_h_spec.rb#L43
     enum = EnumerableSpecs::EachDefiner.new([:x])
     lambda { enum.to_h }.should raise_error(ArgumentError)
   end
+
+  ruby_version_is "2.6" do
+    it "converts [key, value] pairs returned by the block to a hash" do
+      enum = EnumerableSpecs::EachDefiner.new(:a, :b)
+      i = 0
+      enum.to_h {|k| [k, i += 1]}.should == { a: 1, b: 2 }
+    end
+  end
 end
Index: spec/ruby/core/struct/to_h_spec.rb
===================================================================
--- spec/ruby/core/struct/to_h_spec.rb	(revision 64793)
+++ spec/ruby/core/struct/to_h_spec.rb	(revision 64794)
@@ -12,4 +12,12 @@ describe "Struct#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/struct/to_h_spec.rb#L12
     car.to_h[:make] = 'Suzuki'
     car.make.should == 'Ford'
   end
+
+  ruby_version_is "2.6" do
+    it "converts [key, value] pairs returned by the block to a hash" do
+      car = StructClasses::Car.new('Ford', 'Ranger')
+      h = car.to_h {|k, v| [k.to_s, "#{v}".downcase]}
+      h.should == {"make" => "ford", "model" => "ranger", "year" => ""}
+    end
+  end
 end
Index: spec/ruby/core/env/to_h_spec.rb
===================================================================
--- spec/ruby/core/env/to_h_spec.rb	(revision 64793)
+++ spec/ruby/core/env/to_h_spec.rb	(revision 64794)
@@ -3,4 +3,17 @@ require_relative 'shared/to_hash' https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/env/to_h_spec.rb#L3
 
 describe "ENV.to_hash" do
   it_behaves_like :env_to_hash, :to_h
+
+  ruby_version_is "2.6" do
+    it "converts [key, value] pairs returned by the block to a hash" do
+      orig = ENV.to_hash
+      begin
+        ENV.replace "a" => "b", "c" => "d"
+        i = 0
+        ENV.to_h {|k, v| [k.to_sym, v.upcase]}.should == {a:"B", c:"D"}
+      ensure
+        ENV.replace orig
+      end
+    end
+  end
 end
Index: spec/ruby/core/hash/to_h_spec.rb
===================================================================
--- spec/ruby/core/hash/to_h_spec.rb	(revision 64793)
+++ spec/ruby/core/hash/to_h_spec.rb	(revision 64794)
@@ -7,6 +7,12 @@ describe "Hash#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/hash/to_h_spec.rb#L7
     h.to_h.should equal(h)
   end
 
+  ruby_version_is "2.6" do
+    it "converts [key, value] pairs returned by the block to a hash" do
+      {a: 1, b: 2}.to_h {|k, v| [k.to_s, v*v]}.should == { "a" => 1, "b" => 4 }
+    end
+  end
+
   describe "when called on a subclass of Hash" do
     before :each do
       @h = HashSpecs::MyHash.new
Index: array.c
===================================================================
--- array.c	(revision 64793)
+++ array.c	(revision 64794)
@@ -2171,13 +2171,20 @@ rb_ary_to_a(VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L2171
 
 /*
  *  call-seq:
- *     ary.to_h     -> hash
+ *     ary.to_h            -> hash
+ *     ary.to_h { block }  -> hash
  *
  *  Returns the result of interpreting <i>ary</i> as an array of
  *  <tt>[key, value]</tt> pairs.
  *
  *     [[:foo, :bar], [1, 2]].to_h
  *       # => {:foo => :bar, 1 => 2}
+ *
+ *  If a block is given, the results of the block on each element of
+ *  the array will be used as pairs.
+ *
+ *     ["foo", "bar"].to_h {|s| [s.ord, s]}
+ *       # => {102=>"foo", 98=>"bar"}
  */
 
 static VALUE
@@ -2185,8 +2192,11 @@ rb_ary_to_h(VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L2192
 {
     long i;
     VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary));
+    int block_given = rb_block_given_p();
+
     for (i=0; i<RARRAY_LEN(ary); i++) {
-	const VALUE elt = rb_ary_elt(ary, i);
+	const VALUE e = rb_ary_elt(ary, i);
+	const VALUE elt = block_given ? rb_yield_force_blockarg(e) : e;
 	const VALUE key_value_pair = rb_check_array_type(elt);
 	if (NIL_P(key_value_pair)) {
 	    rb_raise(rb_eTypeError, "wrong element type %"PRIsVALUE" at %ld (expected array)",
Index: internal.h
===================================================================
--- internal.h	(revision 64793)
+++ internal.h	(revision 64794)
@@ -1359,6 +1359,7 @@ VALUE rb_hash_values(VALUE hash); https://github.com/ruby/ruby/blob/trunk/internal.h#L1359
 VALUE rb_hash_rehash(VALUE hash);
 int rb_hash_add_new_element(VALUE hash, VALUE key, VALUE val);
 #define HASH_PROC_DEFAULT FL_USER2
+VALUE rb_hash_set_pair(VALUE hash, VALUE pair);
 
 /* inits.c */
 void rb_call_inits(void);
Index: enum.c
===================================================================
--- enum.c	(revision 64793)
+++ enum.c	(revision 64794)
@@ -613,38 +613,42 @@ enum_to_a(int argc, VALUE *argv, VALUE o https://github.com/ruby/ruby/blob/trunk/enum.c#L613
 static VALUE
 enum_to_h_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
 {
-    VALUE key_value_pair;
     ENUM_WANT_SVALUE();
     rb_thread_check_ints();
-    key_value_pair = rb_check_array_type(i);
-    if (NIL_P(key_value_pair)) {
-	rb_raise(rb_eTypeError, "wrong element type %s (expected array)",
-	    rb_builtin_class_name(i));
-    }
-    if (RARRAY_LEN(key_value_pair) != 2) {
-        rb_raise(rb_eArgError, "element has wrong array length (expected 2, was %ld)",
-	    RARRAY_LEN(key_value_pair));
-    }
-    rb_hash_aset(hash, RARRAY_AREF(key_value_pair, 0), RARRAY_AREF(key_value_pair, 1));
-    return Qnil;
+    return rb_hash_set_pair(hash, i);
+}
+
+static VALUE
+enum_to_h_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
+{
+    rb_thread_check_ints();
+    return rb_hash_set_pair(hash, rb_yield_values2(argc, argv));
 }
 
 /*
  *  call-seq:
- *     enum.to_h(*args)  -> hash
+ *     enum.to_h(*args)        -> hash
+ *     enum.to_h(*args) {...}  -> hash
  *
  *  Returns the result of interpreting <i>enum</i> as a list of
  *  <tt>[key, value]</tt> pairs.
  *
  *     %i[hello world].each_with_index.to_h
  *       # => {:hello => 0, :world => 1}
+ *
+ *  If a block is given, the results of the block on each element of
+ *  the array will be used as pairs.
+ *
+ *     (1..5).to_h {|x| [x, x ** 2]}
+ *       #=> {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
  */
 
 static VALUE
 enum_to_h(int argc, VALUE *argv, VALUE obj)
 {
     VALUE hash = rb_hash_new();
-    rb_block_call(obj, id_each, argc, argv, enum_to_h_i, hash);
+    rb_block_call_func *iter = rb_block_given_p() ? enum_to_h_ii : enum_to_h_i;
+    rb_block_call(obj, id_each, argc, argv, iter, hash);
     OBJ_INFECT(hash, obj);
     return hash;
 }
Index: NEWS
===================================================================
--- NEWS	(revision 64793)
+++ NEWS	(revision 64794)
@@ -44,6 +44,11 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L44
         * Added `Array#union` instance method.
           [Feature #14097]
 
+    * Modified methods:
+
+        * `Array#to_h` now maps elements to new keys and values by the
+	  block if given.  [Feature #15143]
+
     * Aliased methods:
 
         * `Array#filter` is a new alias for `Array#select`.
@@ -74,6 +79,11 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L79
 
 * `Enumerable`
 
+    * Modified methods:
+
+        * `Enumerable#to_h` now maps elements to new keys and values
+	  by the block if given.  [Feature #15143]
+
     * Aliased methods:
 
         * `Enumerable#filter` is a new alias for `Enumerable#select`.
@@ -86,6 +96,13 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L96
         * `Enumerator::Lazy#filter` is a new alias for
           `Enumerator::Lazy#select`.  [Feature #13784]
 
+* `ENV`
+
+    * Modified methods:
+
+        * `ENV.to_h` now maps names and values to new keys and values
+	  by the block if given.  [Feature #15143]
+
 * `Exception`
 
     * New options:
@@ -100,6 +117,9 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L117
         * `Hash#merge`, `update`, `merge!` and `update!` now accept multiple
            arguments.  [Feature #15111]
 
+        * `Hash#to_h` now maps keys and values to new keys and values
+	  by the block if given.  [Feature #15143]
+
     * Aliased methods:
 
         * `Hash#filter` is a new alias for `Hash#select`.  [Feature #13784]
@@ -207,6 +227,11 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L227
 
 * `Struct`
 
+    * Modified methods:
+
+        * `Hash#to_h` now maps keys and values to new keys and values
+	  by the block if given.  [Feature #15143]
+
     * Aliased method:
 
         * `Struct#filter` is a new alias for `Struct#select` [Feature #13784]
Index: hash.c
===================================================================
--- hash.c	(revision 64793)
+++ hash.c	(revision 64794)
@@ -2122,17 +2122,58 @@ rb_hash_to_hash(VALUE hash) https://github.com/ruby/ruby/blob/trunk/hash.c#L2122
     return hash;
 }
 
+VALUE
+rb_hash_set_pair(VALUE hash, VALUE arg)
+{
+    VALUE pair;
+
+    pair = rb_check_array_type(arg);
+    if (NIL_P(pair)) {
+        rb_raise(rb_eTypeError, "wrong element type %s (expected array)",
+                 rb_builtin_class_name(arg));
+    }
+    if (RARRAY_LEN(pair) != 2) {
+        rb_raise(rb_eArgError, "element has wrong array length (expected 2, was %ld)",
+                 RARRAY_LEN(pair));
+    }
+    rb_hash_aset(hash, RARRAY_AREF(pair, 0), RARRAY_AREF(pair, 1));
+    return hash;
+}
+
+static int
+to_h_i(VALUE key, VALUE value, VALUE hash)
+{
+    rb_hash_set_pair(hash, rb_yield_values(2, key, value));
+    return ST_CONTINUE;
+}
+
+static VALUE
+rb_hash_to_h_block(VALUE hash)
+{
+    VALUE h = rb_hash_new_with_size(RHASH_SIZE(hash));
+    rb_hash_foreach(hash, to_h_i, h);
+    OBJ_INFECT(h, hash);
+    return h;
+}
+
 /*
  *  call-seq:
- *     hsh.to_h     -> hsh or new_hash
+ *     hsh.to_h                         -> hsh or new_hash
+ *     hsh.to_h {|key, value| block }   -> new_hash
  *
  *  Returns +self+. If called on a subclass of Hash, converts
  *  the receiver to a Hash object.
+ *
+ *  If a block is given, the results of the block on each pair of
+ *  the receiver will be used as pairs.
  */
 
 static VALUE
 rb_hash_to_h(VALUE hash)
 {
+    if (rb_block_given_p()) {
+        return rb_hash_to_h_block(hash);
+    }
     if (rb_obj_class(hash) != rb_cHash) {
 	const VALUE flags = RBASIC(hash)->flags;
 	hash = hash_dup(hash, rb_cHash, flags & HASH_PROC_DEFAULT);
@@ -4460,7 +4501,6 @@ env_index(VALUE dmy, VALUE value) https://github.com/ruby/ruby/blob/trunk/hash.c#L4501
 /*
  * call-seq:
  *   ENV.to_hash -> hash
- *   ENV.to_h    -> hash
  *
  * Creates a hash with a copy of the environment variables.
  *
@@ -4487,6 +4527,24 @@ env_to_hash(void) https://github.com/ruby/ruby/blob/trunk/hash.c#L4527
 
 /*
  * call-seq:
+ *   ENV.to_h                        -> hash
+ *   ENV.to_h {|name, value| block } -> hash
+ *
+ * Creates a hash with a copy of the environment variables.
+ *
+ */
+static VALUE
+env_to_h(void)
+{
+    VALUE hash = env_to_hash();
+    if (rb_block_given_p()) {
+        hash = rb_hash_to_h_block(hash);
+    }
+    return hash;
+}
+
+/*
+ * call-seq:
  *   ENV.reject { |name, value| } -> Hash
  *   ENV.reject                   -> Enumerator
  *
@@ -4873,7 +4931,7 @@ Init_Hash(void) https://github.com/ruby/ruby/blob/trunk/hash.c#L4931
     rb_define_singleton_method(envtbl, "key?", env_has_key, 1);
     rb_define_singleton_method(envtbl, "value?", env_has_value, 1);
     rb_define_singleton_method(envtbl, "to_hash", env_to_hash, 0);
-    rb_define_singleton_method(envtbl, "to_h", env_to_hash, 0);
+    rb_define_singleton_method(envtbl, "to_h", env_to_h, 0);
     rb_define_singleton_method(envtbl, "assoc", env_assoc, 1);
     rb_define_singleton_method(envtbl, "rassoc", env_rassoc, 1);
 

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

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