1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Add case equality arity to Enumerable#all?, any?, none? and one?,

and specialized Array#any? and Hash#any?
Based on patch by D.E. Akers [#11286]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@61098 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
marcandre 2017-12-10 22:36:28 +00:00
parent 86a794a6c3
commit a9770bac63
6 changed files with 198 additions and 33 deletions

4
NEWS
View file

@ -38,6 +38,10 @@ with all sufficient information, see the ChangeLog file or Redmine
* Dir.children [Feature #11302]
* Dir.each_child [Feature #11302]
* Enumerable
* Enumerable#any?, all?, none? and one? now accept a pattern argument [Feature #11286]
* File
* :newline option to File.open implies text mode now. [Bug #13350]

12
array.c
View file

@ -5771,13 +5771,19 @@ rb_ary_drop_while(VALUE ary)
*/
static VALUE
rb_ary_any_p(VALUE ary)
rb_ary_any_p(int argc, VALUE *argv, VALUE ary)
{
long i, len = RARRAY_LEN(ary);
const VALUE *ptr = RARRAY_CONST_PTR(ary);
rb_check_arity(argc, 0, 1);
if (!len) return Qfalse;
if (!rb_block_given_p()) {
if (argc) {
for (i = 0; i < RARRAY_LEN(ary); ++i) {
if (RTEST(rb_funcall(argv[0], idEqq, 1, RARRAY_AREF(ary, i)))) return Qtrue;
}
}
else if (!rb_block_given_p()) {
for (i = 0; i < len; ++i) if (RTEST(ptr[i])) return Qtrue;
}
else {
@ -6329,7 +6335,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "drop_while", rb_ary_drop_while, 0);
rb_define_method(rb_cArray, "bsearch", rb_ary_bsearch, 0);
rb_define_method(rb_cArray, "bsearch_index", rb_ary_bsearch_index, 0);
rb_define_method(rb_cArray, "any?", rb_ary_any_p, 0);
rb_define_method(rb_cArray, "any?", rb_ary_any_p, -1);
rb_define_method(rb_cArray, "dig", rb_ary_dig, -1);
rb_define_method(rb_cArray, "sum", rb_ary_sum, -1);

59
enum.c
View file

@ -1150,7 +1150,9 @@ enum_sort_by(VALUE obj)
return ary;
}
#define ENUMFUNC(name) rb_block_given_p() ? name##_iter_i : name##_i
#define ENUMFUNC(name) argc ? name##_eqq : rb_block_given_p() ? name##_iter_i : name##_i
#define MEMO_ENUM_NEW(v1) (rb_check_arity(argc, 0, 1), MEMO_NEW((v1), (argc ? *argv : 0), 0))
#define DEFINE_ENUMFUNCS(name) \
static VALUE enum_##name##_func(VALUE result, struct MEMO *memo); \
@ -1168,6 +1170,13 @@ name##_iter_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, memo)) \
} \
\
static VALUE \
name##_eqq(RB_BLOCK_CALL_FUNC_ARGLIST(i, memo)) \
{ \
ENUM_WANT_SVALUE(); \
return enum_##name##_func(rb_funcallv(MEMO_CAST(memo)->v2, id_eqq, 1, &i), MEMO_CAST(memo)); \
} \
\
static VALUE \
enum_##name##_func(VALUE result, struct MEMO *memo)
DEFINE_ENUMFUNCS(all)
@ -1182,6 +1191,7 @@ DEFINE_ENUMFUNCS(all)
/*
* call-seq:
* enum.all? [{ |obj| block } ] -> true or false
* enum.all?(pattern) -> true or false
*
* Passes each element of the collection to the given block. The method
* returns <code>true</code> if the block never returns
@ -1190,17 +1200,22 @@ DEFINE_ENUMFUNCS(all)
* cause #all? to return +true+ when none of the collection members are
* +false+ or +nil+.
*
* If instead a pattern is supplied, the method returns whether
* <code>pattern === element</code> for every element of <i>enum</i>.
*
* %w[ant bear cat].all? { |word| word.length >= 3 } #=> true
* %w[ant bear cat].all? { |word| word.length >= 4 } #=> false
* %w[ant bear cat].all?(/t/) #=> false
* [1, 2i, 3.14].all?(Numeric) #=> true
* [nil, true, 99].all? #=> false
* [].all? #=> true
*
*/
static VALUE
enum_all(VALUE obj)
enum_all(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_NEW(Qtrue, 0, 0);
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
return memo->v1;
}
@ -1217,6 +1232,7 @@ DEFINE_ENUMFUNCS(any)
/*
* call-seq:
* enum.any? [{ |obj| block }] -> true or false
* enum.any?(pattern) -> true or false
*
* Passes each element of the collection to the given block. The method
* returns <code>true</code> if the block ever returns a value other
@ -1225,17 +1241,22 @@ DEFINE_ENUMFUNCS(any)
* will cause #any? to return +true+ if at least one of the collection
* members is not +false+ or +nil+.
*
* If instead a pattern is supplied, the method returns whether
* <code>pattern === element</code> for any element of <i>enum</i>.
*
* %w[ant bear cat].any? { |word| word.length >= 3 } #=> true
* %w[ant bear cat].any? { |word| word.length >= 4 } #=> true
* %w[ant bear cat].any?(/d/) #=> false
* [nil, true, 99].any?(Integer) #=> true
* [nil, true, 99].any? #=> true
* [].any? #=> false
*
*/
static VALUE
enum_any(VALUE obj)
enum_any(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_NEW(Qfalse, 0, 0);
struct MEMO *memo = MEMO_ENUM_NEW(Qfalse);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(any), (VALUE)memo);
return memo->v1;
}
@ -1476,6 +1497,7 @@ rb_nmin_run(VALUE obj, VALUE num, int by, int rev, int ary)
/*
* call-seq:
* enum.one? [{ |obj| block }] -> true or false
* enum.one?(pattern) -> true or false
*
* Passes each element of the collection to the given block. The method
* returns <code>true</code> if the block returns <code>true</code>
@ -1483,17 +1505,22 @@ rb_nmin_run(VALUE obj, VALUE num, int by, int rev, int ary)
* <code>true</code> only if exactly one of the collection members is
* true.
*
* If instead a pattern is supplied, the method returns whether
* <code>pattern === element</code> for exactly one element of <i>enum</i>.
*
* %w{ant bear cat}.one? { |word| word.length == 4 } #=> true
* %w{ant bear cat}.one? { |word| word.length > 4 } #=> false
* %w{ant bear cat}.one? { |word| word.length < 4 } #=> false
* %w{ant bear cat}.one?(/t/) #=> false
* [ nil, true, 99 ].one? #=> false
* [ nil, true, false ].one? #=> true
* [ nil, true, 99 ].one?(Integer) #=> true
*
*/
static VALUE
enum_one(VALUE obj)
enum_one(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_NEW(Qundef, 0, 0);
struct MEMO *memo = MEMO_ENUM_NEW(Qundef);
VALUE result;
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(one), (VALUE)memo);
@ -1514,23 +1541,29 @@ DEFINE_ENUMFUNCS(none)
/*
* call-seq:
* enum.none? [{ |obj| block }] -> true or false
* enum.none?(pattern) -> true or false
*
* Passes each element of the collection to the given block. The method
* returns <code>true</code> if the block never returns <code>true</code>
* for all elements. If the block is not given, <code>none?</code> will return
* <code>true</code> only if none of the collection members is true.
*
* If instead a pattern is supplied, the method returns whether
* <code>pattern === element</code> for none of the elements of <i>enum</i>.
*
* %w{ant bear cat}.none? { |word| word.length == 5 } #=> true
* %w{ant bear cat}.none? { |word| word.length >= 4 } #=> false
* %w{ant bear cat}.none?(/d/) #=> true
* [1, 3.14, 42].none?(Float) #=> false
* [].none? #=> true
* [nil].none? #=> true
* [nil, false].none? #=> true
* [nil, false, true].none? #=> false
*/
static VALUE
enum_none(VALUE obj)
enum_none(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_NEW(Qtrue, 0, 0);
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(none), (VALUE)memo);
return memo->v1;
}
@ -3969,10 +4002,10 @@ Init_Enumerable(void)
rb_define_method(rb_mEnumerable, "partition", enum_partition, 0);
rb_define_method(rb_mEnumerable, "group_by", enum_group_by, 0);
rb_define_method(rb_mEnumerable, "first", enum_first, -1);
rb_define_method(rb_mEnumerable, "all?", enum_all, 0);
rb_define_method(rb_mEnumerable, "any?", enum_any, 0);
rb_define_method(rb_mEnumerable, "one?", enum_one, 0);
rb_define_method(rb_mEnumerable, "none?", enum_none, 0);
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, "none?", enum_none, -1);
rb_define_method(rb_mEnumerable, "min", enum_min, -1);
rb_define_method(rb_mEnumerable, "max", enum_max, -1);
rb_define_method(rb_mEnumerable, "minmax", enum_minmax, 0);

42
hash.c
View file

@ -2983,6 +2983,17 @@ any_p_i_fast(VALUE key, VALUE value, VALUE arg)
return ST_CONTINUE;
}
static int
any_p_i_pattern(VALUE key, VALUE value, VALUE arg)
{
VALUE ret = rb_funcall(((VALUE *)arg)[1], idEqq, 1, rb_assoc_new(key, value));
if (RTEST(ret)) {
*(VALUE *)arg = Qtrue;
return ST_STOP;
}
return ST_CONTINUE;
}
/*
* call-seq:
* hsh.any? [{ |(key, value)| block }] -> true or false
@ -2991,20 +3002,29 @@ any_p_i_fast(VALUE key, VALUE value, VALUE arg)
*/
static VALUE
rb_hash_any_p(VALUE hash)
rb_hash_any_p(int argc, VALUE *argv, VALUE hash)
{
VALUE ret = Qfalse;
VALUE args[2];
args[0] = Qfalse;
rb_check_arity(argc, 0, 1);
if (RHASH_EMPTY_P(hash)) return Qfalse;
if (!rb_block_given_p()) {
/* yields pairs, never false */
return Qtrue;
if (argc) {
args[1] = argv[0];
rb_hash_foreach(hash, any_p_i_pattern, (VALUE)args);
}
if (rb_block_arity() > 1)
rb_hash_foreach(hash, any_p_i_fast, (VALUE)&ret);
else
rb_hash_foreach(hash, any_p_i, (VALUE)&ret);
return ret;
else {
if (!rb_block_given_p()) {
/* yields pairs, never false */
return Qtrue;
}
if (rb_block_arity() > 1)
rb_hash_foreach(hash, any_p_i_fast, (VALUE)args);
else
rb_hash_foreach(hash, any_p_i, (VALUE)args);
}
return args[0];
}
/*
@ -4663,7 +4683,7 @@ Init_Hash(void)
rb_define_method(rb_cHash, "compare_by_identity", rb_hash_compare_by_id, 0);
rb_define_method(rb_cHash, "compare_by_identity?", rb_hash_compare_by_id_p, 0);
rb_define_method(rb_cHash, "any?", rb_hash_any_p, 0);
rb_define_method(rb_cHash, "any?", rb_hash_any_p, -1);
rb_define_method(rb_cHash, "dig", rb_hash_dig, -1);
rb_define_method(rb_cHash, "<=", rb_hash_le, 1);

View file

@ -20,12 +20,19 @@ describe "Enumerable#any?" do
{}.any? { nil }.should == false
end
it "raises an ArgumentError when any arguments provided" do
lambda { @enum.any?(Proc.new {}) }.should raise_error(ArgumentError)
lambda { @enum.any?(nil) }.should raise_error(ArgumentError)
lambda { @empty.any?(1) }.should raise_error(ArgumentError)
lambda { @enum1.any?(1) {} }.should raise_error(ArgumentError)
lambda { @enum2.any?(1, 2, 3) {} }.should raise_error(ArgumentError)
it "raises an ArgumentError when more than 1 argument is provided" do
lambda { @enum.any?(1, 2, 3) }.should raise_error(ArgumentError)
lambda { [].any?(1, 2, 3) }.should raise_error(ArgumentError)
lambda { {}.any?(1, 2, 3) }.should raise_error(ArgumentError)
end
ruby_version_is ""..."2.5" do
it "raises an ArgumentError when any arguments provided" do
lambda { @enum.any?(Proc.new {}) }.should raise_error(ArgumentError)
lambda { @enum.any?(nil) }.should raise_error(ArgumentError)
lambda { @empty.any?(1) }.should raise_error(ArgumentError)
lambda { @enum1.any?(1) {} }.should raise_error(ArgumentError)
end
end
it "does not hide exceptions out of #each" do
@ -138,4 +145,81 @@ describe "Enumerable#any?" do
end
end
ruby_version_is "2.5" do
describe 'when given a pattern argument' do
class EnumerableSpecs::Pattern
attr_reader :yielded
def initialize(&block)
@block = block
@yielded = []
end
def ===(*args)
@yielded << args
@block.call(*args)
end
end
it "calls `===` on the pattern the return value " do
pattern = EnumerableSpecs::Pattern.new { |x| x == 2 }
@enum1.any?(pattern).should == true
pattern.yielded.should == [[0], [1], [2]]
end
it "ignores block" do
@enum2.any?(NilClass) { raise }.should == true
[1, 2, nil].any?(NilClass) { raise }.should == true
{a: 1}.any?(Array) { raise }.should == true
end
it "always returns false on empty enumeration" do
@empty.any?(Integer).should == false
[].any?(Integer).should == false
{}.any?(NilClass).should == false
end
it "does not hide exceptions out of #each" do
lambda {
EnumerableSpecs::ThrowingEach.new.any?(Integer)
}.should raise_error(RuntimeError)
end
it "returns true if the pattern ever returns a truthy value" do
@enum2.any?(NilClass).should == true
pattern = EnumerableSpecs::Pattern.new { |x| 42 }
@enum.any?(pattern).should == true
[1, 42, 3].any?(pattern).should == true
pattern = EnumerableSpecs::Pattern.new { |x| x == [:b, 2] }
{a: 1, b: 2}.any?(pattern).should == true
end
it "any? should return false if the block never returns other than false or nil" do
pattern = EnumerableSpecs::Pattern.new { |x| nil }
@enum1.any?(pattern).should == false
pattern.yielded.should == [[0], [1], [2], [-1]]
[1, 2, 3].any?(pattern).should == false
{a: 1}.any?(pattern).should == false
end
it "does not hide exceptions out of the block" do
pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
lambda {
@enum.any?(pattern)
}.should raise_error(RuntimeError)
end
it "calls the pattern with gathered array when yielded with multiple arguments" do
pattern = EnumerableSpecs::Pattern.new { false }
EnumerableSpecs::YieldsMixed2.new.any?(pattern).should == false
pattern.yielded.should == EnumerableSpecs::YieldsMixed2.gathered_yields.map { |x| [x] }
pattern = EnumerableSpecs::Pattern.new { false }
{a: 1, b: 2}.any?(pattern).should == false
pattern.yielded.should == [[[:a, 1]], [[:b, 2]]]
end
end
end
end

View file

@ -310,6 +310,8 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(false, [true, true, false].all?)
assert_equal(true, [].all?)
assert_equal(true, @empty.all?)
assert_equal(true, @obj.all?(Fixnum))
assert_equal(false, @obj.all?(1..2))
end
def test_any
@ -319,27 +321,43 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(false, [false, false, false].any?)
assert_equal(false, [].any?)
assert_equal(false, @empty.any?)
assert_equal(true, @obj.any?(1..2))
assert_equal(false, @obj.any?(Float))
assert_equal(false, [1, 42].any?(Float))
assert_equal(true, [1, 4.2].any?(Float))
assert_equal(false, {a: 1, b: 2}.any?(->(kv) { kv == [:foo, 42] }))
assert_equal(true, {a: 1, b: 2}.any?(->(kv) { kv == [:b, 2] }))
end
def test_one
assert(@obj.one? {|x| x == 3 })
assert(!(@obj.one? {|x| x == 1 }))
assert(!(@obj.one? {|x| x == 4 }))
assert(@obj.one?(3..4))
assert(!(@obj.one?(1..2)))
assert(!(@obj.one?(4..5)))
assert(%w{ant bear cat}.one? {|word| word.length == 4})
assert(!(%w{ant bear cat}.one? {|word| word.length > 4}))
assert(!(%w{ant bear cat}.one? {|word| word.length < 4}))
assert(%w{ant bear cat}.one?(/b/))
assert(!(%w{ant bear cat}.one?(/t/)))
assert(!([ nil, true, 99 ].one?))
assert([ nil, true, false ].one?)
assert(![].one?)
assert(!@empty.one?)
assert([ nil, true, 99 ].one?(Integer))
end
def test_none
assert(@obj.none? {|x| x == 4 })
assert(!(@obj.none? {|x| x == 1 }))
assert(!(@obj.none? {|x| x == 3 }))
assert(@obj.none?(4..5))
assert(!(@obj.none?(1..3)))
assert(%w{ant bear cat}.none? {|word| word.length == 5})
assert(!(%w{ant bear cat}.none? {|word| word.length >= 4}))
assert(%w{ant bear cat}.none?(/d/))
assert(!(%w{ant bear cat}.none?(/b/)))
assert([].none?)
assert([nil].none?)
assert([nil,false].none?)