Ruby Tuesday – Array Decomposition

Today’s Ruby Tuesday takes a look at Array Decomposition.

Ruby gives you some ability to destructure, or decompose, an Array into its component pieces.

Say we have an Array which represents a point in two-dimensional Cartesian space, we can decompose that array into its x and y coordinates, by doing an assignment of x and y to the point represented by the given Array, if we wrap them in parenthesis.

(x, y) = [1, -1]
# => [1, -1]
x
# => 1
y
# => -1

If we want to decompose only part of the Array, and save off anything at the end, we can use a * before the last variable in the assignment.

(_, second, *rest) = [:a, :b, :c, :d]
# => [:a, :b, :c, :d]

The capturing even works, if there are less items in the array than there are variables to decompose into.

(_, second, *rest) = [:a]
# => [:a]
second
# => nil
rest
# => []

We can also use Array Decomposition in method arguments to decompose a passed in Array to individual variable for specific elements.

def cartesian_point((x, y))
  puts "x: #{x}, y: #{y}"
end
# => :cartesian_point

cartesian_point([1, -1])
# x: 1, y: -1
# => nil

Above I mentioned that we can capture the “rest” of the Array we aren’t wanting to decompose at this point by using a * (called the “splat” operator).

(head, *rest) = [1, 2, 3, 4, 5]
# => [1, 2, 3, 4, 5]
head
# => 1
rest
# => [2, 3, 4, 5]

So let’s see what we get when we use the splat to capture the rest of an Array that is smaller than what is at the end.

(first, *tail) = [1]
# => [1]
first
# => 1
tail
# => []

We get an empty list.

Let’s see what we get on the decomposition for an empty list, into the items, and a capturing “rest” variable.

(h, *t) = []
# => []
h
# => nil
t
# => []

We get a nil as part of the binding, and we still get an empty array the “rest” of the items.

This means we can use destructing to handle recursing a list, and have a guard clause that checks if we received and empty Array by checking if the head of the Array is nil, and then just recurse by passing in the tail as the argument when recursing.

def recurse_list((head, *tail))
  if (head != nil)
    puts head
    recurse_list(tail)
  else
    puts "all done"
  end
end
# => :recurse_list

recurse_list([1, 2, 3, 4, 5, 6, 7])
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# all done
# => nil

–Proctor