Project

General

Profile

Feature #9826 ยป slice_when.patch

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

View differences:

enumerator.c (working copy)
2040 2040
    rb_define_method(rb_cLazy, "chunk", lazy_super, -1);
2041 2041
    rb_define_method(rb_cLazy, "slice_before", lazy_super, -1);
2042 2042
    rb_define_method(rb_cLazy, "slice_after", lazy_super, -1);
2043
    rb_define_method(rb_cLazy, "slice_when", lazy_super, -1);
2043 2044

  
2044 2045
    rb_define_alias(rb_cLazy, "force", "to_a");
2045 2046

  
enum.c (working copy)
3206 3206
    return enumerator;
3207 3207
}
3208 3208

  
3209
struct slicewhen_arg {
3210
    VALUE pred;
3211
    VALUE prev_elt;
3212
    VALUE prev_elts;
3213
    VALUE yielder;
3214
};
3215

  
3216
static VALUE
3217
slicewhen_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo))
3218
{
3219
#define UPDATE_MEMO ((void)(memo = MEMO_FOR(struct slicewhen_arg, _memo)))
3220
    struct slicewhen_arg *memo;
3221
    int split_p;
3222
    UPDATE_MEMO;
3223

  
3224
    ENUM_WANT_SVALUE();
3225

  
3226
    if (memo->prev_elt == Qundef) {
3227
        /* The first element */
3228
        memo->prev_elt = i;
3229
        memo->prev_elts = rb_ary_new3(1, i);
3230
    }
3231
    else {
3232
        split_p = RTEST(rb_funcall(memo->pred, id_call, 2, memo->prev_elt, i));
3233
        UPDATE_MEMO;
3234

  
3235
        if (split_p) {
3236
            rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts);
3237
            UPDATE_MEMO;
3238
            memo->prev_elts = rb_ary_new3(1, i);
3239
        }
3240
        else {
3241
            rb_ary_push(memo->prev_elts, i);
3242
        }
3243

  
3244
        memo->prev_elt = i;
3245
    }
3246

  
3247
    return Qnil;
3248
#undef UPDATE_MEMO
3249
}
3250

  
3251
static VALUE
3252
slicewhen_i(RB_BLOCK_CALL_FUNC_ARGLIST(yielder, enumerator))
3253
{
3254
    VALUE enumerable;
3255
    VALUE arg;
3256
    struct slicewhen_arg *memo = NEW_MEMO_FOR(struct slicewhen_arg, arg);
3257

  
3258
    enumerable = rb_ivar_get(enumerator, rb_intern("slicewhen_enum"));
3259
    memo->pred = rb_attr_get(enumerator, rb_intern("slicewhen_pred"));
3260
    memo->prev_elt = Qundef;
3261
    memo->prev_elts = Qnil;
3262
    memo->yielder = yielder;
3263

  
3264
    rb_block_call(enumerable, id_each, 0, 0, slicewhen_ii, arg);
3265
    memo = MEMO_FOR(struct slicewhen_arg, arg);
3266
    if (!NIL_P(memo->prev_elts))
3267
        rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts);
3268
    return Qnil;
3269
}
3270

  
3271
/*
3272
 *  call-seq:
3273
 *     enum.slice_when {|elt_before, elt_after| bool } -> an_enumerator
3274
 *
3275
 *  Creates an enumerator for each chunked elements.
3276
 *  The beginnings of chunks are defined by the block.
3277
 *
3278
 *  This method split each chunk using adjacent elements,
3279
 *  _elt_before_ and _elt_after_,
3280
 *  in the receiver enumerator.
3281
 *  This method split chunks between _elt_before_ and _elt_after_ where
3282
 *  the block returns true.
3283
 *
3284
 *  The block is called the length of the receiver enumerator minus one.
3285
 *
3286
 *  The result enumerator yields the chunked elements as an array.
3287
 *  So +each+ method can be called as follows:
3288
 *
3289
 *    enum.slice_when { |elt_before, elt_after| bool }.each { |ary| ... }
3290
 *
3291
 *  Other methods of the Enumerator class and Enumerable module,
3292
 *  such as +map+, etc., are also usable.
3293
 *
3294
 *  For example, one-by-one increasing subsequence can be chunked as follows:
3295
 *
3296
 *    a = [1,2,4,9,10,11,12,15,16,19,20,21]
3297
 *    b = a.slice_when {|i, j| i+1 != j }
3298
 *    p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
3299
 *    c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
3300
 *    p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
3301
 *    d = c.join(",")
3302
 *    p d #=> "1,2,4,9-12,15,16,19-21"
3303
 *
3304
 *  Increasing (non-decreasing) subsequence can be chunked as follows:
3305
 *
3306
 *    a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
3307
 *    p a.slice_when {|i, j| i > j }.to_a
3308
 *    #=> [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
3309
 *
3310
 *  Adjacent evens and odds can be chunked as follows:
3311
 *  (Enumerable#chunk is another way to do it.)
3312
 *
3313
 *    a = [7, 5, 9, 2, 0, 7, 9, 4, 2, 0]
3314
 *    p a.slice_when {|i, j| i.even? != j.even? }.to_a
3315
 *    #=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]]
3316
 *
3317
 *  Paragraphs (non-empty lines with trailing empty lines) can be chunked as follows:
3318
 *  (See Enumerable#chunk to ignore empty lines.)
3319
 *
3320
 *    lines = ["foo\n", "bar\n", "\n", "baz\n", "qux\n"]
3321
 *    p lines.slice_when {|l1, l2| /\A\s*\z/ =~ l1 && /\S/ =~ l2 }.to_a
3322
 *    #=> [["foo\n", "bar\n", "\n"], ["baz\n", "qux\n"]]
3323
 *
3324
 */
