mruby-array-ext: implement Array#- in C

Refactor Array#- to a C implementation for improved memory and performance,
especially for set operations. Uses a hybrid approach for efficiency.

Co-authored-by: Gemini <gemini@google.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-06-27 22:08:23 +09:00
parent 6d3c4e2876
commit 409f39e911
3 changed files with 117 additions and 32 deletions
-32
View File
@@ -56,38 +56,6 @@ class Array
ary
end
##
# call-seq:
# ary - other_ary -> new_ary
#
# Array Difference---Returns a new array that is a copy of
# the original array, removing any items that also appear in
# <i>other_ary</i>. (If you need set-like behavior, see the
# library class Set.)
#
# [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
#
def -(elem)
raise TypeError, "can't convert #{elem.class} into Array" unless elem.class == Array
hash = {}
array = []
idx = 0
len = elem.size
while idx < len
hash[elem[idx]] = true
idx += 1
end
idx = 0
len = size
while idx < len
v = self[idx]
array << v unless hash[v]
idx += 1
end
array
end
##
# call-seq:
# ary.difference(other_ary1, other_ary2, ...) -> new_ary
+71
View File
@@ -341,6 +341,76 @@ ary_rotate_bang(mrb_state *mrb, mrb_value self)
return self;
}
#define SET_OP_HASH_THRESHOLD 16
/*
* call-seq:
* ary - other_ary -> new_ary
*
* Returns a new array that is a copy of the original array, with any items
* that also appear in +other_ary+ removed.
*
* [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
*/
static mrb_value
ary_sub(mrb_state *mrb, mrb_value self)
{
mrb_value other, result_ary;
struct RArray *self_ary, *other_ary;
mrb_value *p, *p_end;
mrb_get_args(mrb, "A", &other);
self_ary = mrb_ary_ptr(self);
other_ary = mrb_ary_ptr(other);
p = ARY_PTR(self_ary);
p_end = p + ARY_LEN(self_ary);
result_ary = mrb_ary_new(mrb);
if (ARY_LEN(other_ary) > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, ARY_LEN(other_ary));
mrb_value *other_p = ARY_PTR(other_ary);
mrb_value *other_p_end = other_p + ARY_LEN(other_ary);
while (other_p < other_p_end) {
mrb_hash_set(mrb, hash, *other_p, mrb_true_value());
other_p++;
}
while (p < p_end) {
mrb_value val = mrb_hash_get(mrb, hash, *p);
if (mrb_nil_p(val)) { /* key doesn't exist in other_ary */
mrb_ary_push(mrb, result_ary, *p);
}
p++;
}
}
else {
while (p < p_end) {
mrb_value *other_p = ARY_PTR(other_ary);
mrb_value *other_p_end = other_p + ARY_LEN(other_ary);
mrb_bool found = FALSE;
while (other_p < other_p_end) {
if (mrb_equal(mrb, *p, *other_p)) {
found = TRUE;
break;
}
other_p++;
}
if (!found) {
mrb_ary_push(mrb, result_ary, *p);
}
p++;
}
}
return result_ary;
}
void
mrb_mruby_array_ext_gem_init(mrb_state* mrb)
{
@@ -355,6 +425,7 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb)
mrb_define_method_id(mrb, a, MRB_SYM_B(compact), ary_compact_bang, MRB_ARGS_NONE());
mrb_define_method_id(mrb, a, MRB_SYM(rotate), ary_rotate, MRB_ARGS_OPT(1));
mrb_define_method_id(mrb, a, MRB_SYM_B(rotate), ary_rotate_bang, MRB_ARGS_OPT(1));
mrb_define_method_id(mrb, a, MRB_OPSYM(sub), ary_sub, MRB_ARGS_REQ(1));
}
void
+46
View File
@@ -83,6 +83,52 @@ assert("Array#-") do
assert_equal [1, 2, 3, 1], a
end
assert("Array#- with large arrays") do
# Test hash-based implementation (other_ary length > 16)
a = (1..30).to_a
b = (10..25).to_a # 16 elements, triggers hash approach
result = a - b
expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 26, 27, 28, 29, 30]
assert_equal expected, result
assert_equal 14, result.size
# Test with larger removal set
a = (1..50).to_a
b = (20..40).to_a # 21 elements > 16, triggers hash approach
result = a - b
expected = (1..19).to_a + (41..50).to_a
assert_equal expected, result
assert_equal 29, result.size
# Test removing all elements
a = (1..20).to_a
b = (1..20).to_a
result = a - b
expected = []
assert_equal expected, result
assert_equal 0, result.size
# Test removing no elements
a = (1..20).to_a
b = (30..50).to_a # 21 elements > 16, triggers hash approach
result = a - b
expected = (1..20).to_a
assert_equal expected, result
assert_equal 20, result.size
# Ensure original arrays are unchanged
original_a = (1..30).to_a
original_b = (10..25).to_a
result = original_a - original_b
assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 26, 27, 28, 29, 30], result
assert_equal (1..30).to_a, original_a
assert_equal (10..25).to_a, original_b
end
assert("Array#|") do
a = [1, 2, 3, 1]
b = [1, 4]