Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

choices Logo -> Ruby

7 views
Skip to first unread message

Brian Adkins

unread,
Aug 31, 2007, 8:31:32 PM8/31/07
to
I was doing some Logo browsing and came across Brian Harvey's
"choices" sample:
http://www.cs.berkeley.edu/~bh/logo-sample.html

I was curious what it would look like in Ruby, and I was surprised at
the similarity, so I thought I'd share it.

Of course, given Crockford's comments about JavaScript being closer to
Scheme than Java, I had to port it to JavaScript also, but I had no
desire for a Java version!

Logo:

to choices :menu [:sofar []]
if emptyp :menu [print :sofar stop]
foreach first :menu [(choices butfirst :menu sentence :sofar ?)]
end

choices [[small medium large]
[vanilla [ultra chocolate] lychee [rum raisin] ginger]
[cone cup]]

Ruby:

def choices menu, sofar=[]
if menu.empty?: puts sofar.join(' ')
else menu[0].each {|item| choices(menu[1..-1], sofar + [item]) } end
end

choices [['small', 'medium', 'large'],
['vanilla', 'ultra chocolate', 'lychee', 'rum raisin',
'ginger'],
['cone', 'cup']]

JavaScript:

function choices(menu, sofar) {
if (emptyp(menu)) print(sofar);
else foreach(menu[0], function (x) { choices(menu.slice(1),
sofar.concat(x)); });
}

choices([['small', 'medium', 'large'],
['vanilla', 'ultra chocolate', 'lychee', 'rum raisin',
'ginger'],
['cone', 'cup']], []);

// supporting functions for JavaScript

function emptyp(a) {
return a.length === 0;
}

function print(list) {
foreach(list, function (x) { document.write(x + ' '); });
document.write('<br />');
}

function foreach(arr, f) {
for (var idx in arr) { f(arr[idx]); }
}

Brian Adkins

unread,
Aug 31, 2007, 9:38:10 PM8/31/07
to
On Aug 31, 8:31 pm, Brian Adkins <lojicdot...@gmail.com> wrote:
> I was doing some Logo browsing and came across Brian Harvey's
> "choices" sample:http://www.cs.berkeley.edu/~bh/logo-sample.html
>
> I was curious what it would look like in Ruby, and I was surprised at
> the similarity, so I thought I'd share it.

Wow. I thought I'd crank out a Java version, but it was just too
painful :) I programmed in Java from '96 to '06, but after spending a
year and a half programming Ruby, and now learning Logo/Scheme/Lisp, I
hope I never have to earn a living from Java!

Brian Harvey

unread,
Sep 1, 2007, 1:01:36 AM9/1/07
to
Brian Adkins <lojic...@gmail.com> writes:
>I was doing some Logo browsing and came across Brian Harvey's
>"choices" sample:
>http://www.cs.berkeley.edu/~bh/logo-sample.html
>
>I was curious what it would look like in Ruby, and I was surprised at
>the similarity, so I thought I'd share it.

Wow, talk about synchronicity. Just today I got an email from a former
student who sent me a Python translation of the same example!

Brian Adkins

unread,
Sep 1, 2007, 4:37:58 PM9/1/07
to
On Sep 1, 1:01 am, b...@cs.berkeley.edu (Brian Harvey) wrote:

Well send him over to http://lojic.com/blog/2007/08/31/logo-ruby-javascript
to add it to the collection :)

Brian Adkins

unread,
Sep 2, 2007, 12:43:19 AM9/2/07
to
On Sep 1, 1:01 am, b...@cs.berkeley.edu (Brian Harvey) wrote:
> Wow, talk about synchronicity. Just today I got an email from a former
> student who sent me a Python translation of the same example!

Not sure what your student's Python version looks like, but a friend
of mine just submitted one that's pretty unique:

