#!/usr/bin/env ruby
#
# pascal.rb - Pretty-print Pascal's Triangle
#
# Copyright (C) 2006 Louis J. Scoras
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation;
# either version 2 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# [http://www.gnu.org/copyleft/gpl.html]
# Here's my solution for Ruby Quiz 84. I realize that you can calculate the
# last row in the expansion using the binomial theorem, but you'll need to
# iterate through each row anyway to print the numbers out, so I just went
# with a caching method.
#
# I was going to implement a persistant disk cache, but alas I find myself
# currently stuck on a windows machine, which takes all the fun out of it--
# for me at least :)
#
# Thanks to Dirk and James for a great quiz.
require 'enumerator'
class PascalsRow
attr_reader :numbers
def initialize(numbers = [1], depth = 0)
@numbers = numbers
@depth = depth
end
def even?
@depth % 2 == 0
end
def calculate_next
sums = if @numbers.length == 1
[1]
else
@numbers.enum_cons(2).collect {|j,k| j+k}
end
sums = sums << sums.last if even? && @depth != 0
self.class.new([1] + sums, @depth + 1)
end
def canonical
half = @numbers.dup
half.pop
half.pop unless even?
@numbers + half.reverse
end
def to_s
canonical.join(' ')
end
def padded_string(word_size)
canonical.collect do |n|
digits = n.to_s
padding = ((word_size - digits.length) / 2.0).ceil
make_padding(padding) +
digits +
make_padding(word_size - digits.length - padding, 1)
end.join
end
private
def make_padding(size, min_length = 0)
' ' * [size, min_length].max
end
end
class PascalsTriangle
def initialize
@rows = [PascalsRow.new]
end
def iterate_rows(n, &block)
current_row = @rows.first
block.call(current_row) if block
n.times do |n|
current_row = if @rows[n+1]
@rows[n+1]
else
current_row = current_row.calculate_next
@rows[n+1] = current_row
end
block.call(current_row) if block
end
current_row
end
def pretty(n)
last_line = iterate_rows(n)
word_size = last_line.numbers.last.to_s.length
width = last_line.padded_string(word_size).length
iterate_rows(n) do |row|
padded = row.padded_string(word_size)
puts (' ' * ((width - padded.length) / 2)) + padded
end
end
end
pt = PascalsTriangle.new
pt.pretty((ARGV[0] ? ARGV[0].to_i : 10) - 1)