diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index fe881367e..8b83a0db1 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -92,38 +92,6 @@ class Array ary end - ## - # call-seq: - # ary & other_ary -> new_ary - # - # Set Intersection---Returns a new array - # containing elements common to the two arrays, with no duplicates. - # - # [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ] - # - def &(elem) - raise TypeError, "cannot 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] - if hash[v] - array << v - hash.delete v - end - idx += 1 - end - array - end ## # call-seq: diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index e03dd7ab4..70a585884 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -516,6 +516,96 @@ ary_union(mrb_state *mrb, mrb_value self) return result_ary; } +/* + * call-seq: + * ary & other_ary -> new_ary + * + * Set Intersection---Returns a new array + * containing elements common to the two arrays, with no duplicates. + * + * [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ] + */ + +static mrb_value +ary_intersection(mrb_state *mrb, mrb_value self) +{ + mrb_value other, result_ary; + struct RArray *self_ary, *other_ary; + mrb_value *p, *p_end, *other_p, *other_p_end; + + mrb_get_args(mrb, "A", &other); + + self_ary = mrb_ary_ptr(self); + other_ary = mrb_ary_ptr(other); + + result_ary = mrb_ary_new(mrb); + + if (ARY_LEN(other_ary) > SET_OP_HASH_THRESHOLD) { + /* Use hash for large arrays to achieve O(n) performance */ + mrb_value hash = mrb_hash_new_capa(mrb, ARY_LEN(other_ary)); + + /* Populate hash with elements from other_ary */ + other_p = ARY_PTR(other_ary); + 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++; + } + + /* Check elements from self against hash */ + p = ARY_PTR(self_ary); + p_end = p + ARY_LEN(self_ary); + while (p < p_end) { + mrb_value val = mrb_hash_get(mrb, hash, *p); + if (!mrb_nil_p(val)) { /* key exists in other_ary */ + mrb_ary_push(mrb, result_ary, *p); + mrb_hash_delete_key(mrb, hash, *p); /* remove to ensure uniqueness */ + } + p++; + } + } + else { + /* Use linear search for small arrays */ + p = ARY_PTR(self_ary); + p_end = p + ARY_LEN(self_ary); + while (p < p_end) { + /* Check if element exists in other_ary */ + other_p = ARY_PTR(other_ary); + 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) { + /* Check if already in result to ensure uniqueness */ + mrb_int result_len = RARRAY_LEN(result_ary); + mrb_value *result_ptr = RARRAY_PTR(result_ary); + mrb_bool already_added = FALSE; + + for (mrb_int i = 0; i < result_len; i++) { + if (mrb_equal(mrb, *p, result_ptr[i])) { + already_added = TRUE; + break; + } + } + + if (!already_added) { + mrb_ary_push(mrb, result_ary, *p); + } + } + p++; + } + } + + return result_ary; +} + void mrb_mruby_array_ext_gem_init(mrb_state* mrb) { @@ -532,6 +622,7 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb) 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)); mrb_define_method_id(mrb, a, MRB_OPSYM(or), ary_union, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, a, MRB_OPSYM(and), ary_intersection, MRB_ARGS_REQ(1)); } void diff --git a/mrbgems/mruby-array-ext/test/array.rb b/mrbgems/mruby-array-ext/test/array.rb index 706b55712..3f8fe7845 100644 --- a/mrbgems/mruby-array-ext/test/array.rb +++ b/mrbgems/mruby-array-ext/test/array.rb @@ -193,6 +193,61 @@ 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 = (10..25).to_a + + assert_equal expected, result + assert_equal 16, result.size + + # Test with larger intersection set + a = (1..50).to_a + b = (20..40).to_a # 21 elements > 16, triggers hash approach + result = a & b + expected = (20..40).to_a + + assert_equal expected, result + assert_equal 21, result.size + + # Test with duplicates in first array + a = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10] + b = (5..25).to_a # 21 elements > 16, triggers hash approach + result = a & b + expected = [5, 6, 7, 8, 9, 10] # no duplicates in result + + assert_equal expected, result + assert_equal 6, result.size + + # Test no intersection + a = (1..20).to_a + b = (30..50).to_a # 21 elements > 16, triggers hash approach + result = a & b + expected = [] + + assert_equal expected, result + assert_equal 0, result.size + + # Test complete intersection + a = (1..20).to_a + b = (1..20).to_a + 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 (10..25).to_a, result + assert_equal (1..30).to_a, original_a + assert_equal (10..25).to_a, original_b +end + assert("Array#intersection") do a = [1, 2, 3, 1, 8, 6, 7, 8] b = [1, 4, 6, 8]