diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2016-02-17 17:42:09 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2016-04-12 22:17:33 +0900 |
commit | 80b537f14c4af699b26e52931ae7e64a547e68c5 (patch) | |
tree | 17dd1e7babde4c90f27a14988a36bd96b6557c28 /enum.c | |
parent | 456523e2ede3073767fd8cb73cc4b159c3608890 (diff) | |
download | ruby-80b537f14c4af699b26e52931ae7e64a547e68c5.tar.gz |
{Enumerable,Array,Range}#first, {Array,Range}#last with blockfeature/enumerable-first-with-block
* array.c (rb_ary_first, ary_last): extend Array#{first,last} to accept
a block. If a block is passed, these methods collects only elements
for which the block returns a truthy value.
* enum.c: extend Enumerable#first to accept a block.
* range.c: extend Range#{first,last} to accept a block.
* gc.c: avoid using rb_ary_last(), because it may call a block.
* test/ruby/test_array.rb: add test
* test/ruby/test_enum.rb: ditto
* test/ruby/test_range.rb: ditto
Diffstat (limited to 'enum.c')
-rw-r--r-- | enum.c | 47 |
1 files changed, 37 insertions, 10 deletions
@@ -909,22 +909,40 @@ first_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, params)) UNREACHABLE; } +static VALUE +take_find_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, params)) +{ + struct MEMO *memo = MEMO_CAST(params); + ENUM_WANT_SVALUE(); + if (RTEST(rb_yield(i))) { + rb_ary_push(memo->v1, i); + if (!--memo->u3.cnt) rb_iter_break(); + } + return Qnil; +} + static VALUE enum_take(VALUE obj, VALUE n); /* * call-seq: - * enum.first -> obj or nil - * enum.first(n) -> an_array + * enum.first -> obj or nil + * enum.first { |obj| block } -> obj or nil + * enum.first(n) -> an_array + * enum.first(n) { |obj| block } -> an_array * * Returns the first element, or the first +n+ elements, of the enumerable. * If the enumerable is empty, the first form returns <code>nil</code>, and the * second form returns an empty array. + * If a block is given, only elements for which the given block returns a true + * value are counted. * - * %w[foo bar baz].first #=> "foo" - * %w[foo bar baz].first(2) #=> ["foo", "bar"] - * %w[foo bar baz].first(10) #=> ["foo", "bar", "baz"] - * [].first #=> nil - * [].first(10) #=> [] + * %w[foo bar baz].first #=> "foo" + * %w[foo bar baz].first(2) #=> ["foo", "bar"] + * %w[foo bar baz].first(10) #=> ["foo", "bar", "baz"] + * [].first #=> nil + * [].first(10) #=> [] + * [1,2,3,4].first { |i| i.even? } #=> 2 + * [1,2,2,2].first(2) { |i| i > 1 } #=> [2, 2] * */ @@ -932,18 +950,27 @@ static VALUE enum_first(int argc, VALUE *argv, VALUE obj) { struct MEMO *memo; + long len; rb_check_arity(argc, 0, 1); + if (argc > 0) { - return enum_take(obj, argv[0]); + if (!rb_block_given_p()) return enum_take(obj, argv[0]); + + len = NUM2LONG(argv[0]); + if (len < 0) rb_raise(rb_eArgError, "attempt to take negative size"); + if (len == 0) return rb_ary_new2(0); + + memo = MEMO_NEW(rb_ary_new(), 0, len); + rb_block_call(obj, id_each, 0, 0, take_find_i, (VALUE)memo); + return memo->v1; } else { memo = MEMO_NEW(Qnil, 0, 0); - rb_block_call(obj, id_each, 0, 0, first_i, (VALUE)memo); + rb_block_call(obj, id_each, 0, 0, rb_block_given_p() ? find_i : first_i, (VALUE)memo); return memo->v1; } } - /* * call-seq: * enum.sort -> array |