mruby-hash-ext: enhance __merge for multiple arguments

Replace single-argument __merge with multi-argument C implementation.
Eliminates Ruby loop overhead for merging multiple hashes.
Optimizes merge! method while maintaining block functionality.

Co-authored-by: Atlassian Rovo Dev
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-07-01 22:08:09 +09:00
parent 69d278589d
commit 33d6ec08f3
3 changed files with 51 additions and 16 deletions
+3 -7
View File
@@ -32,13 +32,9 @@ class Hash
other = others[i]
i += 1
raise TypeError, "Hash required (#{other.class} given)" unless Hash === other
if block
other.each_key{|k|
self[k] = (self.has_key?(k))? block.call(k, self[k], other[k]): other[k]
}
else
other.each_key{|k| self[k] = other[k]}
end
other.each_key{|k|
self[k] = (self.has_key?(k))? block.call(k, self[k], other[k]): other[k]
}
end
self
end
+39
View File
@@ -295,6 +295,44 @@ hash_deconstruct_keys(mrb_state *mrb, mrb_value hash)
return result;
}
/*
* call-seq:
* hsh.__merge(*others) -> hsh
*
* Merges multiple hashes into hsh. This is an internal method
* used by merge! for non-block cases.
*
* Raises ArgumentError if no arguments given.
* Raises TypeError if any argument is not a Hash.
*
* h = { a: 1, b: 2 }
* h.__merge({ c: 3 }, { d: 4 }) #=> { a: 1, b: 2, c: 3, d: 4 }
*/
static mrb_value
hash_merge(mrb_state *mrb, mrb_value hash)
{
const mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
/* Validate arguments */
if (argc == 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given 0, expected 1+)");
}
/* Merge multiple hashes in C */
for (mrb_int i = 0; i < argc; i++) {
if (!mrb_hash_p(argv[i])) {
mrb_raisef(mrb, E_TYPE_ERROR, "no implicit conversion of %C into Hash",
mrb_obj_class(mrb, argv[i]));
}
mrb_hash_merge(mrb, hash, argv[i]);
}
return hash;
}
void
mrb_mruby_hash_ext_gem_init(mrb_state *mrb)
{
@@ -306,6 +344,7 @@ mrb_mruby_hash_ext_gem_init(mrb_state *mrb)
mrb_define_method_id(mrb, h, MRB_SYM(except), hash_except, MRB_ARGS_ANY());
mrb_define_method_id(mrb, h, MRB_SYM(key), hash_key, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, h, MRB_SYM(deconstruct_keys), hash_deconstruct_keys, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, h, MRB_SYM(__merge), hash_merge, MRB_ARGS_ANY());
mrb_define_class_method_id(mrb, h, MRB_OPSYM(aref), hash_s_create, MRB_ARGS_ANY());
}
+9 -9
View File
@@ -46,27 +46,27 @@ assert('Hash.[] for sub class') do
end
assert('Hash#merge!') do
# Single hash merge
a = { 'abc_key' => 'abc_value', 'cba_key' => 'cba_value' }
b = { 'cba_key' => 'XXX', 'xyz_key' => 'xyz_value' }
result_1 = a.merge! b
assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'XXX',
'xyz_key' => 'xyz_value' }, result_1)
# Block handling
a = { 'abc_key' => 'abc_value', 'cba_key' => 'cba_value' }
result_2 = a.merge!(b) do |key, original, new|
original
end
assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'XXX',
'xyz_key' => 'xyz_value' }, result_1)
assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'cba_value',
'xyz_key' => 'xyz_value' }, result_2)
assert_raise(TypeError) do
{ 'abc_key' => 'abc_value' }.merge! "a"
end
# multiple arguments
# Multiple arguments
assert_equal({a:1,b:2,c:3}, {a:1}.merge!({b:2},{c:3}))
# Error cases
assert_raise(ArgumentError) { {}.merge!() }
assert_raise(TypeError) { {}.merge!("not a hash") }
end
assert('Hash#values_at') do