summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormarcandre <marcandre@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-12-10 22:36:28 +0000
committermarcandre <marcandre@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-12-10 22:36:28 +0000
commita9770bac6375c9d4ff8ba8bb1842841aec7c59e3 (patch)
tree94c9212fae50488fa144697a6715be89576d74ad
parent86a794a6c34c9412f497907736b4857739b7af3c (diff)
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
-rw-r--r--NEWS4
-rw-r--r--array.c12
-rw-r--r--enum.c59
-rw-r--r--hash.c42
-rw-r--r--spec/ruby/core/enumerable/any_spec.rb96
-rw-r--r--test/ruby/test_enum.rb18
6 files changed, 198 insertions, 33 deletions
diff --git a/NEWS b/NEWS
index 20a35c93ad..96ea8f83a9 100644
--- a/NEWS
+++ b/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]
diff --git a/array.c b/array.c
index 425e28aa69..b533e81055 100644
--- a/array.c
+++ b/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);
diff --git a/enum.c b/enum.c
index 086e3aa13c..aaf4e33386 100644
--- a/enum.c
+++ b/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);
diff --git a/hash.c b/hash.c
index cd1cc1437f..627f207d8e 100644
--- a/hash.c
+++ b/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);
diff --git a/spec/ruby/core/enumerable/any_spec.rb b/spec/ruby/core/enumerable/any_spec.rb
index 4a7511f649..edf7e36519 100644
--- a/spec/ruby/core/enumerable/any_spec.rb
+++ b/spec/ruby/core/enumerable/any_spec.rb
@@ -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
diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb
index ae119c73bf..12fc2ee66a 100644
--- a/test/ruby/test_enum.rb
+++ b/test/ruby/test_enum.rb
@@ -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?)