3325
static VALUE
3326
enum_slice_when(VALUE enumerable)
3327
{
3328
    VALUE enumerator;
3329
    VALUE pred;
3330

  
3331
    pred = rb_block_proc();
3332

  
3333
    enumerator = rb_obj_alloc(rb_cEnumerator);
3334
    rb_ivar_set(enumerator, rb_intern("slicewhen_enum"), enumerable);
3335
    rb_ivar_set(enumerator, rb_intern("slicewhen_pred"), pred);
3336

  
3337
    rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator);
3338
    return enumerator;
3339
}
3340

  
3209 3341
/*
3210 3342
 *  The <code>Enumerable</code> mixin provides collection classes with
3211 3343
 *  several traversal and searching methods, and with the ability to
......
3275 3407
    rb_define_method(rb_mEnumerable, "chunk", enum_chunk, -1);
3276 3408
    rb_define_method(rb_mEnumerable, "slice_before", enum_slice_before, -1);
3277 3409
    rb_define_method(rb_mEnumerable, "slice_after", enum_slice_after, -1);
3410
    rb_define_method(rb_mEnumerable, "slice_when", enum_slice_when, 0);
3278 3411

  
3279 3412
    id_next = rb_intern("next");
3280 3413
    id_call = rb_intern("call");
test/ruby/test_enum.rb (working copy)
574 574
    assert_equal([["foo", ""], ["bar"]], e.to_a)
575 575
  end
576 576

  
577
  def test_slice_when_0
578
    e = [].slice_when {|a, b| flunk "should not be called" }
579
    assert_equal([], e.to_a)
580
  end
581

  
582
  def test_slice_when_1
583
    e = [1].slice_when {|a, b| flunk "should not be called" }
584
    assert_equal([[1]], e.to_a)
585
  end
586

  
587
  def test_slice_when_2
588
    e = [1,2].slice_when {|a,b|
589
      assert_equal(1, a)
590
      assert_equal(2, b)
591
      true
592
    }
593
    assert_equal([[1], [2]], e.to_a)
594

  
595
    e = [1,2].slice_when {|a,b|
596
      assert_equal(1, a)
597
      assert_equal(2, b)
598
      false
599
    }
600
    assert_equal([[1, 2]], e.to_a)
601
  end
602

  
603
  def test_slice_when_3
604
    block_invocations = [
605
      lambda {|a, b|
606
        assert_equal(1, a)
607
        assert_equal(2, b)
608
        true
609
      },
610
      lambda {|a, b|
611
        assert_equal(2, a)
612
        assert_equal(3, b)
613
        false
614
      }
615
    ]
616
    e = [1,2,3].slice_when {|a,b|
617
      block_invocations.shift.call(a, b)
618
    }
619
    assert_equal([[1], [2, 3]], e.to_a)
620
    assert_equal([], block_invocations)
621
  end
622

  
623
  def test_slice_when_noblock
624
    assert_raise(ArgumentError) { [].slice_when }
625
  end
626

  
627
  def test_slice_when_contiguously_increasing_integers
628
    e = [1,4,9,10,11,12,15,16,19,20,21].slice_when {|i, j| i+1 != j }
629
    assert_equal([[1], [4], [9,10,11,12], [15,16], [19,20,21]], e.to_a)
630
  end
631

  
577 632
  def test_detect
578 633
    @obj = ('a'..'z')
579 634
    assert_equal('c', @obj.detect {|x| x == 'c' })
test/ruby/test_lazy_enumerator.rb (working copy)
481 481
      assert_equal Enumerator::Lazy, [].lazy.send(method, *arg).class, bug7507
482 482
    end
483 483
    assert_equal Enumerator::Lazy, [].lazy.chunk{}.class, bug7507
484
    assert_equal Enumerator::Lazy, [].lazy.slice_when{}.class, bug7507
484 485
  end
485 486

  
486 487
  def test_no_warnings