mruby-array-ext: add Array#find and Array#rfind

Array#find is an optimized version of Enumerable#find for arrays,
using direct index access instead of each iterator.

Array#rfind finds from the end of the array, returning the first
match when scanning backwards.

Both methods support the ifnone parameter for default values.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-12-25 16:17:21 +09:00
parent b841bd7439
commit 2ae1160b39
2 changed files with 120 additions and 0 deletions
+55
View File
@@ -726,4 +726,59 @@ class Array
self
end
##
# call-seq:
# ary.find(ifnone = nil) { |elem| block } -> obj or nil
# ary.find(ifnone = nil) -> Enumerator
#
# Returns the first element for which the block returns a true value.
# If no element matches and +ifnone+ is given, calls +ifnone+ and
# returns its result. Otherwise returns +nil+.
#
# This is an optimized version of Enumerable#find for arrays.
#
# [1, 2, 3, 4].find { |x| x > 2 } #=> 3
# [1, 2, 3, 4].find { |x| x > 10 } #=> nil
# [1, 2, 3, 4].find(->{0}) { |x| x > 10 } #=> 0
#
def find(ifnone=nil, &block)
return to_enum(:find, ifnone) unless block
idx = 0
len = self.size
while idx < len
elem = self[idx]
return elem if block.call(elem)
idx += 1
end
ifnone&.call
end
##
# call-seq:
# ary.rfind(ifnone = nil) { |elem| block } -> obj or nil
# ary.rfind(ifnone = nil) -> Enumerator
#
# Returns the last element for which the block returns a true value.
# Searches from the end of the array to the beginning.
# If no element matches and +ifnone+ is given, calls +ifnone+ and
# returns its result. Otherwise returns +nil+.
#
# [1, 2, 3, 4, 3].rfind { |x| x == 3 } #=> 3 (the last one)
# [1, 2, 3, 4].rfind { |x| x > 2 } #=> 4
# [1, 2, 3, 4].rfind { |x| x > 10 } #=> nil
# [1, 2, 3, 4].rfind(->{0}) { |x| x > 10 } #=> 0
#
def rfind(ifnone=nil, &block)
return to_enum(:rfind, ifnone) unless block
idx = self.size - 1
while idx >= 0
elem = self[idx]
return elem if block.call(elem)
idx -= 1
end
ifnone&.call
end
end
+65
View File
@@ -782,3 +782,68 @@ assert("Array#deconstruct") do
assert_equal([[1, 2], [3, 4], [5]], result_nested)
assert_true(result_nested.equal?(d))
end
assert("Array#find") do
# Basic find
assert_equal 3, [1, 2, 3, 4, 5].find { |x| x > 2 }
assert_equal 1, [1, 2, 3, 4, 5].find { |x| x < 2 }
# No match returns nil
assert_nil [1, 2, 3].find { |x| x > 10 }
# Empty array
assert_nil [].find { |x| x > 0 }
# With ifnone callable
assert_equal 0, [1, 2, 3].find(->{ 0 }) { |x| x > 10 }
assert_equal "default", [1, 2, 3].find(->{ "default" }) { |x| x > 10 }
# ifnone not called when match found
called = false
[1, 2, 3].find(->{ called = true; 0 }) { |x| x == 2 }
assert_false called
# Returns first match
assert_equal 2, [1, 2, 2, 3].find { |x| x == 2 }
# Works with different types
assert_equal "b", ["a", "b", "c"].find { |x| x == "b" }
assert_equal :bar, [:foo, :bar, :baz].find { |x| x == :bar }
end
assert("Array#rfind") do
# Basic rfind - finds from end (first match scanning backwards)
assert_equal 5, [1, 2, 3, 4, 5].rfind { |x| x > 2 } # 5 is first match from end
assert_equal 5, [1, 2, 3, 4, 5].rfind { |x| x > 0 } # 5 is first match from end
# Returns last occurrence when duplicates exist
a = [1, 2, 3, 2, 1]
assert_equal 2, a.rfind { |x| x == 2 } # finds the 2 at index 3
# No match returns nil
assert_nil [1, 2, 3].rfind { |x| x > 10 }
# Empty array
assert_nil [].rfind { |x| x > 0 }
# With ifnone callable
assert_equal 0, [1, 2, 3].rfind(->{ 0 }) { |x| x > 10 }
assert_equal "default", [1, 2, 3].rfind(->{ "default" }) { |x| x > 10 }
# ifnone not called when match found
called = false
[1, 2, 3].rfind(->{ called = true; 0 }) { |x| x == 2 }
assert_false called
# Compare find vs rfind - same result for unique match
arr = [1, 2, 3, 4, 3, 2, 1]
assert_equal 3, arr.find { |x| x == 3 } # first 3 (index 2)
assert_equal 3, arr.rfind { |x| x == 3 } # last 3 (index 4), same value
# Different results with inequality - rfind scans from end
assert_equal 3, arr.find { |x| x >= 3 } # first >= 3 is 3 (at index 2)
assert_equal 3, arr.rfind { |x| x >= 3 } # scanning from end: 1,2,3 - 3 matches first
# Works with different types
assert_equal "b", ["a", "b", "c", "b", "a"].rfind { |x| x > "a" } # scanning from end: a,b - b matches
end