diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index 8b83a0db1..28242c093 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -111,46 +111,6 @@ class Array ary end - ## - # call-seq: - # ary.intersect?(other_ary) -> true or false - # - # Returns +true+ if the array and +other_ary+ have at least one element in - # common, otherwise returns +false+. - # - # a = [ 1, 2, 3 ] - # b = [ 3, 4, 5 ] - # c = [ 5, 6, 7 ] - # a.intersect?(b) #=> true - # a.intersect?(c) #=> false - def intersect?(ary) - raise TypeError, "cannot convert #{ary.class} into Array" unless ary.class == Array - - hash = {} - if self.length > ary.length - shorter = ary - longer = self - else - shorter = self - longer = ary - end - idx = 0 - len = shorter.size - while idx < len - hash[shorter[idx]] = true - idx += 1 - end - idx = 0 - len = size - while idx < len - v = longer[idx] - if hash[v] - return true - end - idx += 1 - end - false - end ## # call-seq: diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index 70a585884..814c318e9 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -606,6 +606,92 @@ ary_intersection(mrb_state *mrb, mrb_value self) return result_ary; } +/* + * call-seq: + * ary.intersect?(other_ary) -> true or false + * + * Returns +true+ if the array and +other_ary+ have at least one element in + * common, otherwise returns +false+. + * + * a = [ 1, 2, 3 ] + * b = [ 3, 4, 5 ] + * c = [ 5, 6, 7 ] + * a.intersect?(b) #=> true + * a.intersect?(c) #=> false + */ + +static mrb_value +ary_intersect_p(mrb_state *mrb, mrb_value self) +{ + mrb_value other; + struct RArray *self_ary, *other_ary, *shorter_ary, *longer_ary; + mrb_value *shorter_p, *shorter_p_end, *longer_p, *longer_p_end; + + mrb_get_args(mrb, "A", &other); + + self_ary = mrb_ary_ptr(self); + other_ary = mrb_ary_ptr(other); + + /* Choose shorter array for hash, longer for iteration (optimization) */ + if (ARY_LEN(self_ary) > ARY_LEN(other_ary)) { + shorter_ary = other_ary; + longer_ary = self_ary; + } + else { + shorter_ary = self_ary; + longer_ary = other_ary; + } + + /* Early termination for empty arrays */ + if (ARY_LEN(shorter_ary) == 0 || ARY_LEN(longer_ary) == 0) { + return mrb_false_value(); + } + + if (ARY_LEN(shorter_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(shorter_ary)); + + /* Populate hash with elements from shorter array */ + shorter_p = ARY_PTR(shorter_ary); + shorter_p_end = shorter_p + ARY_LEN(shorter_ary); + while (shorter_p < shorter_p_end) { + mrb_hash_set(mrb, hash, *shorter_p, mrb_true_value()); + shorter_p++; + } + + /* Check elements from longer array against hash with early termination */ + longer_p = ARY_PTR(longer_ary); + longer_p_end = longer_p + ARY_LEN(longer_ary); + while (longer_p < longer_p_end) { + mrb_value val = mrb_hash_get(mrb, hash, *longer_p); + if (!mrb_nil_p(val)) { /* key exists in shorter array */ + return mrb_true_value(); /* Early termination */ + } + longer_p++; + } + } + else { + /* Use linear search for small arrays */ + longer_p = ARY_PTR(longer_ary); + longer_p_end = longer_p + ARY_LEN(longer_ary); + while (longer_p < longer_p_end) { + /* Check if element exists in shorter array */ + shorter_p = ARY_PTR(shorter_ary); + shorter_p_end = shorter_p + ARY_LEN(shorter_ary); + + while (shorter_p < shorter_p_end) { + if (mrb_equal(mrb, *longer_p, *shorter_p)) { + return mrb_true_value(); /* Early termination */ + } + shorter_p++; + } + longer_p++; + } + } + + return mrb_false_value(); +} + void mrb_mruby_array_ext_gem_init(mrb_state* mrb) { @@ -623,6 +709,7 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb) 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)); + mrb_define_method_id(mrb, a, MRB_SYM_Q(intersect), ary_intersect_p, MRB_ARGS_REQ(1)); } void diff --git a/mrbgems/mruby-array-ext/test/array.rb b/mrbgems/mruby-array-ext/test/array.rb index 3f8fe7845..b264a8007 100644 --- a/mrbgems/mruby-array-ext/test/array.rb +++ b/mrbgems/mruby-array-ext/test/array.rb @@ -264,6 +264,63 @@ assert("Array#intersect?") do assert_false(a.intersect?(c)) end +assert("Array#intersect? with large arrays") do + # Test hash-based implementation (shorter array > 16) + a = (1..30).to_a + b = (25..50).to_a # 26 elements > 16, but a is longer so b is shorter + result = a.intersect?(b) + assert_true(result) # should find intersection at 25-30 + + # Test with larger arrays, no intersection + a = (1..20).to_a + b = (30..50).to_a # 21 elements > 16, triggers hash approach + result = a.intersect?(b) + assert_false(result) # no intersection + + # Test with first element matching (early termination) + a = (1..30).to_a + b = [1] + (50..70).to_a # 22 elements > 16, first element matches + result = a.intersect?(b) + assert_true(result) # should terminate early on first element + + # Test with last element matching + a = (1..30).to_a + b = (50..70).to_a + [30] # 22 elements > 16, last element matches + result = a.intersect?(b) + assert_true(result) # should find match at the end + + # Test empty arrays + a = [] + b = (1..20).to_a + result = a.intersect?(b) + assert_false(result) # empty array intersects with nothing + + a = (1..20).to_a + b = [] + result = a.intersect?(b) + assert_false(result) # intersecting with empty array + + # Test array size optimization (shorter array used for hash) + a = (1..5).to_a # shorter + b = (3..30).to_a # longer, 28 elements > 16 + result = a.intersect?(b) + assert_true(result) # should use a (shorter) for hash, find 3,4,5 + + # Test with duplicates + a = [1, 1, 2, 2, 3, 3] * 5 # 30 elements with duplicates + b = (25..50).to_a # 26 elements > 16, no intersection + result = a.intersect?(b) + assert_false(result) + + # Ensure original arrays are unchanged + original_a = (1..30).to_a + original_b = (25..50).to_a + result = original_a.intersect?(original_b) + assert_true(result) + assert_equal (1..30).to_a, original_a + assert_equal (25..50).to_a, original_b +end + assert("Array#flatten") do assert_equal [1, 2, "3", {4=>5}, :'6'], [1, 2, "3", {4=>5}, :'6'].flatten assert_equal [1, 2, 3, 4, 5, 6], [1, 2, [3, 4, 5], 6].flatten