This commit is contained in:
OKURA Masafumi 2025-08-14 18:21:47 +09:00 committed by GitHub
commit 1416dff6f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 218 additions and 0 deletions

53
enum.c
View file

@ -2216,6 +2216,58 @@ enum_one(int argc, VALUE *argv, VALUE obj)
return result;
}
DEFINE_ENUMFUNCS(many)
{
if (RTEST(result)) {
if (memo->v1 == Qundef) {
MEMO_V1_SET(memo, Qfalse);
}
else if (memo->v1 == Qfalse) {
MEMO_V1_SET(memo, Qtrue);
rb_iter_break();
}
}
return Qnil;
}
/*
* call-seq:
* enum.many? [{ |obj| block }] -> true or false
* enum.many?(pattern) -> true or false
*
* Passes each element of the collection to the given block. The method
* returns <code>true</code> if the block returns truthy value
* more than once. If the block is not given, <code>many?</code>
* will return <code>true</code> only if more than one of the
* collection members are truthy.
*
* If instead a pattern is supplied, the method returns whether
* <code>pattern === element</code> for more than one of the
* collection member.
*
* %w{ant bear cat}.many? { |word| word.length == 4 } #=> false
* %w{ant bear cat}.many? { |word| word.length > 4 } #=> false
* %w{ant bear cat}.many? { |word| word.length < 4 } #=> true
* %w{ant bear cat}.many?(/t/) #=> true
* [ nil, true, 99 ].many? #=> true
* [ nil, true, false ].many? #=> false
* [ nil, 42, 99 ].many?(Integer) #=> true
* [].many? #=> false
*
*/
static VALUE
enum_many(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qundef);
VALUE result;
WARN_UNUSED_BLOCK(argc);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(many), (VALUE)memo);
result = memo->v1;
if (result == Qundef) return Qfalse;
return result;
}
DEFINE_ENUMFUNCS(none)
{
if (RTEST(result)) {
@ -5233,6 +5285,7 @@ Init_Enumerable(void)
rb_define_method(rb_mEnumerable, "all?", enum_all, -1);
rb_define_method(rb_mEnumerable, "any?", enum_any, -1);
rb_define_method(rb_mEnumerable, "one?", enum_one, -1);
rb_define_method(rb_mEnumerable, "many?", enum_many, -1);
rb_define_method(rb_mEnumerable, "none?", enum_none, -1);
rb_define_method(rb_mEnumerable, "min", enum_min, -1);
rb_define_method(rb_mEnumerable, "max", enum_max, -1);

View file

@ -0,0 +1,165 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
ruby_version_is "3.0" do
describe "Enumerable#many?" do
before :each do
@empty = EnumerableSpecs::Empty.new
@enum = EnumerableSpecs::Numerous.new
@enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
@enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
end
it "always returns false on empty enumeration" do
@empty.should_not.many?
@empty.many? { true }.should == false
end
it "raises an ArgumentError when more than 1 argument is provided" do
-> { @enum.many?(1, 2, 3) }.should raise_error(ArgumentError)
-> { [].many?(1, 2, 3) }.should raise_error(ArgumentError)
-> { {}.many?(1, 2, 3) }.should raise_error(ArgumentError)
end
it "does not hide exceptions out of #each" do
-> {
EnumerableSpecs::ThrowingEach.new.many?
}.should raise_error(RuntimeError)
-> {
EnumerableSpecs::ThrowingEach.new.many? { false }
}.should raise_error(RuntimeError)
end
describe "with no block" do
it "returns false if only one element evaluates to true" do
[false, nil, true].many?.should be_false
end
it "returns true if two elements evaluate to true" do
[false, :value, nil, true].many?.should be_true
end
it "returns false if all elements evaluate to false" do
[false, nil, false].many?.should be_false
end
it "gathers whole arrays as elements when each yields multiple" do
multi = EnumerableSpecs::YieldsMultiWithSingleTrue.new
multi.many?.should be_true
end
end
describe "with a block" do
it "returns false if block returns true once" do
[:a, :b, :c].many? { |s| s == :a }.should be_false
end
it "returns true if the block returns true more than once" do
[:a, :b, :c].many? { |s| s == :a || s == :b }.should be_true
end
it "returns false if the block only returns false" do
[:a, :b, :c].many? { |s| s == :d }.should be_false
end
it "does not hide exceptions out of the block" do
-> {
@enum.many? { raise "from block" }
}.should raise_error(RuntimeError)
end
it "gathers initial args as elements when each yields multiple" do
multi = EnumerableSpecs::YieldsMulti.new
yielded = []
multi.many? { |e| yielded << e; false }.should be_false
yielded.should == [1, 3, 6]
end
it "yields multiple arguments when each yields multiple" do
multi = EnumerableSpecs::YieldsMulti.new
yielded = []
multi.many? { |*args| yielded << args; false }.should be_false
yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
end
it "calls blocks until the second truthy return value" do
called = []
[1, 2, 3].many? {|i| called << i ; i }
called.should == [1, 2]
end
end
describe 'when given a pattern argument' do
it "calls `===` on the pattern the return value " do
pattern = EnumerableSpecs::Pattern.new { |x| x == 1 }
@enum1.many?(pattern).should be_false
pattern.yielded.should == [[0], [1], [2], [-1]]
end
# may raise an exception in future versions
ruby_version_is ""..."2.6" do
it "ignores block" do
@enum2.many?(NilClass) { raise }.should be_false
[1, 2, nil].many?(Integer) { raise }.should be_true
{a: 1, b: 2}.many?(Array) { raise }.should be_true
end
end
it "always returns false on empty enumeration" do
@empty.many?(Integer).should be_false
[].many?(Integer).should be_false
{}.many?(NilClass).should be_false
end
it "does not hide exceptions out of #each" do
-> {
EnumerableSpecs::ThrowingEach.new.many?(Integer)
}.should raise_error(RuntimeError)
end
it "returns false if the pattern returns a truthy value only once" do
@enum2.many?(NilClass).should be_false
pattern = EnumerableSpecs::Pattern.new { |x| x == 2 }
@enum1.many?(pattern).should be_false
[1, 2, 42, 3].many?(pattern).should be_false
pattern = EnumerableSpecs::Pattern.new { |x| x == [:b, 2] }
{a: 1, b: 2}.many?(pattern).should be_false
end
it "returns true if the pattern returns a truthy value more than once" do
pattern = EnumerableSpecs::Pattern.new { |x| !x }
@enum2.many?(pattern).should be_true
pattern.yielded.should == [[nil], [false]]
[1, 2, 3].many?(Integer).should be_true
{a: 1, b: 2}.many?(Array).should be_true
end
it "returns false if the pattern never returns a truthy value" do
pattern = EnumerableSpecs::Pattern.new { |x| nil }
@enum1.many?(pattern).should be_false
pattern.yielded.should == [[0], [1], [2], [-1]]
[1, 2, 3].many?(pattern).should be_false
{a: 1}.many?(pattern).should be_false
end
it "does not hide exceptions out of pattern#===" do
pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
-> {
@enum.many?(pattern)
}.should raise_error(RuntimeError)
end
it "calls the pattern with gathered array when yielded with multiple arguments" do
multi = EnumerableSpecs::YieldsMulti.new
pattern = EnumerableSpecs::Pattern.new { false }
multi.many?(pattern).should be_false
pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
end
end
end
end