[前][次][番号順一覧][スレッド一覧]

ruby-changes:48979

From: marcandre <ko1@a...>
Date: Mon, 11 Dec 2017 07:36:34 +0900 (JST)
Subject: [ruby-changes:48979] marcandRe: r61098 (trunk): Add case equality arity to Enumerable#all?, any?, none? and one?,

marcandre	2017-12-11 07:36:28 +0900 (Mon, 11 Dec 2017)

  New Revision: 61098

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=61098

  Log:
    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]

  Modified files:
    trunk/NEWS
    trunk/array.c
    trunk/enum.c
    trunk/hash.c
    trunk/spec/ruby/core/enumerable/any_spec.rb
    trunk/test/ruby/test_enum.rb
Index: test/ruby/test_enum.rb
===================================================================
--- test/ruby/test_enum.rb	(revision 61097)
+++ test/ruby/test_enum.rb	(revision 61098)
@@ -310,6 +310,8 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L310
     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::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L321
     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?)
Index: enum.c
===================================================================
--- enum.c	(revision 61097)
+++ enum.c	(revision 61098)
@@ -1150,7 +1150,9 @@ enum_sort_by(VALUE obj) https://github.com/ruby/ruby/blob/trunk/enum.c#L1150
     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 https://github.com/ruby/ruby/blob/trunk/enum.c#L1170
 } \
 \
 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) https://github.com/ruby/ruby/blob/trunk/enum.c#L1191
 /*
  *  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) https://github.com/ruby/ruby/blob/trunk/enum.c#L1200
  *  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) https://github.com/ruby/ruby/blob/trunk/enum.c#L1232
 /*
  *  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) https://github.com/ruby/ruby/blob/trunk/enum.c#L1241
  *  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 https://github.com/ruby/ruby/blob/trunk/enum.c#L1497
 /*
  *  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 https://github.com/ruby/ruby/blob/trunk/enum.c#L1505
  *  <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) https://github.com/ruby/ruby/blob/trunk/enum.c#L1541
 /*
  *  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) https://github.com/ruby/ruby/blob/trunk/enum.c#L4002
     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);
Index: hash.c
===================================================================
--- hash.c	(revision 61097)
+++ hash.c	(revision 61098)
@@ -2983,6 +2983,17 @@ any_p_i_fast(VALUE key, VALUE value, VAL https://github.com/ruby/ruby/blob/trunk/hash.c#L2983
     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, VAL https://github.com/ruby/ruby/blob/trunk/hash.c#L3002
  */
 
 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);
+    }
+    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);
     }
-    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;
+    return args[0];
 }
 
 /*
@@ -4663,7 +4683,7 @@ Init_Hash(void) https://github.com/ruby/ruby/blob/trunk/hash.c#L4683
     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);
Index: spec/ruby/core/enumerable/any_spec.rb
===================================================================
--- spec/ruby/core/enumerable/any_spec.rb	(revision 61097)
+++ spec/ruby/core/enumerable/any_spec.rb	(revision 61098)
@@ -20,12 +20,19 @@ describe "Enumerable#any?" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/enumerable/any_spec.rb#L20
     {}.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 https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/enumerable/any_spec.rb#L145
     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
Index: NEWS
===================================================================
--- NEWS	(revision 61097)
+++ NEWS	(revision 61098)
@@ -38,6 +38,10 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L38
   * 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]
Index: array.c
===================================================================
--- array.c	(revision 61097)
+++ array.c	(revision 61098)
@@ -5771,13 +5771,19 @@ rb_ary_drop_while(VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L5771
  */
 
 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) https://github.com/ruby/ruby/blob/trunk/array.c#L6335
     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);
 

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]