Slicing and Dicing Ruby Enumerables

Have you ever needed to group items in an array, or lines in a file? In this post we'll discuss a few often-overlooked Enumerable methods that let you do just that.

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. :)

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    More articles by Starr Horne
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial