Saturday, June 02, 2007

Let's eval some strings

List comprehensions are neat, but ruby doesn't have em. That's ok, we've got string eval!


module Enumerable
def concat_map(&f)
inject([]) { |a, b| a.concat(f.call(b)) }
end
end


def guard( b )
if b
[nil]
else
[]
end
end

def r( v )
[v]
end

def comp( s, b = binding )
before_bar, after_bar = s.split("|")
before_bar.gsub!(/\A\s*\[/, '')
after_bar.gsub!(/\]\s*\z/, '')
components = after_bar.split(/;/)
gen_exprs, guard_exprs = components.partition { |e| e =~ /<-/ }
final = "r(#{before_bar})"
final = guard_exprs.reverse.inject(final) { |s, e| "guard(#{e}).concat_map { #{s} }" }
final = gen_exprs.reverse.inject(final) { |s, e|
var, expr = e.split("<-").map { |c| c.strip }
"(#{expr}).concat_map { |#{var}| #{s} }"
}
eval(final,b)
#final
end


See, easy?

Now we can do things like:

def factors( n )
comp "[ [x,y] | x <- (1..n) ; y <- (1..n) ; x * y == n ]", binding
end


If we do factors 25 we get [[1, 25], [5, 5], [25, 1]].

We can also write "quicksort" (being that not so great list comprehension quicksort that I'm sure you've seen before:"

def qsort( a )
if a.length < 2
a
else
pivot = a.first
tail = a[1..-1]
b = binding
qsort(comp("[ x | x <- tail ; x < pivot ]", b)) + [pivot] + qsort(comp("[ y | y <- tail ; y >= pivot ]", b))
end
end


And of course we write a cartesian product function:


def cart_prod(a, b)
comp "[ [x,y] | x <- a ; y <- b ]", binding
end


By the way the code that this expands to is:

(a).concat_map { |x| (b).concat_map { |y| r( [x,y] ) } }


Of course we could've written all these functions before, w/o list comprehensions but this was more fun.

No comments: