ruby-changes:38808
From: akr <ko1@a...>
Date: Sun, 14 Jun 2015 17:54:24 +0900 (JST)
Subject: [ruby-changes:38808] akr:r50889 (trunk): * enum.c (enum_chunk_while): New method Enumerable#chunk_while.
akr 2015-06-14 17:54:19 +0900 (Sun, 14 Jun 2015) New Revision: 50889 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=50889 Log: * enum.c (enum_chunk_while): New method Enumerable#chunk_while. [ruby-core:67738] [Feature #10769] proposed by Tsuyoshi Sawada. Modified files: trunk/ChangeLog trunk/NEWS trunk/enum.c trunk/test/ruby/test_enum.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 50888) +++ ChangeLog (revision 50889) @@ -1,3 +1,8 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Sun Jun 14 17:26:03 2015 Tanaka Akira <akr@f...> + + * enum.c (enum_chunk_while): New method Enumerable#chunk_while. + [ruby-core:67738] [Feature #10769] proposed by Tsuyoshi Sawada. + Sun Jun 14 17:20:40 2015 Nobuyoshi Nakada <nobu@r...> * file.c (rb_file_load_ok): try opening file without gvl not to Index: enum.c =================================================================== --- enum.c (revision 50888) +++ enum.c (revision 50889) @@ -3255,6 +3255,7 @@ struct slicewhen_arg { https://github.com/ruby/ruby/blob/trunk/enum.c#L3255 VALUE prev_elt; VALUE prev_elts; VALUE yielder; + int inverted; /* 0 for slice_when and 1 for chunk_while. */ }; static VALUE @@ -3276,6 +3277,9 @@ slicewhen_ii(RB_BLOCK_CALL_FUNC_ARGLIST( https://github.com/ruby/ruby/blob/trunk/enum.c#L3277 split_p = RTEST(rb_funcall(memo->pred, id_call, 2, memo->prev_elt, i)); UPDATE_MEMO; + if (memo->inverted) + split_p = !split_p; + if (split_p) { rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts); UPDATE_MEMO; @@ -3304,6 +3308,7 @@ slicewhen_i(RB_BLOCK_CALL_FUNC_ARGLIST(y https://github.com/ruby/ruby/blob/trunk/enum.c#L3308 memo->prev_elt = Qundef; memo->prev_elts = Qnil; memo->yielder = yielder; + memo->inverted = RTEST(rb_attr_get(enumerator, rb_intern("slicewhen_inverted"))); rb_block_call(enumerable, id_each, 0, 0, slicewhen_ii, arg); memo = MEMO_FOR(struct slicewhen_arg, arg); @@ -3383,6 +3388,71 @@ enum_slice_when(VALUE enumerable) https://github.com/ruby/ruby/blob/trunk/enum.c#L3388 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_ivar_set(enumerator, rb_intern("slicewhen_inverted"), Qfalse); + + rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator); + return enumerator; +} + +/* + * call-seq: + * enum.chunk_while {|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 false. + * + * 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.chunk_while { |elt_before, elt_after| bool }.each { |ary| ... } + * + * Other methods of the Enumerator class and Enumerable module, + * such as +to_a+, +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.chunk_while {|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.chunk_while {|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.chunk_while {|i, j| i.even? == j.even? }.to_a + * #=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]] + * + */ +static VALUE +enum_chunk_while(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_ivar_set(enumerator, rb_intern("slicewhen_inverted"), Qtrue); rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator); return enumerator; @@ -3459,6 +3529,7 @@ Init_Enumerable(void) https://github.com/ruby/ruby/blob/trunk/enum.c#L3529 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); + rb_define_method(rb_mEnumerable, "chunk_while", enum_chunk_while, 0); id_next = rb_intern("next"); id_call = rb_intern("call"); Index: NEWS =================================================================== --- NEWS (revision 50888) +++ NEWS (revision 50889) @@ -19,6 +19,7 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L19 * Enumerable#grep_v is added as inverse version of Enumerable#grep. [Feature #11049] + * Enumerable#chunk_while [Feature #10769] * Numeric Index: test/ruby/test_enum.rb =================================================================== --- test/ruby/test_enum.rb (revision 50888) +++ test/ruby/test_enum.rb (revision 50889) @@ -659,6 +659,11 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L659 assert_equal([[1], [4], [9,10,11,12], [15,16], [19,20,21]], e.to_a) end + def test_chunk_while_contiguously_increasing_integers + e = [1,4,9,10,11,12,15,16,19,20,21].chunk_while {|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' }) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/