aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS25
-rw-r--r--array.c14
-rw-r--r--enum.c32
-rw-r--r--hash.c64
-rw-r--r--internal.h1
-rw-r--r--spec/ruby/core/array/to_h_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/to_h_spec.rb8
-rw-r--r--spec/ruby/core/env/to_h_spec.rb13
-rw-r--r--spec/ruby/core/hash/to_h_spec.rb6
-rw-r--r--spec/ruby/core/struct/to_h_spec.rb8
-rw-r--r--struct.c7
-rw-r--r--test/ruby/test_array.rb39
-rw-r--r--test/ruby/test_enum.rb30
-rw-r--r--test/ruby/test_env.rb2
-rw-r--r--test/ruby/test_hash.rb10
-rw-r--r--test/ruby/test_struct.rb7
16 files changed, 243 insertions, 30 deletions
diff --git a/NEWS b/NEWS
index 052b7b9a0c..d1be38cfb9 100644
--- a/NEWS
+++ b/NEWS
@@ -44,6 +44,11 @@ sufficient information, see the ChangeLog file or Redmine
* 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 ChangeLog file or Redmine
* `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 ChangeLog file or Redmine
* `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 ChangeLog file or Redmine
* `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 ChangeLog file or Redmine
* `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]
diff --git a/array.c b/array.c
index 4b62394daf..c58b316525 100644
--- a/array.c
+++ b/array.c
@@ -2171,13 +2171,20 @@ rb_ary_to_a(VALUE ary)
/*
* 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)
{
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)",
diff --git a/enum.c b/enum.c
index 127b839f2d..d75e9d7f95 100644
--- a/enum.c
+++ b/enum.c
@@ -613,38 +613,42 @@ enum_to_a(int argc, VALUE *argv, VALUE obj)
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;
}
diff --git a/hash.c b/hash.c
index d8aaaaa924..b53bb1660e 100644
--- a/hash.c
+++ b/hash.c
@@ -2122,17 +2122,58 @@ rb_hash_to_hash(VALUE hash)
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)
/*
* 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)
/*
* 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)
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);
diff --git a/internal.h b/internal.h
index 1d4302d645..34e157e5e3 100644
--- a/internal.h
+++ b/internal.h
@@ -1359,6 +1359,7 @@ VALUE rb_hash_values(VALUE hash);
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);
diff --git a/spec/ruby/core/array/to_h_spec.rb b/spec/ruby/core/array/to_h_spec.rb
index 9104aed301..e845d2c950 100644
--- a/spec/ruby/core/array/to_h_spec.rb
+++ b/spec/ruby/core/array/to_h_spec.rb
@@ -34,4 +34,11 @@ describe "Array#to_h" do
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
diff --git a/spec/ruby/core/enumerable/to_h_spec.rb b/spec/ruby/core/enumerable/to_h_spec.rb
index 3b712d25d9..966325c736 100644
--- a/spec/ruby/core/enumerable/to_h_spec.rb
+++ b/spec/ruby/core/enumerable/to_h_spec.rb
@@ -43,4 +43,12 @@ describe "Enumerable#to_h" do
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
diff --git a/spec/ruby/core/env/to_h_spec.rb b/spec/ruby/core/env/to_h_spec.rb
index f7f4314335..f6c796b4d6 100644
--- a/spec/ruby/core/env/to_h_spec.rb
+++ b/spec/ruby/core/env/to_h_spec.rb
@@ -3,4 +3,17 @@ require_relative 'shared/to_hash'
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
diff --git a/spec/ruby/core/hash/to_h_spec.rb b/spec/ruby/core/hash/to_h_spec.rb
index 55c5532fc0..40bd0d06d2 100644
--- a/spec/ruby/core/hash/to_h_spec.rb
+++ b/spec/ruby/core/hash/to_h_spec.rb
@@ -7,6 +7,12 @@ describe "Hash#to_h" do
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
diff --git a/spec/ruby/core/struct/to_h_spec.rb b/spec/ruby/core/struct/to_h_spec.rb
index 9239c87988..ddfbfbca61 100644
--- a/spec/ruby/core/struct/to_h_spec.rb
+++ b/spec/ruby/core/struct/to_h_spec.rb
@@ -12,4 +12,12 @@ describe "Struct#to_h" do
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
diff --git a/struct.c b/struct.c
index 54aa510b01..f2db11cb35 100644
--- a/struct.c
+++ b/struct.c
@@ -880,9 +880,14 @@ rb_struct_to_h(VALUE s)
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;
}
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index e1a77b73b1..1440d00577 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -1585,15 +1585,17 @@ class TestArray < Test::Unit::TestCase
$, = 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) {
@@ -1608,6 +1610,27 @@ class TestArray < Test::Unit::TestCase
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)
assert_equal(3, [1, 2, 3, 1, 2].min {|a,b| b <=> a })
diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb
index 5c5d359b82..a4eace2d57 100644
--- a/test/ruby/test_enum.rb
+++ b/test/ruby/test_enum.rb
@@ -144,8 +144,7 @@ class TestEnumerable < Test::Unit::TestCase
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::TestCase
yield kvp
end
obj.extend Enumerable
+ obj.freeze
+ end
+
+ def test_to_h
+ obj = StubToH
+
assert_equal({
:hello => :world,
:key => :value,
@@ -175,6 +180,27 @@ class TestEnumerable < Test::Unit::TestCase
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 })
assert_equal(48, @obj.inject {|z, x| z * 2 + x })
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index b9b8fc8b04..54a0270967 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -399,6 +399,8 @@ class TestEnv < Test::Unit::TestCase
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
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index 11735c5620..6aaeddc9d4 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -847,6 +847,16 @@ class TestHash < Test::Unit::TestCase
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)
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index edcd111acb..4748419d6b 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -362,6 +362,13 @@ module TestStruct
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