mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-array-ext: implement Array#& in C for better performance
Moved Array#& (set intersection) implementation from Ruby to C to improve memory usage and performance. The C implementation uses hash-based deduplication for large arrays (>16 elements) and linear search for smaller arrays, following the same hybrid pattern as Array#| and Array#-. Key improvements: - Hash-based approach uses mrb_hash_delete_key() for proper deduplication - Linear search approach checks result array to ensure uniqueness - Maintains order preservation from the first array - Eliminates temporary object creation in Ruby implementation Added comprehensive test coverage for both small and large array scenarios, including edge cases like no intersection, complete intersection, and duplicate handling. Co-authored-by: Atlassian Rovo Dev
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user