Pascal's Triangle is a fun sequence. Here's what it looks like: It represents a "triangular array of the binomial coefficients". Each row contains an increases in size and contains numbers which can be derived by adding adjacent members of the previous row. We can model this in Ruby as an array of arrays. The first array member is ``. Each successive array (or "row") will increase in size, and each array member will be the sum of the member at the same index `n` in the `k-1` row, where `k` is the current row, and the `n-1` member in the `k-1` row or 0. In other words, add the number above and the number above to the left (or zero) to get the current number. We can express the first five rows as follows:

``````[
,
[1, 1],
[1, 2, 1],
[1, 3, 3, 1],
[1, 4, 6, 4, 1]
]
``````

Let's solve this with Ruby. While there are a number of approaches to generating Pascal's Triangle, including both recursive and iterative solutions, we'll explore an approach to treating this as an enumerable.

Starting with the first row, ``, we can write a Ruby method that will generate the next row `[1, 1]`. Let's write this in a way so it will be possible to generate any row `k` from row `k-1`. Here's what the usage of this method will look like:

``````pascal_row()
=> [1, 1]
pascal_row([1, 3, 3, 1])
=> [1, 4, 6, 4, 1]
``````

We'll use Test-Driven Development to validate our implementation starting with a few assertions to ensure the first several rows are returned as expected.

``````require 'minitest/autorun'

def pascals_row(row)
# yo no se
end

class TestPascalsTriangle < Minitest::Test
def test_pascals_row
assert_equal [1, 1], pascals_row()
assert_equal [1, 2, 1], pascals_row([1, 1])
assert_equal [1, 3, 3, 1], pascals_row([1, 2, 1])
assert_equal [1, 4, 6, 4, 1], pascals_row([1, 3, 3, 1])
assert_equal [1, 5, 10, 10, 5, 1], pascals_row([1, 4, 6, 4, 1])
end
end
``````

Our failing test run will look something like this:

``````\$ ruby pascals_triangle_test.rb
Run options: --seed 45117

# Running:

F

Finished in 0.001035s, 966.0380 runs/s, 966.0380 assertions/s.

1) Failure:
TestPascalsTriangle#test_pascals_row [code/pascals_row_test.rb:8]:
Expected: [1, 1]
Actual: nil

1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
``````

To extract a general method, let's deconstruct a single row, the fifth: `[1, 4, 6, 4, 1]`. Each member is the sum of `n` and `n-1` from the previous row, `[1, 3, 3, 1]`. We substitute zero when `n` or `n-1` is missing. Therefore, we can rewrite the fifth row as

``````[(0 + 1), (1 + 3), (3 + 3), (3 + 1), (1 + 0)]
=> [1, 4, 6, 4, 1]
``````

We can also represent this as a nested array of number pairs then collect the sum of each pair like so:

``````[[0, 1], [1, 3], [3, 3], [3, 1], [1, 0]].collect { |a, b| a + b }
=> [1, 4, 6, 4, 1]
``````

Looking closely at the pairs, taking just first members of each pair form the array we get `[0, 1, 3, 3, 1]`. The second members of each pair are `[1, 3, 3, 1, 0]`. Written differently, the groups are `( + [1, 3, 3, 1])` and `([1, 3, 3, 1] + )`. In each we see the members of row four, `[1, 3, 3, 1]` augmented by prepending zero or appending zero respectively.

Getting the nested array pairs from these groups is perfect for the `Enumerable#zip` method: `zip` groups members of given arrays by position. Therefore, we can "zip" `[0, 1, 3, 3, 1]` with `[1, 3, 3, 1, 0]` to produce `[[0, 1], [1, 3], [3, 3], [3, 1], [1, 0]]`:

``````[0, 1, 3, 3, 1].zip([1, 3, 3, 1, 0])
=> [[0, 1], [1, 3], [3, 3], [3, 1], [1, 0]]
``````

Let's extract a variable to represent row four:

``````row = [1, 3, 3, 1]
( + row).zip(row + )
=> [[0, 1], [1, 3], [3, 3], [3, 1], [1, 0]]
``````

Putting it altogether, we can now produce the fifth row from the fourth:

``````row = [1, 3, 3, 1]
( + row).zip(row + ).collect { |a, b| a + b }
=> [1, 4, 6, 4, 1]
``````

Let's confirm this expression works with for other row conversions:

``````row = 
( + row).zip(row + ).collect { |a, b| a + b }
=> [1, 1]

row = [1, 1]
( + row).zip(row + ).collect { |a, b| a + b }
=> [1, 2, 1]

row = [1, 2, 1]
( + row).zip(row + ).collect { |a, b| a + b }
=> [1, 3, 3, 1]
``````

Yes! We now have the implementation for our method to produce any row for Pascal's Triangle given the preceding row:

``````def pascal_row(row)
( + row).zip(row + ).collect { |a, b| a + b }
end
``````

Plugging in this implementation...

``````require 'minitest/autorun'

def pascals_row(row)
( + row).zip(row + ).collect { |a, b| a + b }
end

class TestPascalsTriangle < Minitest::Test
def test_pascals_row
assert_equal [1, 1], pascals_row()
assert_equal [1, 2, 1], pascals_row([1, 1])
assert_equal [1, 3, 3, 1], pascals_row([1, 2, 1])
assert_equal [1, 4, 6, 4, 1], pascals_row([1, 3, 3, 1])
assert_equal [1, 5, 10, 10, 5, 1], pascals_row([1, 4, 6, 4, 1])
end
end
``````

... we get passing tests

``````\$ ruby code/pascals_row_test.rb
Run options: --seed 61039

# Running:

.

