Today we're going to talk about slice_before, slice_when and slice_after. These are super-useful methods when you need to group items in an array or other enumerable based on arbitrary criteria.

You're probably familiar with Array#slice. It lets you pull out a subset of an array based on a range of indices:

a = ["a", "b", "c"]
a.slice(1, 2)
# => ["b", "c"]

This is useful, but it can't be used with enumerables, because enumerables don't have indices.

The slice_before, slice_when and slice_after methods don't rely on indices - so you can use them with any enumerable. Let's take a look!

Using Enumerable#slice_before

Enumerable#slice_before splits and enumerable into groups at the point before a match is made.

The match is made via the === operator, which means that you can match all kinds of things.

Value Matches

You can match a single value. That should be obvious. :)

a = ["a", "b", "c"]
a.slice_before("b").to_a
# => [["a"], ["b", "c"]]

Regular Expression Matches

You can use regular expressions for more complex textual matching.

a = ["000", "b", "999"]
a.slice_before(/[a-z]/).to_a
# => [["000"], ["b", "999"]]

Range Matches

If you're working with numbers, you can slice the array based on a range.

a = [100, 200, 300]
a.slice_before(150..250).to_a
# => [[100], [200, 300]]

Class matches

This one may seem a little strange to you, but it's fully in keeping with the behavior of the === operator.

a = [1, "200", 1.3]
a.slice_before(String).to_a
# => [[1], ["200", 1.3]]

Using a block

If none of the other options are flexible enough for you can always to find a match programmatically with a block.

a = [1, 2, 3, 4, 5]
a.slice_before do |item|
  item % 2 == 0
end
# => [[1], [2, 3], [4, 5]]

Using Enumerable#slice_after

Enumerable#slice_after works exactly like Enumerable#slice_before except that the slice happens after the match. Go figure. :-)

a = ["a", "b", "c"]
a.slice_after("b").to_a
# => [["a", "b"], ["c"]]

Of course, you can match using regular expressions, ranges, and blocks. I'm not going to show examples of those here because it would be tedious.

Using Enumerable#slice_when

Enumerable#slice_when is a different beast from slice_before and slice_after. Instead of matching a single item in the array, you match a pair of adjacent items.

This means that you can group items based on the "edges" between them.

For example, here we group items based on "nearness" to their adjacent items.

a = [1, 2, 3, 100, 101, 102]

# Create a new group when the difference 
# between two adjacent items is > 10.
a.slice_when do |x, y| 
  (y - x) > 10
end
# => [[1, 2, 3], [100, 101, 102]]

If you're interested in learning more, check out the Ruby Docs for slice_when. They have several great code examples.

Arrays vs Enumerables

I've used arrays in most of the examples above because arrays are easy to understand. You should keep in mind though that you can use slice_before, slice_when and slice_after with any enumerable.

For example, if you had a file containing a bunch of emails, you could split out the individual emails using slice_before. The code below is taken from the docs.

open("mbox") { |f|
  f.slice_before { |line|
    line.start_with? "From "
  }.each { |mail|
    puts mail
  }

And be sure to notice that the slice methods don't return arrays. They return enumerables. That means that you can use map, each and all your other favorite enumerable methods on the. Heck, you could even do another split. :)