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

ruby-changes:35570

From: akr <ko1@a...>
Date: Sat, 20 Sep 2014 15:52:44 +0900 (JST)
Subject: [ruby-changes:35570] akr:r47652 (trunk): * enum.c (enum_slice_when): New method: Enumerable#slice_when.

akr	2014-09-20 15:52:29 +0900 (Sat, 20 Sep 2014)

  New Revision: 47652

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

  Log:
    * enum.c (enum_slice_when): New method: Enumerable#slice_when.
      (slicewhen_i): New function.
      (slicewhen_ii): New function.
    
    * enumerator.c (InitVM_Enumerator): New method:
      Enumerator::Lazy#slice_when.
    
      [ruby-core:62499] [Feature #9826]

  Modified files:
    trunk/ChangeLog
    trunk/NEWS
    trunk/enum.c
    trunk/enumerator.c
    trunk/test/ruby/test_enum.rb
    trunk/test/ruby/test_lazy_enumerator.rb
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 47651)
+++ ChangeLog	(revision 47652)
@@ -1,3 +1,14 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Sat Sep 20 15:39:11 2014  Tanaka Akira  <akr@f...>
+
+	* enum.c (enum_slice_when): New method: Enumerable#slice_when.
+	  (slicewhen_i): New function.
+	  (slicewhen_ii): New function.
+
+	* enumerator.c (InitVM_Enumerator): New method:
+	  Enumerator::Lazy#slice_when.
+
+	  [ruby-core:62499] [Feature #9826]
+
 Sat Sep 20 11:55:19 2014  SHIBATA Hiroshi  <shibata.hiroshi@g...>
 
 	* .travis.yml: added new configurations for osx on travis ci.
Index: enumerator.c
===================================================================
--- enumerator.c	(revision 47651)
+++ enumerator.c	(revision 47652)
@@ -2040,6 +2040,7 @@ InitVM_Enumerator(void) https://github.com/ruby/ruby/blob/trunk/enumerator.c#L2040
     rb_define_method(rb_cLazy, "chunk", lazy_super, -1);
     rb_define_method(rb_cLazy, "slice_before", lazy_super, -1);
     rb_define_method(rb_cLazy, "slice_after", lazy_super, -1);
+    rb_define_method(rb_cLazy, "slice_when", lazy_super, -1);
 
     rb_define_alias(rb_cLazy, "force", "to_a");
 
Index: enum.c
===================================================================
--- enum.c	(revision 47651)
+++ enum.c	(revision 47652)
@@ -3206,6 +3206,138 @@ enum_slice_after(int argc, VALUE *argv, https://github.com/ruby/ruby/blob/trunk/enum.c#L3206
     return enumerator;
 }
 
+struct slicewhen_arg {
+    VALUE pred;
+    VALUE prev_elt;
+    VALUE prev_elts;
+    VALUE yielder;
+};
+
+static VALUE
+slicewhen_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo))
+{
+#define UPDATE_MEMO ((void)(memo = MEMO_FOR(struct slicewhen_arg, _memo)))
+    struct slicewhen_arg *memo;
+    int split_p;
+    UPDATE_MEMO;
+
+    ENUM_WANT_SVALUE();
+
+    if (memo->prev_elt == Qundef) {
+        /* The first element */
+        memo->prev_elt = i;
+        memo->prev_elts = rb_ary_new3(1, i);
+    }
+    else {
+        split_p = RTEST(rb_funcall(memo->pred, id_call, 2, memo->prev_elt, i));
+        UPDATE_MEMO;
+
+        if (split_p) {
+            rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts);
+            UPDATE_MEMO;
+            memo->prev_elts = rb_ary_new3(1, i);
+        }
+        else {
+            rb_ary_push(memo->prev_elts, i);
+        }
+
+        memo->prev_elt = i;
+    }
+
+    return Qnil;
+#undef UPDATE_MEMO
+}
+
+static VALUE
+slicewhen_i(RB_BLOCK_CALL_FUNC_ARGLIST(yielder, enumerator))
+{
+    VALUE enumerable;
+    VALUE arg;
+    struct slicewhen_arg *memo = NEW_MEMO_FOR(struct slicewhen_arg, arg);
+
+    enumerable = rb_ivar_get(enumerator, rb_intern("slicewhen_enum"));
+    memo->pred = rb_attr_get(enumerator, rb_intern("slicewhen_pred"));
+    memo->prev_elt = Qundef;
+    memo->prev_elts = Qnil;
+    memo->yielder = yielder;
+
+    rb_block_call(enumerable, id_each, 0, 0, slicewhen_ii, arg);
+    memo = MEMO_FOR(struct slicewhen_arg, arg);
+    if (!NIL_P(memo->prev_elts))
+        rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts);
+    return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     enum.slice_when {|elt_before, elt_after| bool } -> an_enumerator
+ *
+ *  Creates an enumerator for each chunked elements.
+ *  The beginnings of chunks are defined by the block.
+ *
+ *  This method split each chunk using adjacent elements,
+ *  _elt_before_ and _elt_after_,
+ *  in the receiver enumerator.
+ *  This method split chunks between _elt_before_ and _elt_after_ where
+ *  the block returns true.
+ *
+ *  The block is called the length of the receiver enumerator minus one.
+ *
+ *  The result enumerator yields the chunked elements as an array.
+ *  So +each+ method can be called as follows:
+ *
+ *    enum.slice_when { |elt_before, elt_after| bool }.each { |ary| ... }
+ *
+ *  Other methods of the Enumerator class and Enumerable module,
+ *  such as +map+, etc., are also usable.
+ *
+ *  For example, one-by-one increasing subsequence can be chunked as follows:
+ *
+ *    a = [1,2,4,9,10,11,12,15,16,19,20,21]
+ *    b = a.slice_when {|i, j| i+1 != j }
+ *    p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
+ *    c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
+ *    p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
+ *    d = c.join(",")
+ *    p d #=> "1,2,4,9-12,15,16,19-21"
+ *
+ *  Increasing (non-decreasing) subsequence can be chunked as follows:
+ *
+ *    a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
+ *    p a.slice_when {|i, j| i > j }.to_a
+ *    #=> [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
+ *
+ *  Adjacent evens and odds can be chunked as follows:
+ *  (Enumerable#chunk is another way to do it.)
+ *
+ *    a = [7, 5, 9, 2, 0, 7, 9, 4, 2, 0]
+ *    p a.slice_when {|i, j| i.even? != j.even? }.to_a
+ *    #=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]]
+ *
+ *  Paragraphs (non-empty lines with trailing empty lines) can be chunked as follows:
+ *  (See Enumerable#chunk to ignore empty lines.)
+ *
+ *    lines = ["foo\n", "bar\n", "\n", "baz\n", "qux\n"]
+ *    p lines.slice_when {|l1, l2| /\A\s*\z/ =~ l1 && /\S/ =~ l2 }.to_a
+ *    #=> [["foo\n", "bar\n", "\n"], ["baz\n", "qux\n"]]
+ *
+ */
+static VALUE
+enum_slice_when(VALUE enumerable)
+{
+    VALUE enumerator;
+    VALUE pred;
+
+    pred = rb_block_proc();
+
+    enumerator = rb_obj_alloc(rb_cEnumerator);
+    rb_ivar_set(enumerator, rb_intern("slicewhen_enum"), enumerable);
+    rb_ivar_set(enumerator, rb_intern("slicewhen_pred"), pred);
+
+    rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator);
+    return enumerator;
+}
+
 /*
  *  The <code>Enumerable</code> mixin provides collection classes with
  *  several traversal and searching methods, and with the ability to
@@ -3275,6 +3407,7 @@ Init_Enumerable(void) https://github.com/ruby/ruby/blob/trunk/enum.c#L3407
     rb_define_method(rb_mEnumerable, "chunk", enum_chunk, -1);
     rb_define_method(rb_mEnumerable, "slice_before", enum_slice_before, -1);
     rb_define_method(rb_mEnumerable, "slice_after", enum_slice_after, -1);
+    rb_define_method(rb_mEnumerable, "slice_when", enum_slice_when, 0);
 
     id_next = rb_intern("next");
     id_call = rb_intern("call");
Index: NEWS
===================================================================
--- NEWS	(revision 47651)
+++ NEWS	(revision 47652)
@@ -33,6 +33,7 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L33
 * Enumerable
   * New methods:
     * Enumerable#slice_after
+    * Enumerable#slice_when
   * Extended methods:
     * min, min_by, max and max_by supports optional argument to return
       multiple elements.
Index: test/ruby/test_enum.rb
===================================================================
--- test/ruby/test_enum.rb	(revision 47651)
+++ test/ruby/test_enum.rb	(revision 47652)
@@ -574,6 +574,61 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L574
     assert_equal([["foo", ""], ["bar"]], e.to_a)
   end
 
+  def test_slice_when_0
+    e = [].slice_when {|a, b| flunk "should not be called" }
+    assert_equal([], e.to_a)
+  end
+
+  def test_slice_when_1
+    e = [1].slice_when {|a, b| flunk "should not be called" }
+    assert_equal([[1]], e.to_a)
+  end
+
+  def test_slice_when_2
+    e = [1,2].slice_when {|a,b|
+      assert_equal(1, a)
+      assert_equal(2, b)
+      true
+    }
+    assert_equal([[1], [2]], e.to_a)
+
+    e = [1,2].slice_when {|a,b|
+      assert_equal(1, a)
+      assert_equal(2, b)
+      false
+    }
+    assert_equal([[1, 2]], e.to_a)
+  end
+
+  def test_slice_when_3
+    block_invocations = [
+      lambda {|a, b|
+        assert_equal(1, a)
+        assert_equal(2, b)
+        true
+      },
+      lambda {|a, b|
+        assert_equal(2, a)
+        assert_equal(3, b)
+        false
+      }
+    ]
+    e = [1,2,3].slice_when {|a,b|
+      block_invocations.shift.call(a, b)
+    }
+    assert_equal([[1], [2, 3]], e.to_a)
+    assert_equal([], block_invocations)
+  end
+
+  def test_slice_when_noblock
+    assert_raise(ArgumentError) { [].slice_when }
+  end
+
+  def test_slice_when_contiguously_increasing_integers
+    e = [1,4,9,10,11,12,15,16,19,20,21].slice_when {|i, j| i+1 != j }
+    assert_equal([[1], [4], [9,10,11,12], [15,16], [19,20,21]], e.to_a)
+  end
+
   def test_detect
     @obj = ('a'..'z')
     assert_equal('c', @obj.detect {|x| x == 'c' })
Index: test/ruby/test_lazy_enumerator.rb
===================================================================
--- test/ruby/test_lazy_enumerator.rb	(revision 47651)
+++ test/ruby/test_lazy_enumerator.rb	(revision 47652)
@@ -481,6 +481,7 @@ EOS https://github.com/ruby/ruby/blob/trunk/test/ruby/test_lazy_enumerator.rb#L481
       assert_equal Enumerator::Lazy, [].lazy.send(method, *arg).class, bug7507
     end
     assert_equal Enumerator::Lazy, [].lazy.chunk{}.class, bug7507
+    assert_equal Enumerator::Lazy, [].lazy.slice_when{}.class, bug7507
   end
 
   def test_no_warnings

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

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