From 2ae1160b3966a65abf74b92eab48e3e0b5d77445 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 25 Dec 2025 16:17:21 +0900 Subject: [PATCH] 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 --- mrbgems/mruby-array-ext/mrblib/array.rb | 55 +++++++++++++++++++++ mrbgems/mruby-array-ext/test/array.rb | 65 +++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index ea8de8a81..de3bb0a79 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -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 diff --git a/mrbgems/mruby-array-ext/test/array.rb b/mrbgems/mruby-array-ext/test/array.rb index 91a7cdabf..fc175b863 100644 --- a/mrbgems/mruby-array-ext/test/array.rb +++ b/mrbgems/mruby-array-ext/test/array.rb @@ -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