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:
parent
86a794a6c3
commit
a9770bac63
6 changed files with 198 additions and 33 deletions
4
NEWS
4
NEWS
|
@ -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
12
array.c
|
@ -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
59
enum.c
|
@ -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
42
hash.c
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?)
|
||||
|
|
Loading…
Reference in a new issue