Array#slice_when
slice_when { |elt_before, elt_after| ... } Array#slice_when returns an array of arrays, splitting the receiver between every pair of adjacent elements for which the given block returns a truthy value. It is defined on Enumerable and Array inherits it unchanged. The method was added in Ruby 2.3, alongside the closely related chunk_while, slice_before, and slice_after.
The canonical use case is breaking a sorted sequence into runs:
[1, 2, 3, 5, 6, 8, 9, 10].slice_when { |a, b| b - a > 1 }.to_a
# => [[1, 2, 3], [5, 6], [8, 9, 10]]
The block b - a > 1 is true exactly when there is a gap, so a new slice starts at every gap.
Signature
slice_when { |elt_before, elt_after| ... } # => Array
slice_when # => Enumerator
| Form | Returns |
|---|---|
| With a block | Array of Array slices. Eager. |
| Without a block | Enumerator. Lazy until iterated or converted with .to_a. |
The block receives two arguments, the element on the left of the candidate split and the element on the right. It is called array.length - 1 times, so the block never runs for arrays of length 0 or 1.
How it works
The block is a boundary detector. When it returns a truthy value for a pair (a, b), a new slice starts before b, and a and b end up in different slices. Returning falsy keeps the two elements together in the current slice. Once a boundary is inserted, the next comparison starts from the element after the boundary, so there is no lookahead beyond one step.
Walking through [1, 2, 3, 5, 6, 8] with the block b - a > 1:
| Pair | Block result | Effect |
|---|---|---|
| (1, 2) | 1 > 1 is false | keep together |
| (2, 3) | 1 > 1 is false | keep together |
| (3, 5) | 2 > 1 is true | cut before 5 |
| (5, 6) | 1 > 1 is false | keep together |
| (6, 8) | 2 > 1 is true | cut before 8 |
Result: [[1, 2, 3], [5, 6], [8]].
Examples
Group by sign change
<=> against 0 returns -1, 0, or 1, so comparing the signs of two adjacent numbers is a clean way to detect a sign boundary:
[1, 2, 3, -2, -3, 4, 5, -1].slice_when { |a, b| (a <=> 0) != (b <=> 0) }.to_a
# => [[1, 2, 3], [-2, -3], [4, 5], [-1]]
Split a string by case alternation
Array#chars returns an array of single-character strings, so slice_when operates on the character array directly. The block decides where each new slice starts based on the relationship between two neighboring characters, which makes case alternation straightforward to express as a transition condition between adjacent letters.
"aBcDeFgH".chars.slice_when { |a, b| a == a.upcase && b == b.downcase }.to_a
# => [["a", "B"], ["c", "D"], ["e", "F"], ["g", "H"]]
Group by time gap
Any object that responds to a comparison works with slice_when, including structs, hashes, and custom classes. The example below groups a log of timestamped events by gaps larger than 60 seconds, a common pattern for spotting activity bursts separated by quiet stretches in a stream of records.
events = [
{ time: 0, msg: "ping" },
{ time: 10, msg: "ping" },
{ time: 20, msg: "ping" },
{ time: 200, msg: "ping" },
{ time: 210, msg: "ping" }
]
events.slice_when { |prev, curr| curr[:time] - prev[:time] > 60 }.to_a
# => [[{time: 0, ...}, {time: 10, ...}, {time: 20, ...}],
# [{time: 200, ...}, {time: 210, ...}]]
Without a block, returns an Enumerator
When called without a block, slice_when returns an Enumerator that you can iterate or convert to an array later. The lazy form is handy for chaining slice_when with other Enumerable methods, or for deferring the work until the slices are actually needed. The receiver array itself stays untouched until you call .to_a or otherwise iterate the enumerator.
e = [1, 2, 4, 5].slice_when { |a, b| b - a > 1 }
e.class # => Enumerator
e.to_a # => [[1, 2], [4, 5]]
slice_when vs chunk_while
slice_when and chunk_while are the two halves of the same idea, and they are exact inverses of each other. slice_when inserts a slice boundary whenever the block returns true, while chunk_while groups elements together only as long as the block returns true. Reading both versions of the same predicate side by side makes the inverse framing easy to verify.
[1, 2, 3, 5, 6, 8].slice_when { |a, b| b - a > 1 }.to_a
# => [[1, 2, 3], [5, 6], [8]]
[1, 2, 3, 5, 6, 8].chunk_while { |a, b| (b - a) <= 1 }.to_a
# => [[1, 2, 3], [5, 6], [8]]
A useful framing: slice_when answers “should a cut go here?”, while chunk_while answers “should these stay together?” Pick the one whose question reads more naturally for the problem at hand.
Common Mistakes
- Truthy means split, not “include”. A truthy return value inserts a slice boundary between the two elements. It never causes an element to be dropped.
- No block returns
Enumerator, notArray. If you writeresult = arr.slice_when, call.to_a(or iterate) before you try to useresultas an array. - Different from
Array#slice.Array#slice(no_when) is the index, range, and offset accessor. Same prefix, unrelated method. - Different from
slice_beforeandslice_after. Those split at elements matching a pattern or sentinel.slice_whensplits purely on the relationship between two neighbors, so it is the right tool for runs, monotonicity changes, and sign changes. - Does not mutate. The receiver is unchanged. A new array of new inner arrays is returned.
- Edge cases. Empty arrays return
[]. A one-element array returns[[element]]and the block never runs. A block that always returnsfalsereturns one big slice, and a block that always returnstruereturns one-element slices for every element.
See Also
Array#chunk_while: the direct inverse, which keeps elements together while the block is truthy.Array#each_slice: fixed-size windowing with no predicate involved.Enumerable#chunk: the older, element-based grouping primitive.