mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-array-ext: implement Array#intersect? in C for better performance
Moved Array#intersect? implementation from Ruby to C to improve memory usage and performance with early termination optimization. The C implementation uses hash-based lookup for large arrays (>16 elements) and linear search for smaller arrays. Added comprehensive test coverage including early termination scenarios, empty arrays, size optimization verification, and edge cases with duplicates and large arrays. Co-authored-by: Atlassian Rovo Dev
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user