optstr = lambda x : (type(x)==str)*str(x)+(type(x)!=str)*' '.join(x)
prod1 = lambda elem, vec : map(lambda x : optstr(elem)+'
'+optstr(x), vec)
xprod = lambda vec1, vec2 : reduce(list.__add__,map(lambda elem :
prod1(elem ,vec2), vec1))
choices = lambda x : '\n'.join(reduce(xprod, x))

q = [['small','medium','large'],
['vanilla', ['ultra', 'chocolate'], 'lychee', ['rum', 'raisin'],


'ginger'],
['cone', 'cup']]

print choices(q)

Anyone care to translate this back to Logo? Here's a translation of
the above to Ruby in case anyone is more familiar with that. I had to
write reduce since Ruby doesn't have it :(

def reduce fn, lst
return lst if lst.length < 2
result = fn.call(lst[0],lst[1])
return lst.length == 2 ? result : reduce(fn,
[fn.call(lst[0],lst[1])] + lst[2..-1])
end

optstr = lambda {|x| x.instance_of?(String) ? x : x.join(' ')}
prod1 = lambda {|elem, vec| vec.map {|x| optstr.call(elem)+'
'+optstr.call(x)}}
xprod = lambda {|vec1, vec2| reduce(lambda {|a, b| a+b}, vec1.map {|
elem| prod1.call(elem, vec2)})}
choices = lambda {|x| reduce(xprod, x).join("\n")}

q = [ ['small','large'],
['vanilla', ['ultra', 'chocolate'], ['rum', 'raisin']],
['cone', 'cup'],
[ 'here', 'to go'] ]

puts choices.call(q)

Brian Harvey

unread,
Sep 2, 2007, 11:27:11 AM9/2/07
to
Brian Adkins <lojic...@gmail.com> writes:
>Anyone care to translate this back to Logo?

Sure:

to choices :menu
foreach crossmap "sentence :menu "print
end

That's it -- FOREACH and CROSSMAP are in the UCBLogo library, and SENTENCE,
which does the joining of words, is primitive in every Logo.

To use it, you say

choices [[large medium small]
[vanilla [ultra chocolate] lychee [rum raisin]]
[cone cup]]

I didn't do it this way on my web page partly because I thought it was easier
to understand the other way (using recursion instead of CROSSMAP) and partly
because I think it's the word/sentence data types that *really* give Logo its
advantage here. In those other languages, you're constructing a long string
of characters that includes N-1 spaces, not N spaces, for N words; in Logo,
you don't think about spaces at all, because conceptually you have a sequence
of words, not a sequence of characters.

Another slight advantage we have is that it's CROSSMAP, not just CROSSPRODUCT,
in the Logo library, so I can do the combining of elements and the flattening
of the result in a single step. Otherwise I would have had to say

map [apply "se ?] crossproduct :menu

or something like that.

Brian Adkins

unread,
Sep 2, 2007, 4:32:54 PM9/2/07
to
On Sep 2, 11:27 am, b...@cs.berkeley.edu (Brian Harvey) wrote:

> Brian Adkins <lojicdot...@gmail.com> writes:
> >Anyone care to translate this back to Logo?
>
> Sure:
>
> to choices :menu
> foreach crossmap "sentence :menu "print
> end
>
> That's it -- FOREACH and CROSSMAP are in the UCBLogo library, and SENTENCE,
> which does the joining of words, is primitive in every Logo.

Amazing :) My appreciation of Logo continues to grow. I started out
using it to teach my daughter to program and had the stereotypical
impression of Logo (turtle graphics, etc.), but beginning to work
through CSLS and the manual has truly expanded my impression.

Maybe it's the fact that my comfort zone is with infix syntax
(currently Ruby), but being able to combine prefix and infix seems
quite nice. Although, it does introduce a Perl-like "there's more than
one way to do it" issue.

> To use it, you say
>
> choices [[large medium small]
> [vanilla [ultra chocolate] lychee [rum raisin]]
> [cone cup]]
>
> I didn't do it this way on my web page partly because I thought it was easier
> to understand the other way (using recursion instead of CROSSMAP) and partly
> because I think it's the word/sentence data types that *really* give Logo its
> advantage here. In those other languages, you're constructing a long string
> of characters that includes N-1 spaces, not N spaces, for N words; in Logo,
> you don't think about spaces at all, because conceptually you have a sequence
> of words, not a sequence of characters.

I agree about the convenience of sentence and word. Has anyone created
a Lisp package to allow programming with UCBLogo in Common Lisp? I'm
still very much a newbie to Logo and Lisp, but from my reading, it
seems Common Lisp's macro capabilities would handle this. If one
doesn't exist, it sounds like a good learning project for me. My
interest in this would be to gain the efficiency of a Lisp compiler.

Brian Harvey

unread,
Sep 3, 2007, 1:03:36 AM9/3/07
to
Brian Adkins <lojic...@gmail.com> writes:
>I agree about the convenience of sentence and word. Has anyone created
>a Lisp package to allow programming with UCBLogo in Common Lisp?

I don't know about CL, but there's a word/sentence package for Scheme:

http://www.cs.berkeley.edu/~bh/downloads/simply/simply.scm

Andru Luvisi

unread,
Sep 4, 2007, 2:31:08 PM9/4/07
to
>>>>> "Brian" == Brian Harvey <b...@cs.berkeley.edu> writes:

Brian> to choices :menu
Brian> foreach crossmap "sentence :menu "print
Brian> end

If each sublist has two items in it, and the number of sublists is n,
then there will be 2^n lines to print out. It seems to me that the
recursive version is using O(n) storage while the crossmap version is
using O(2^n) storage (in the case of sublists of length 2). I suspect
that for any given memory size, it wouldn't be difficult to find an n
such that the recursive version would work but the crossmap version
would not work.

Andru
--
Andru Luvisi

Quote Of The Moment:
If we don't succeed, we run the risk of failure.

Brian Harvey

unread,
Sep 4, 2007, 10:35:17 PM9/4/07
to
Andru Luvisi <luv...@sonoma.edu> writes:
>If each sublist has two items in it, and the number of sublists is n,
>then there will be 2^n lines to print out. It seems to me that the
>recursive version is using O(n) storage while the crossmap version is
>using O(2^n) storage (in the case of sublists of length 2).

Indeed, that's another reason to prefer the recursive version. I never
remember to do that sort of analysis; I'm secretly a hacker, not a computer
scientist, at heart. :-) But the crossmap version is the direct translation
of that other version that started this subthread.

0 new messages