Finished in 0.001020s, 980.6882 runs/s, 4903.4412 assertions/s.

1 runs, 5 assertions, 0 failures, 0 errors, 0 skips
``````

### In Sequence

Now that we have a method to convert one row to its successor, we have a nice building block for an infinite sequence. We can call `pascals_row` repeatedly to generate the triangle rows infinitely. I previously wrote about creating infinite sequences in Ruby with Enumerator and we'll apply this approach here.

We'd like to be able to call a method and enumerate the rows representing Pascal's Triangle as we would for an array. Since we'll be using `Enumerator`, which exposes the `Enumerable` api, we can use external enumeration with `Enumerator#next` to extract rows in succession. Let's rewrite our previous test to demonstrate:

``````require 'minitest/autorun'

def pascals_triangle
# Enumerator, please
end

def pascals_row(row)
( + row).zip(row + ).collect { |a, b| a + b }
end

class TestPascalsTriangle < Minitest::Test
def test_pascals_triangle
rows = pascals_triangle
assert_equal , rows.next
assert_equal [1, 1], rows.next
assert_equal [1, 2, 1], rows.next
assert_equal [1, 3, 3, 1], rows.next
assert_equal [1, 4, 6, 4, 1], rows.next
assert_equal [1, 5, 10, 10, 5, 1], rows.next
end
end
``````

With no implementation, the test fails on calling `next`:

``````\$ ruby pascals_triangle_test.rb
Run options: --seed 62081

# Running:

E

Finished in 0.000949s, 1053.4832 runs/s, 0.0000 assertions/s.

1) Error:
TestPascalsTriangle#test_pascals_rows:
NoMethodError: undefined method `next' for nil:NilClass
code/pascals_row_test.rb:14:in `test_pascals_rows'

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
``````

Our enumerator needs to call the `pascals_row` method repeatedly with the previous result. We'll maintain the current row as `current`, pass this as the arg to `pascals_row`, replace it with the result and repeat in a loop. Returning the `Enumerator` from the method will allow the caller to control how it's enumerated.

Here's what the implementation could look like:

``````current = 
Enumerator.new do |y|
loop do
y << current
current = pascals_row(current)
end
end
``````

Let's plug this into our method and rerun:

``````require 'minitest/autorun'

def pascals_triangle(row = )
current = row
Enumerator.new do |y|
loop do
y << current
current = pascals_row(current)
end
end
end

def pascals_row(row)
( + row).zip(row + ).collect { |a, b| a + b }
end

class TestPascalsTriangle < Minitest::Test
def test_pascals_rows
rows = pascals_triangle
assert_equal , rows.next
assert_equal [1, 1], rows.next
assert_equal [1, 2, 1], rows.next
assert_equal [1, 3, 3, 1], rows.next
assert_equal [1, 4, 6, 4, 1], rows.next
assert_equal [1, 5, 10, 10, 5, 1], rows.next
end
end
``````

The exciting thing about this implementation is we can treat our sequence like a collection and call enumerable methods. We can also chain enumerable methods like `Enumerator#with_index` and `Enumerator#each` to print a "pretty" triangle of each row with its row number.

``````pascals_triangle.with_index(1).take(10).each do |row, i|
puts "%d:%#{20+(row.inspect.length/2)}s" % [i, row.inspect]
end

1:                  
2:                 [1, 1]
3:               [1, 2, 1]
4:              [1, 3, 3, 1]
5:            [1, 4, 6, 4, 1]
6:          [1, 5, 10, 10, 5, 1]
7:        [1, 6, 15, 20, 15, 6, 1]
8:      [1, 7, 21, 35, 35, 21, 7, 1]
9:    [1, 8, 28, 56, 70, 56, 28, 8, 1]
10: [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

=>
[[, 1],
[[1, 1], 2],
[[1, 2, 1], 3],
[[1, 3, 3, 1], 4],
[[1, 4, 6, 4, 1], 5],
[[1, 5, 10, 10, 5, 1], 6],
[[1, 6, 15, 20, 15, 6, 1], 7],
[[1, 7, 21, 35, 35, 21, 7, 1], 8],
[[1, 8, 28, 56, 70, 56, 28, 8, 1], 9],
[[1, 9, 36, 84, 126, 126, 84, 36, 9, 1], 10]]
``````

Notice the return value combines the each row with its index, an interesting outcome of how enumerator chains can augment the enumerated values.

We can also take advantage of `Enumerator#lazy` to operate on rows without relying on eager evaluation. Here we use a lazy enumerator chain to demonstrate that the sum of numbers in each row is 2^n:

``````pascals_triangle.lazy.map { |row| Math.log(row.reduce(:+), 2) }.take_while { |n| n < 9 }.force
=> [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
``````

Enumerators allow us to provide an enumerable interface to generated data in much the same way we do for collections. Try test-driving an enumerable implementation of other sequences on your own.

Discuss it on Twitter · Part of the Enumerable series. Published on Nov 30, 2015

## Need help with Webpack on Rails?

My name is Ross Kaffenberger.

Webpacker now ships by default in Rails 6 but there's no need to stress. I'm putting together a course to help you master Webpack on Rails.

I teach full stack web developers about frontend tools and performance, especially at the intersection of JavaScript and Ruby on Rails.

Subscribe to stay in the loop.

## More posts

Handling Paginated Resources in Ruby

Extracting data from an API can get messy once pagination is involved. We'll look at a few ways of dealing with this complexity, including "recursive each" and Kernel#to_enum method to enumerate paginated resources

Use inverse_of

ActiveRecord will try hard to infer the inverse relation for your associations, but you may benefit from setting the inverse_of option wherever possible