“Matthew M.” [email protected] writes:
(You’ll also see people here going the opposite route, to make it as
fast as possible no holds bars… both are optimization problems.)
I’m now tempted to /really/ go the opposite route, and make the
calculation as painfully slow as possible, and use up gobs of memory
while I’m at it.
Something like this monstrosity.
It’s not just expensive memory-wise, creating temporary two-element
arrays all over the place, and pre-filling the hash with a load of
different singleton objects, it’s also expensive CPU-wise: the code
depends on a slew of callcc invocations, which are slow to begin with
and to top that the algorithm itself is fundamentally O(n*2^n), so will
take ages on even the fastest hardware.
On my fast machine, I really didn’t want to run this for more than 20
rows. I’m not letting this code anywhere near my slow machine.
$ time ruby painful_ppt.rb 20 > /dev/null
real 0m54.559s
user 0m53.421s
sys 0m0.921s
For comparison, on the same machine my second solution gives these
times:
$ time ruby ppt.rb 1000 > /dev/null
real 0m27.440s
user 0m25.186s
sys 0m2.171s
Despite its usefulness as an example of how NOT to program, though, I
do think that this code illustrates a nice feature of Pascal’s
triangle: namely, that the number at any spot represents the number of
paths to that spot from the top point in the triangle, if you only
move down-and-right or down-and-left.
================ painful_ppt.rb ===============
#!ruby
$coin_spots = []
This function returns true the first time, but sets
things up so that reflip causes it to return false
to the same spot in the program.
def flip_coin
does amb(true,false), but without all the machinery
callcc { |c|
$coin_spots.push(c)
return true
}
false
end
Go back to the last time flip_coin returned true, and
make it return false this time.
def reflip
$coin_spots.pop.call
end
def pp_pascal(n)
ptri = {}
n.times { |p|
(2*n - 1).times { |q|
g = Object.new
class << g
def coerce(o); 0.coerce(o); end
def +(o); o; end
def to_s; “”; end
def inspect; “g”; end
end
ptri[[p,q]] = g
}
}
So now ptri has this grid of “g” objects, from
[(0 … n), (0 … 2*n-1)]
now walk down the triangle, starting at [0,n-1] and
at each step flipping a coin to either increase or decrease
the “q” coordinate.
if flip_coin then
(0 … n).inject(n-1){ |q,p|
ptri[[p,q]] = ptri[[p,q]]+1
q + (flip_coin ? 1 : -1)
}
reflip
else
width = (ptri[[n-1,n-1]]+ptri[[n-1,n]]).to_s.length
fmt = [“%#{width}s”] * 2 * n
fmt.pop
fmt[0] = “%1s”
fmt[-1] = “%s”
n.times { |p|
pascal_row = (0 … fmt.size).map{|q| ptri[[p,q]]}
pascal_row.zip(fmt){|v,f| print(f%v)}
puts “”
}
end
end
if FILE == $0
n = 4
n = ARGV[0].to_i if ARGV[0]
pp_pascal(n)
end
END