code.life

Rose Weixel’s technical blog

Looking for Something? Just #find It!

Searching through nested data structures can be downright messy. Even saying “nested data structures” sounds complicated. Luckily, Ruby has some powerful Enumerable methods that can simplify our lives (or at least our code).

Let’s say you’ve been programming for hours and suddenly realize your stomach is growling at you (why does this happen so often?). Here is how you might search for some delicious food, if you happened to be searching inside an array filled with hashes. Just humor me for the sake of example.

Here is our restaurants array:

1
2
3
4
5
6
restaurants = [
  {:name => "The Decent Diner", :rating => "average"},
  {:name => "The GoGo Grill", :rating => "delicious"},
  {:name => "Emporium of Mystery Meat", :rating => "poor"},
  {:name => "Dig Inn Seasonal Market", :rating => "delicious"}
]

So, we want to get the name of a restaurant whose :rating is "delicious". We could do something like this:

1
2
3
4
5
6
7
8
9
def find_delicious_food(restaurants)
    restaurants.each do |restaurant_info|
    restaurant_info.values.each do |value|
      if value == "delicious"
        return restaurant_info[:name]
      end
    end
  end
end

If we call this method on our restaurants hash, it will return the value “The GoGo Grill”. Sounds good to me. But in order to get there we needed a return inside an if statement inside an each loop inside another each loop. Not pretty.

There is a better way. The find method! Behold:

1
2
3
def find_delicious_food(restaurants)
  restaurants.find{|restaurant| restaurant[:rating] == "delicious"}[:name]
end

Just one line of code seems to do the same thing as its ugly predecessor. Let’s look at it more closely. Here is what find does:

  • It passes each element of the object on which it was called to a block.
  • It returns the first element for which the block evaluates to true.
  • If none of the elements return true for the given block, it returns nil.

As it turns out, this nil value could cause some problems for our find_delicious_food method. If there are no restaurants in the hash with a :rating of "delicious", our code breaks. We would get an error, because Ruby cannot make any sense out of nil[:name].

Here is the method refactored to avoid that error:

1
2
3
4
5
6
def find_delicious_food(restaurants)
  good_restaurant = restaurants.find{|restaurant| restaurant[:rating] == "delicious"}
  if good_restaurant
    return good_restaurant[:name]
  end
end

Sure, it may not be as cute and little as our one-line method, but it will never break and it’s still a lot prettier than loops inside loops.

So when you are searching for that one thing you need inside of some nasty nesting, just find it!

cheese.slice

When learning a programming language as natural as Ruby, the syntax inevitably seeps into our thoughts and alters the way we conceptualize everything from the complex to the mundane. Thus, the title of this blog (and, indeed, this post).

It is easy for this to happen, in part because Ruby has such an extensive library of methods whose names and behaviors make them ideal for modeling real life. One such method is slice.

The method slice can be called on an array (or an object that acts like an array, like a string). It literally returns a “slice” of the object on which it is called. The original object is left intact.

> cheese = ['gouda', 'muenster', 'provolone', 'manchego', 'brie']
> my_slices = cheese.slice(1, 2)
=> ["muenster", "provolone"] 
> cheese
=> ["gouda", "muenster", "provolone", "manchego", "brie"]

If you need to take more drastic measures, slice! will modify the original object, removing (and returning) everything you sliced out.

> cheese = ['gouda', 'muenster', 'provolone', 'manchego', 'brie']
> my_slices = cheese.slice!(1, 2)
=> ["muenster", "provolone"] 
> cheese
=> ["gouda", "manchego", "brie"]

Here are three different ways to use slice (slice! can also be invoked in these ways, and both can be used with arrays and strings):

  • array.slice(index) returns the object at array[index], or nil if there is no object there to return.

  • array.slice(start, length) returns a new array containing the elements of array starting at start and continuing for length elements (as shown in the code examples above). If there are no objects there, it returns nil.1

  • array.slice(range) returns a new array containing the objects at array[range], or nil.

For more on this and other Ruby Array methods, see the documentation.


  1. There are some special cases that return an empty array. See the documentation.