Project

General

Profile

Feature #9826 » slice_when.patch

akr (Akira Tanaka), 09/18/2014 04:30 AM

View differences:

enumerator.c (working copy)
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");
enum.c (working copy)
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
......
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");
test/ruby/test_enum.rb (working copy)
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' })
test/ruby/test_lazy_enumerator.rb (working copy)
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
(4-4/4)