Array#rfind

Implement Array#rfind, which is the same as find except from the
other side of the Array. Also implemented Array#find (as opposed to
the generic one on Enumerable because it is significantly faster
and to keep the implementations together.

[Feature #21678]
This commit is contained in:
Kevin Newton 2025-11-14 11:31:23 -05:00
parent 4f900e3ce9
commit 6147b69587
Notes: git 2025-12-12 18:35:56 +00:00
2 changed files with 112 additions and 0 deletions

95
array.c
View File

@ -2088,6 +2088,99 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary)
return RARRAY_AREF(ary, idx);
}
/*
* call-seq:
* find(if_none_proc = nil) {|element| ... } -> object or nil
* find(if_none_proc = nil) -> enumerator
*
* Returns the first element for which the block returns a truthy value.
*
* With a block given, calls the block with successive elements of the array;
* returns the first element for which the block returns a truthy value:
*
* (0..9).find {|element| element > 2} # => 3
*
* If no such element is found, calls +if_none_proc+ and returns its return value.
*
* (0..9).find(proc {false}) {|element| element > 12} # => false
* {foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1]
* {foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => []
*
* With no block given, returns an Enumerator.
*
*/
static VALUE
rb_ary_find(int argc, VALUE *argv, VALUE ary)
{
VALUE if_none;
long idx;
RETURN_ENUMERATOR(ary, argc, argv);
if_none = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil;
for (idx = 0; idx < RARRAY_LEN(ary); idx++) {
VALUE elem = RARRAY_AREF(ary, idx);
if (RTEST(rb_yield(elem))) {
return elem;
}
}
if (!NIL_P(if_none)) {
return rb_funcallv(if_none, idCall, 0, 0);
}
return Qnil;
}
/*
* call-seq:
* rfind(if_none_proc = nil) {|element| ... } -> object or nil
* rfind(if_none_proc = nil) -> enumerator
*
* Returns the last element for which the block returns a truthy value.
*
* With a block given, calls the block with successive elements of the array in
* reverse order; returns the last element for which the block returns a truthy
* value:
*
* (0..9).rfind {|element| element < 5} # => 4
*
* If no such element is found, calls +if_none_proc+ and returns its return value.
*
* (0..9).rfind(proc {false}) {|element| element < -2} # => false
* {foo: 0, bar: 1, baz: 2}.rfind {|key, value| key.start_with?('b') } # => [:baz, 2]
* {foo: 0, bar: 1, baz: 2}.rfind(proc {[]}) {|key, value| key.start_with?('c') } # => []
*
* With no block given, returns an Enumerator.
*
*/
static VALUE
rb_ary_rfind(int argc, VALUE *argv, VALUE ary)
{
VALUE if_none;
long len, idx;
RETURN_ENUMERATOR(ary, argc, argv);
if_none = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil;
idx = RARRAY_LEN(ary);
while (idx--) {
VALUE elem = RARRAY_AREF(ary, idx);
if (RTEST(rb_yield(elem))) {
return elem;
}
len = RARRAY_LEN(ary);
idx = (idx >= len) ? len : idx;
}
if (!NIL_P(if_none)) {
return rb_funcallv(if_none, idCall, 0, 0);
}
return Qnil;
}
/*
* call-seq:
* find_index(object) -> integer or nil
@ -8816,6 +8909,8 @@ Init_Array(void)
rb_define_method(rb_cArray, "length", rb_ary_length, 0);
rb_define_method(rb_cArray, "size", rb_ary_length, 0);
rb_define_method(rb_cArray, "empty?", rb_ary_empty_p, 0);
rb_define_method(rb_cArray, "find", rb_ary_find, -1);
rb_define_method(rb_cArray, "rfind", rb_ary_rfind, -1);
rb_define_method(rb_cArray, "find_index", rb_ary_index, -1);
rb_define_method(rb_cArray, "index", rb_ary_index, -1);
rb_define_method(rb_cArray, "rindex", rb_ary_rindex, -1);

View File

@ -3584,6 +3584,23 @@ class TestArray < Test::Unit::TestCase
assert_equal((1..67).to_a.reverse, var_0)
end
def test_find
ary = [1, 2, 3, 4, 5]
assert_equal(2, ary.find {|x| x % 2 == 0 })
assert_equal(nil, ary.find {|x| false })
assert_equal(:foo, ary.find(proc { :foo }) {|x| false })
end
def test_rfind
ary = [1, 2, 3, 4, 5]
assert_equal(4, ary.rfind {|x| x % 2 == 0 })
assert_equal(1, ary.rfind {|x| x < 2 })
assert_equal(5, ary.rfind {|x| x > 4 })
assert_equal(nil, ary.rfind {|x| false })
assert_equal(:foo, ary.rfind(proc { :foo }) {|x| false })
assert_equal(nil, ary.rfind {|x| ary.clear; false })
end
private
def need_continuation
unless respond_to?(:callcc, true)