From 409f39e911e67a28be3a942fce243863e4104edd Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Fri, 27 Jun 2025 22:08:23 +0900 Subject: [PATCH] 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 --- mrbgems/mruby-array-ext/mrblib/array.rb | 32 ----------- mrbgems/mruby-array-ext/src/array.c | 71 +++++++++++++++++++++++++ mrbgems/mruby-array-ext/test/array.rb | 46 ++++++++++++++++ 3 files changed, 117 insertions(+), 32 deletions(-) diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index 5c5d47fff..588ac17a8 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -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 - # other_ary. (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 diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index 1f5dda2d4..808e4d6be 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -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 diff --git a/mrbgems/mruby-array-ext/test/array.rb b/mrbgems/mruby-array-ext/test/array.rb index fb0687521..f19b90278 100644 --- a/mrbgems/mruby-array-ext/test/array.rb +++ b/mrbgems/mruby-array-ext/test/array.rb @@ -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]