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

ruby-changes:32879

From: akr <ko1@a...>
Date: Sat, 15 Feb 2014 00:45:23 +0900 (JST)
Subject: [ruby-changes:32879] akr:r44958 (trunk): * enum.c: Enumerable#{min, min_by, max, max_by} extended to take an

akr	2014-02-15 00:45:11 +0900 (Sat, 15 Feb 2014)

  New Revision: 44958

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=44958

  Log:
    * enum.c: Enumerable#{min,min_by,max,max_by} extended to take an
      optional argument.
      (nmin_cmp): New function.
      (nmin_block_cmp): Ditto
      (nmin_filter): Ditto.
      (nmin_i): Ditto.
      (nmin_run): Ditto.
      (enum_min): Call nmin_run if the optional argument is given.
      (nmin_max): Ditto.
      (nmin_min_by): Ditto.
      (nmin_max_by): Ditto.
    
    * range.c: Range#{min,max} extended to take an optional argument.
      (range_min): Call range_first if the optional argument is given.
      (range_max): Call rb_call_super if the optional argument is given.
    
    [ruby-core:57111] [Feature #8887]

  Modified files:
    trunk/ChangeLog
    trunk/NEWS
    trunk/enum.c
    trunk/range.c
    trunk/test/ruby/test_enum.rb
    trunk/test/ruby/test_range.rb
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 44957)
+++ ChangeLog	(revision 44958)
@@ -1,3 +1,23 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Sat Feb 15 00:38:54 2014  Tanaka Akira  <akr@f...>
+
+	* enum.c: Enumerable#{min,min_by,max,max_by} extended to take an
+	  optional argument.
+	  (nmin_cmp): New function.
+	  (nmin_block_cmp): Ditto
+	  (nmin_filter): Ditto.
+	  (nmin_i): Ditto.
+	  (nmin_run): Ditto.
+	  (enum_min): Call nmin_run if the optional argument is given.
+	  (nmin_max): Ditto.
+	  (nmin_min_by): Ditto.
+	  (nmin_max_by): Ditto.
+
+	* range.c: Range#{min,max} extended to take an optional argument.
+	  (range_min): Call range_first if the optional argument is given.
+	  (range_max): Call rb_call_super if the optional argument is given.
+
+	[ruby-core:57111] [Feature #8887]
+
 Sat Feb 15 00:27:46 2014  Tanaka Akira  <akr@f...>
 
 	* include/ruby/ruby.h,
Index: enum.c
===================================================================
--- enum.c	(revision 44957)
+++ enum.c	(revision 44958)
@@ -1123,6 +1123,190 @@ DEFINE_ENUMFUNCS(one) https://github.com/ruby/ruby/blob/trunk/enum.c#L1123
  *
  */
 
+struct nmin_data {
+  long n;
+  long bufmax;
+  long curlen;
+  VALUE buf;
+  int (*cmpfunc)(const void *, const void *, void *);
+  int rev; /* max if 1 */
+  int by; /* min_by if 1 */
+  const char *method;
+};
+
+static int
+nmin_cmp(const void *ap, const void *bp, void *_data)
+{
+    struct nmin_data *data = (struct nmin_data *)_data;
+    VALUE a = *(const VALUE *)ap, b = *(const VALUE *)bp;
+    VALUE cmp = rb_funcall(a, id_cmp, 1, b);
+    if (RBASIC(data->buf)->klass) {
+	rb_raise(rb_eRuntimeError, "%s reentered", data->method);
+    }
+    return rb_cmpint(cmp, a, b);
+}
+
+static int
+nmin_block_cmp(const void *ap, const void *bp, void *_data)
+{
+    struct nmin_data *data = (struct nmin_data *)_data;
+    VALUE a = *(const VALUE *)ap, b = *(const VALUE *)bp;
+    VALUE cmp = rb_yield_values(2, a, b);
+    if (RBASIC(data->buf)->klass) {
+	rb_raise(rb_eRuntimeError, "%s reentered", data->method);
+    }
+    return rb_cmpint(cmp, a, b);
+}
+
+
+static void
+nmin_filter(struct nmin_data *data)
+{
+    long n;
+    VALUE *beg;
+    int eltsize;
+    long numelts;
+
+    long left, right;
+
+    long i, j;
+
+    if (data->curlen <= data->n)
+	return;
+
+    n = data->n;
+    beg = RARRAY_PTR(data->buf);
+    eltsize = data->by ? 2 : 1;
+    numelts = data->curlen;
+
+    left = 0;
+    right = numelts-1;
+
+#define GETPTR(i) (beg+(i)*eltsize)
+
+#define SWAP(i, j) do { \
+    VALUE tmp[2]; \
+    memcpy(tmp, GETPTR(i), sizeof(VALUE)*eltsize); \
+    memcpy(GETPTR(i), GETPTR(j), sizeof(VALUE)*eltsize); \
+    memcpy(GETPTR(j), tmp, sizeof(VALUE)*eltsize); \
+} while (0)
+
+    while (1) {
+	long pivot_index = left + (right-left)/2;
+	long store_index;
+	long num_pivots = 1;
+
+	SWAP(pivot_index, right);
+	pivot_index = right;
+
+	store_index = left;
+	i = left;
+	while (i <= right-num_pivots) {
+	    int c = data->cmpfunc(GETPTR(i), GETPTR(pivot_index), data);
+	    if (data->rev)
+		c = -c;
+	    if (c == 0) {
+	        SWAP(i, right-num_pivots);
+		num_pivots++;
+		continue;
+	    }
+	    if (c < 0) {
+		SWAP(i, store_index);
+		store_index++;
+	    }
+	    i++;
+	}
+	j = store_index;
+	for (i = right; right-num_pivots < i; i--) {
+	    if (i <= j)
+	        break;
+	    SWAP(j, i);
+	    j++;
+	}
+
+	if (store_index <= n && n <= store_index+num_pivots)
+	    break;
+
+	if (n < store_index) {
+	    right = store_index-1;
+	}
+	else {
+	    left = store_index+num_pivots;
+	}
+    }
+#undef GETPTR
+#undef SWAP
+
+    data->curlen = data->n;
+    rb_ary_resize(data->buf, data->n * eltsize);
+}
+
+static VALUE
+nmin_i(VALUE i, VALUE *_data, int argc, VALUE *argv)
+{
+    struct nmin_data *data = (struct nmin_data *)_data;
+
+    ENUM_WANT_SVALUE();
+
+    if (data->by)
+	rb_ary_push(data->buf, rb_yield(i));
+    rb_ary_push(data->buf, i);
+
+    data->curlen++;
+
+    if (data->curlen == data->bufmax) {
+	nmin_filter(data);
+    }
+
+    return Qnil;
+}
+
+static VALUE
+nmin_run(VALUE obj, VALUE num, int by, int rev)
+{
+    VALUE result;
+    struct nmin_data data;
+
+    data.n = NUM2LONG(num);
+    if (data.n < 0)
+        rb_raise(rb_eArgError, "negative size (%ld)", data.n);
+    if (data.n == 0)
+        return rb_ary_new2(0);
+    if (LONG_MAX/4/(by ? 2 : 1) < data.n)
+        rb_raise(rb_eArgError, "too big size");
+    data.bufmax = data.n * 4;
+    data.curlen = 0;
+    data.buf = rb_ary_tmp_new(data.bufmax * (by ? 2 : 1));
+    data.cmpfunc = by ? nmin_cmp :
+                   rb_block_given_p() ? nmin_block_cmp :
+		   nmin_cmp;
+    data.rev = rev;
+    data.by = by;
+    data.method = rev ? (by ? "max_by" : "max")
+                      : (by ? "min_by" : "min");
+    rb_block_call(obj, id_each, 0, 0, nmin_i, (VALUE)&data);
+    nmin_filter(&data);
+    result = data.buf;
+    if (by) {
+	long i;
+	ruby_qsort(RARRAY_PTR(result),
+	           RARRAY_LEN(result)/2,
+		   sizeof(VALUE)*2,
+		   data.cmpfunc, (void *)&data);
+	for (i=1; i<RARRAY_LEN(result); i+=2) {
+	    RARRAY_PTR(result)[i/2] = RARRAY_PTR(result)[i];
+	}
+	rb_ary_resize(result, RARRAY_LEN(result)/2);
+    }
+    else {
+	ruby_qsort(RARRAY_PTR(result), RARRAY_LEN(result), sizeof(VALUE),
+		   data.cmpfunc, (void *)&data);
+    }
+    *((VALUE *)&RBASIC(result)->klass) = rb_cArray;
+    return result;
+
+}
+
 static VALUE
 enum_one(VALUE obj)
 {
@@ -1210,8 +1394,10 @@ min_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg https://github.com/ruby/ruby/blob/trunk/enum.c#L1394
 
 /*
  *  call-seq:
- *     enum.min                 -> obj
- *     enum.min { |a, b| block } -> obj
+ *     enum.min                     -> obj
+ *     enum.min {| a,b | block }    -> obj
+ *     enum.min(n)                  -> array
+ *     enum.min(n) {| a,b | block } -> array
  *
  *  Returns the object in <i>enum</i> with the minimum value. The
  *  first form assumes all objects implement <code>Comparable</code>;
@@ -1220,13 +1406,26 @@ min_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg https://github.com/ruby/ruby/blob/trunk/enum.c#L1406
  *     a = %w(albatross dog horse)
  *     a.min                                   #=> "albatross"
  *     a.min { |a, b| a.length <=> b.length }  #=> "dog"
+ *
+ *  If the +n+ argument is given, minimum +n+ elements are returned
+ *  as an array.
+ *
+ *     a = %w[albatross dog horse]
+ *     a.min(2)                                  #=> ["albatross", "dog"]
+ *     a.min(2) {|a, b| a.length <=> b.length }  #=> ["dog", "horse"]
  */
 
 static VALUE
-enum_min(VALUE obj)
+enum_min(int argc, VALUE *argv, VALUE obj)
 {
     NODE *memo = NEW_MEMO(Qundef, 0, 0);
     VALUE result;
+    VALUE num;
+
+    rb_scan_args(argc, argv, "01", &num);
+
+    if (!NIL_P(num))
+       return nmin_run(obj, num, 0, 0);
 
     if (rb_block_given_p()) {
 	rb_block_call(obj, id_each, 0, 0, min_ii, (VALUE)memo);
@@ -1281,8 +1480,10 @@ max_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg https://github.com/ruby/ruby/blob/trunk/enum.c#L1480
 
 /*
  *  call-seq:
- *     enum.max                  -> obj
- *     enum.max { |a, b| block } -> obj
+ *     enum.max                   -> obj
+ *     enum.max { |a, b| block }  -> obj
+ *     enum.max(n)                -> obj
+ *     enum.max(n) {|a,b| block } -> obj
  *
  *  Returns the object in _enum_ with the maximum value. The
  *  first form assumes all objects implement <code>Comparable</code>;
@@ -1291,13 +1492,26 @@ max_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg https://github.com/ruby/ruby/blob/trunk/enum.c#L1492
  *     a = %w(albatross dog horse)
  *     a.max                                   #=> "horse"
  *     a.max { |a, b| a.length <=> b.length }  #=> "albatross"
+ *
+ *  If the +n+ argument is given, maximum +n+ elements are returned
+ *  as an array.
+ *
+ *     a = %w[albatross dog horse]
+ *     a.max(2)                                  #=> ["dog", "horse"]
+ *     a.max(2) {|a, b| a.length <=> b.length }  #=> ["horse", "albatross"]
  */
 
 static VALUE
-enum_max(VALUE obj)
+enum_max(int argc, VALUE *argv, VALUE obj)
 {
     NODE *memo = NEW_MEMO(Qundef, 0, 0);
     VALUE result;
+    VALUE num;
+
+    rb_scan_args(argc, argv, "01", &num);
+
+    if (!NIL_P(num))
+       return nmin_run(obj, num, 0, 1);
 
     if (rb_block_given_p()) {
 	rb_block_call(obj, id_each, 0, 0, max_ii, (VALUE)memo);
@@ -1485,8 +1699,10 @@ min_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, a https://github.com/ruby/ruby/blob/trunk/enum.c#L1699
 
 /*
  *  call-seq:
- *     enum.min_by { |obj| block } -> obj
- *     enum.min_by                 -> an_enumerator
+ *     enum.min_by {|obj| block }      -> obj
+ *     enum.min_by                     -> an_enumerator
+ *     enum.min_by(n) {|obj| block }   -> array
+ *     enum.min_by(n)                  -> an_enumerator
  *
  *  Returns the object in <i>enum</i> that gives the minimum
  *  value from the given block.
@@ -1495,14 +1711,26 @@ min_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, a https://github.com/ruby/ruby/blob/trunk/enum.c#L1711
  *
  *     a = %w(albatross dog horse)
  *     a.min_by { |x| x.length }   #=> "dog"
+ *
+ *  If the +n+ argument is given, minimum +n+ elements are returned
+ *  as an array.
+ *
+ *     a = %w[albatross dog horse]
+ *     p a.min_by(2) {|x| x.length } #=> ["dog", "horse"]
  */
 
 static VALUE
-enum_min_by(VALUE obj)
+enum_min_by(int argc, VALUE *argv, VALUE obj)
 {
     NODE *memo;
+    VALUE num;
 
-    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
+    rb_scan_args(argc, argv, "01", &num);
+
+    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
+
+    if (!NIL_P(num))
+        return nmin_run(obj, num, 1, 0);
 
     memo = NEW_MEMO(Qundef, Qnil, 0);
     rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
@@ -1531,8 +1759,10 @@ max_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, a https://github.com/ruby/ruby/blob/trunk/enum.c#L1759
 
 /*
  *  call-seq:
- *     enum.max_by { |obj| block } -> obj
- *     enum.max_by                 -> an_enumerator
+ *     enum.max_by {|obj| block }      -> obj
+ *     enum.max_by                     -> an_enumerator
+ *     enum.max_by(n) {|obj| block }   -> obj
+ *     enum.max_by(n)                  -> an_enumerator
  *
  *  Returns the object in <i>enum</i> that gives the maximum
  *  value from the given block.
@@ -1541,14 +1771,26 @@ max_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, a https://github.com/ruby/ruby/blob/trunk/enum.c#L1771
  *
  *     a = %w(albatross dog horse)
  *     a.max_by { |x| x.length }   #=> "albatross"
+ *
+ *  If the +n+ argument is given, minimum +n+ elements are returned
+ *  as an array.
+ *
+ *     a = %w[albatross dog horse]
+ *     a.max_by(2) {|x| x.length } #=> ["horse", "albatross"]
  */
 
 static VALUE
-enum_max_by(VALUE obj)
+enum_max_by(int argc, VALUE *argv, VALUE obj)
 {
     NODE *memo;
+    VALUE num;
 
-    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
+    rb_scan_args(argc, argv, "01", &num);
+
+    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
+
+    if (!NIL_P(num))
+        return nmin_run(obj, num, 1, 1);
 
     memo = NEW_MEMO(Qundef, Qnil, 0);
     rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo);
@@ -2826,11 +3068,11 @@ Init_Enumerable(void) https://github.com/ruby/ruby/blob/trunk/enum.c#L3068
     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, "min", enum_min, 0);
-    rb_define_method(rb_mEnumerable, "max", enum_max, 0);
+    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);
-    rb_define_method(rb_mEnumerable, "min_by", enum_min_by, 0);
-    rb_define_method(rb_mEnumerable, "max_by", enum_max_by, 0);
+    rb_define_method(rb_mEnumerable, "min_by", enum_min_by, -1);
+    rb_define_method(rb_mEnumerable, "max_by", enum_max_by, -1);
     rb_define_method(rb_mEnumerable, "minmax_by", enum_minmax_by, 0);
     rb_define_method(rb_mEnumerable, "member?", enum_member, 1);
     rb_define_method(rb_mEnumerable, "include?", enum_member, 1);
Index: range.c
===================================================================
--- range.c	(revision 44957)
+++ range.c	(revision 44958)
@@ -908,8 +908,10 @@ range_last(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/range.c#L908
 
 /*
  *  call-seq:
- *     rng.min                    -> obj
- *     rng.min {| a,b | block }   -> obj
+ *     rng.min                       -> obj
+ *     rng.min {| a,b | block }      -> obj
+ *     rng.min(n)                    -> array
+ *     rng.min(n) {| a,b | block }   -> array
  *
  *  Returns the minimum value in the range. Returns +nil+ if the begin
  *  value of the range is larger than the end value.
@@ -922,10 +924,13 @@ range_last(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/range.c#L924
 
 
 static VALUE
-range_min(VALUE range)
+range_min(int argc, VALUE *argv, VALUE range)
 {
     if (rb_block_given_p()) {
-	return rb_call_super(0, 0);
+	return rb_call_super(argc, argv);
+    }
+    else if (argc != 0) {
+	return range_first(argc, argv, range);
     }
     else {
 	VALUE b = RANGE_BEG(range);
@@ -940,8 +945,10 @@ range_min(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L945
 
 /*
  *  call-seq:
- *     rng.max                    -> obj
- *     rng.max {| a,b | block }   -> obj
+ *     rng.max                       -> obj
+ *     rng.max {| a,b | block }      -> obj
+ *     rng.max(n)                    -> obj
+ *     rng.max(n) {| a,b | block }   -> obj
  *
  *  Returns the maximum value in the range. Returns +nil+ if the begin
  *  value of the range larger than the end value.
@@ -953,13 +960,13 @@ range_min(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L960
  */
 
 static VALUE
-range_max(VALUE range)
+range_max(int argc, VALUE *argv, VALUE range)
 {
     VALUE e = RANGE_END(range);
     int nm = FIXNUM_P(e) || rb_obj_is_kind_of(e, rb_cNumeric);
 
-    if (rb_block_given_p() || (EXCL(range) && !nm)) {
-	return rb_call_super(0, 0);
+    if (rb_block_given_p() || (EXCL(range) && !nm) || argc) {
+	return rb_call_super(argc, argv);
     }
     else {
 	VALUE b = RANGE_BEG(range);
@@ -1358,8 +1365,8 @@ Init_Range(void) https://github.com/ruby/ruby/blob/trunk/range.c#L1365
     rb_define_method(rb_cRange, "end", range_end, 0);
     rb_define_method(rb_cRange, "first", range_first, -1);
     rb_define_method(rb_cRange, "last", range_last, -1);
-    rb_define_method(rb_cRange, "min", range_min, 0);
-    rb_define_method(rb_cRange, "max", range_max, 0);
+    rb_define_method(rb_cRange, "min", range_min, -1);
+    rb_define_method(rb_cRange, "max", range_max, -1);
     rb_define_method(rb_cRange, "size", range_size, 0);
     rb_define_method(rb_cRange, "to_s", range_to_s, 0);
     rb_define_method(rb_cRange, "inspect", range_inspect, 0);
Index: NEWS
===================================================================
--- NEWS	(revision 44957)
+++ NEWS	(revision 44958)
@@ -15,6 +15,13 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L15
 
 === Core classes updates (outstanding ones only)
 
+* Enumerable
+  * Extended methods:
+    * min
+    * min_by
+    * max
+    * max_by
+
 === Core classes compatibility issues (excluding feature bug fixes)
 
 === Stdlib updates (outstanding ones only)
Index: test/ruby/test_range.rb
===================================================================
--- test/ruby/test_range.rb	(revision 44957)
+++ test/ruby/test_range.rb	(revision 44958)
@@ -60,6 +60,9 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L60
 
     assert_equal(0, (0..0).min)
     assert_equal(nil, (0...0).min)
+
+    assert_equal([0,1,2], (0..10).min(3))
+    assert_equal([0,1], (0..1).min(3))
   end
 
   def test_max
@@ -77,6 +80,9 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L80
 
     assert_equal(0, (0..0).max)
     assert_equal(nil, (0...0).max)
+
+    assert_equal([8,9,10], (0..10).max(3))
+    assert_equal([7,8,9], (0...10).max(3))
   end
 
   def test_initialize_twice
Index: test/ruby/test_enum.rb
===================================================================
--- test/ruby/test_enum.rb	(revision 44957)
+++ test/ruby/test_enum.rb	(revision 44958)
@@ -209,6 +209,9 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L209
     assert_equal("albatross", ary.min)
     assert_equal("dog", ary.min {|a,b| a.length <=> b.length })
     assert_equal(1, [3,2,1].min)
+    assert_equal(%w[albatross dog], ary.min(2))
+    assert_equal(%w[dog horse],
+                 ary.min(2) {|a,b| a.length <=> b.length })
   end
 
   def test_max
@@ -218,6 +221,9 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L221
     assert_equal("horse", ary.max)
     assert_equal("albatross", ary.max {|a,b| a.length <=> b.length })
     assert_equal(1, [3,2,1].max{|a,b| b <=> a })
+    assert_equal(%w[dog horse], ary.max(2))
+    assert_equal(%w[horse albatross],
+                 ary.max(2) {|a,b| a.length <=> b.length })
   end
 
   def test_minmax
@@ -236,6 +242,7 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L242
     a = %w(albatross dog horse)
     assert_equal("dog", a.min_by {|x| x.length })
     assert_equal(3, [2,3,1].min_by {|x| -x })
+    assert_equal(%w[dog horse], a.min_by(2) {|x| x.length })
   end
 
   def test_max_by
@@ -243,6 +250,7 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L250
     a = %w(albatross dog horse)
     assert_equal("albatross", a.max_by {|x| x.length })
     assert_equal(1, [2,3,1].max_by {|x| -x })
+    assert_equal(%w[horse albatross], a.max_by(2) {|x| x.length })
   end
 
   def test_minmax_by

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

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