# \[SUMMARY\] One-Liners (#113)

**URL:** <https://rubytalk.org/t/summary-one-liners-113/35391>\
**Category:** ruby-talk\
**Created:** [15 February 2007 13:32 UTC](https://rubytalk.org/t/summary-one-liners-113/35391 "2007-02-15T13:32:10Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [15 February 2007 13:32 UTC](https://rubytalk.org/t/summary-one-liners-113/35391/1 "2007-02-15T13:32:10Z")

</div>

If you followed the solutions of this quiz you should have seen a little bit of  
everything. We saw clever algorithms, Ruby idioms, some golfing, and even a  
mistake or two. Follow along and I'll give you the highlights.

The problems of adding commas to numbers, shuffling Arrays, and resolving class  
names were selected because I see them pretty regularly on Ruby Talk. Because  
of that, I figured most of us would know those idioms pretty well. There were a  
couple of surprises though, so I'm glad I decided to include them.

For adding commas to numbers, several of us used some variant of a pretty famous  
reverse(), use a regular expression, and reverse() trick. Here's one such  
solution by Carl Porth:

&nbsp;&nbsp;quiz.to\_s.reverse.scan(/(?:\d\*\.)?\d{1,3}-?/).join(',').reverse

I've always liked this problem and this trick to solve it, because it reminds me  
of one of my favorite rules of computing: when you're hopelessly stuck, reverse  
the data. I can't remember who taught me that rule now and I have no earthly  
idea why it works, but it sure helps a lot.

Take this problem for example. You need to find numbers in groups of three and  
it's natural to turn to regular expressions for this. If you attack the data  
head-on though, it's a heck of a problem. The left-most digit group might be  
one, two, or three long, and you have to be aware of that decimal that ends  
processing. It's a mess, but one call to reverse() cleans it right up.

Now the decimal will be before the section we want to work with and, as we see  
in Carl's code, you can skip right over it. From there the digit groups will  
line up perfectly as long as you always try to greedily grab three or less.  
Carl's code does this, picking the String apart with scan() and then join()ing  
the groups with added commas. I think it's clever, elegant, and Rubyish.

I'm serious about that reverse()ing the data trick too. Try it out next time  
you are struggling.

Shuffling Arrays surprised me. More that one person sent in:

&nbsp;&nbsp;quiz.sort{rand}

Yikes!

Does that even work? Let's ask IRb:

&nbsp;&nbsp;\>\> quiz = (1..10).to\_a  
&nbsp;&nbsp;=\> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  
&nbsp;&nbsp;\>\> quiz.sort{rand}  
&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
&nbsp;&nbsp;\>\> quiz.sort{rand}  
&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
&nbsp;&nbsp;\>\> quiz.sort{rand}  
&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
&nbsp;&nbsp;\>\> quiz.sort{rand}  
&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
&nbsp;&nbsp;\>\> quiz.sort{rand}  
&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]

That's not looking too random to me.

Let's think about this. What does the above code do. sort() compares elements  
of the Array, arranging them based on the returned result. We are suppose to  
return a result of -1, 0, or 1 to indicate how the elements compare. However,  
rand() returns a float between 0.0 and 1.0. Ruby considers anything over 0.0 to  
be the 1 response, so most of the rand calls give this. You can get a 0.0  
result from time to time, but it will be a loner in a sea of 1s.

So what is the above code actually trying to do? It's trying to compare a  
selection of random numbers and sort on that instead. Writing the process out  
longhand it is:

&nbsp;&nbsp;quiz.map { |e| [rand, e] }.sort.map { |arr| arr.last }

We change the elements into Arrays of random numbers and the element itself. We  
then sort() those. Arrays compare themselves element by element, so they will  
always start by comparing the random numbers. Then we just drop the random  
numbers back out of the equation.

Luckily, Ruby has a shorthand version of this process, known as the Schwartzian  
Transform:

&nbsp;&nbsp;quiz.sort\_by { rand }

That's the popular Ruby idiom for randomizing an Array. Make sure you use  
sort\_by() instead of sort() when that's your intent.

Resolving class names is a surprisingly complex issue. Classes can be nested  
inside other classes and modules, as the quiz example showed. Inside that  
nested scope we don't need to use the full name of the constant either.  
Finally, don't forget things like autoload()ing and const\_missing() which  
further complicate the issue.

I'll probably get hate mail for this, but if you want to handle all of those  
cases with one easy bit of code I recommend this "cheating" solution from  
Phrogz:

&nbsp;&nbsp;eval(quiz)

This asks Ruby to lookup the constant(s) and she will always remember to handle  
all of the edge cases for us. I know we always say eval() is evil and you  
should avoid it, but this instance can be one of the exceptions, in my opinion.  
Of course, you must sanitize the input if you are taking it from a user to  
ensure they don't sneak in any scary code, but even with that added overhead  
it's still easier and more accurate than trying to do all the work yourself.

If you just can't get over the eval() call though, you can use something like:

&nbsp;&nbsp;quiz.split("::").inject(Object) { |par, const| par.const\_get(const) }

This doesn't address all of the edge cases, but it often works as long as you  
are working with fully qualified names.

Skipping ahead a bit, let's talk about the last three questions in the quiz.  
First, reading a random line from a file. This one is just a fun algorithm  
problem. Alex Young offered this solution:

&nbsp;&nbsp;(a=quiz.readlines)[rand(a.size)]

That pulls all the lines into an Array and randomly selects one. You can even  
eliminate the assignment to the Array as Aleksandr Lossenko does:

&nbsp;&nbsp;quiz.readlines[rand(quiz.lineno)]

Same thing, but here we get the line count from the File object and thus don't  
need a local variable.

The problem with both of these solutions is when we run them on a very large  
file. Slurping all of that data into memory may prove to be too much.

You could do it without slurping by reading the File twice. You could read the  
whole File to get a line count, choose a random line, then read back to that  
line. That's too much busy work though.

There is an algorithm for reading the File just once and coming out of it with a  
random line. I sent the Ruby version of this algorithm in as my solution:

&nbsp;&nbsp;quiz.inject { |choice, line| rand \< 1/quiz.lineno.to\_f ? line : choice }

The trick is to select a line by random chance, based on the number of lines  
we've read so far. The first line we will select 100% of the time. 50% of the  
time we will then replace it with the second line. 33.3% of the time we will  
replace that choice with the third line. Etc. The end result will be that we  
have fairly selected a random line just by reading through the File once.

The wondrous number problem was more an exploration of Ruby's syntax than  
anything else. I used:

&nbsp;&nbsp;Hash.new { |h, n| n == 1 ? [1] : [n] + h[n % 2 == 0 ? n/2 : n\*3+1] }[quiz]

This is really an abuse of Ruby's Hash syntax though. I don't ever actually  
store the values in the Hash since that would be pointless for this problem.  
Instead I am using a Hash as a nested lambda(), clarified by this translation  
from Ken Bloom:

&nbsp;&nbsp;(h=lambda {|n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n\*3+1] })[quiz]

A solution to this problem probably belongs on more than one line though as  
these both feel like golfing to me. I liked this two step offering from  
Aleksandr Lossenko:

&nbsp;&nbsp;a=[quiz]; a \<\< (a.last%2==1 ? a.last\*3+1 : a.last/2) while a.last!=1

The final question, about nested Hashes, is actually what inspired me to make  
this quiz. The question was raised recently on the Ruport mailing list and it  
took Gregory Brown and myself working together a surprising amount of time to  
land on a solution just like this one from Carl Porth:

&nbsp;&nbsp;quiz.reverse.inject { |mem, var| {var =\> mem} }

Those of you who figured that out quickly deserve a pat on the back. You are  
smarter than me.

Again we see my favorite trick of reverse()ing the data. This time though, the  
mental block for me was figuring out that it's easier if you don't initialize  
the inject() block. This causes inject() to start the mem variable as the first  
String in the Array, eliminating the lone-entry edge case. The problem is  
trivial from there, but that was a counter-intuitive leap for my little brain.

I'll leave you to glance over the other four problems on your own, but do give  
them a look. There was no shortage of great material this week.

My thanks to all of you who just couldn't stop fiddling with these problems,  
generating great ideas all the while. Yes, I'm talking to you Robert and Ken.

Tomorrow we've got a problem for all you Bingo nuts out there...

---

<div class="post-metadata">

**Author:** ![Benedikt\_Heinen](https://avatars.discourse-cdn.com/v4/letter/b/258eb7/32.png) [@Benedikt\_Heinen](https://rubytalk.org/u/Benedikt_Heinen)\
**Post date:** [15 February 2007 14:37 UTC](https://rubytalk.org/t/summary-one-liners-113/35391/2 "2007-02-15T14:37:42Z")

</div>

> &nbsp;&nbsp;quiz.sort{rand}

> Does that even work? Let's ask IRb:
> 
> &nbsp;&nbsp;\>\> quiz = (1..10).to\_a  
> &nbsp;&nbsp;=\> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  
> &nbsp;&nbsp;\>\> quiz.sort{rand}  
> &nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
> &nbsp;&nbsp;\>\> quiz.sort{rand}  
> &nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]

[...]

> That's not looking too random to me.
> 
> Let's think about this. What does the above code do. sort() compares elements  
> of the Array, arranging them based on the returned result. We are suppose to  
> return a result of -1, 0, or 1 to indicate how the elements compare. However,  
> rand() returns a float between 0.0 and 1.0. Ruby considers anything over 0.0 to  
> be the 1 response, so most of the rand calls give this. You can get a 0.0  
> result from time to time, but it will be a loner in a sea of 1s.
> 
> So what is the above code actually trying to do? It's trying to compare a  
> selection of random numbers and sort on that instead. Writing the process out  
> longhand it is:
> 
> &nbsp;&nbsp;quiz.map { |e| [rand, e] }.sort.map { |arr| arr.last }

Hmmm... Not being aware of sort\_by before, why this complicated? What's wrong with

&nbsp;&nbsp;&nbsp;quiz.sort{rand-0.5}

?

(which puts your random numbers from their range of 0.0-1.0 to a new range of -0.5 and 0.5 -- if you find it aesthetically more pleasing, you could of course use

&nbsp;&nbsp;&nbsp;quiz.sort{2\*rand-1.0}

to make the range -1.0..1.0.

&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [4, 2, 3, 9, 7, 8, 1, 6, 5, 10]  
&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [9, 6, 2, 5, 1, 4, 3, 8, 7, 10]  
&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [3, 8, 10, 6, 9, 5, 1, 7, 2, 4]  
&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [4, 7, 10, 1, 9, 5, 2, 8, 6, 3]  
&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [8, 5, 10, 9, 2, 1, 7, 3, 4, 6]  
&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [6, 9, 2, 8, 1, 7, 4, 10, 3, 5]  
&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
&nbsp;&nbsp;&nbsp;=\> [3, 4, 5, 10, 7, 1, 8, 6, 2, 9]

It does seem to find your "random" criteria, is shorter and uses less calls...

> **···**
>
> On Thu, 15 Feb 2007, Ruby Quiz wrote:

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [15 February 2007 14:53 UTC](https://rubytalk.org/t/summary-one-liners-113/35391/3 "2007-02-15T14:53:50Z")

</div>

> > &nbsp;&nbsp;quiz.sort{rand}
> 
> > Does that even work? Let's ask IRb:
> > 
> > &nbsp;&nbsp;\>\> quiz = (1..10).to\_a  
> > &nbsp;&nbsp;=\> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  
> > &nbsp;&nbsp;\>\> quiz.sort{rand}  
> > &nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
> > &nbsp;&nbsp;\>\> quiz.sort{rand}  
> > &nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
> 
> [...]
> 
> > That's not looking too random to me.  
> > Let's think about this. What does the above code do. sort() compares elements  
> > of the Array, arranging them based on the returned result. We are suppose to  
> > return a result of -1, 0, or 1 to indicate how the elements compare. However,  
> > rand() returns a float between 0.0 and 1.0. Ruby considers anything over 0.0 to  
> > be the 1 response, so most of the rand calls give this. You can get a 0.0  
> > result from time to time, but it will be a loner in a sea of 1s.  
> > So what is the above code actually trying to do? It's trying to compare a  
> > selection of random numbers and sort on that instead. Writing the process out  
> > longhand it is:
> > 
> > &nbsp;&nbsp;quiz.map { |e| [rand, e] }.sort.map { |arr| arr.last }
> 
> Hmmm... Not being aware of sort\_by before, why this complicated? What's wrong with
> 
> &nbsp;&nbsp;quiz.sort{rand-0.5}
> 
> ?

In the above example I was trying to show what sort\_by() does behind the scenes. In truth, you should just be using the simple:

&nbsp;&nbsp;&nbsp;quiz.sort\_by { rand }

> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [4, 2, 3, 9, 7, 8, 1, 6, 5, 10]  
> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [9, 6, 2, 5, 1, 4, 3, 8, 7, 10]  
> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [3, 8, 10, 6, 9, 5, 1, 7, 2, 4]  
> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [4, 7, 10, 1, 9, 5, 2, 8, 6, 3]  
> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [8, 5, 10, 9, 2, 1, 7, 3, 4, 6]  
> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [6, 9, 2, 8, 1, 7, 4, 10, 3, 5]  
> &nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;=\> [3, 4, 5, 10, 7, 1, 8, 6, 2, 9]
> 
> It does seem to find your "random" criteria, is shorter and uses less calls...

Actually it's more calls.

You generate a random number for every comparison of elements and do math on them each time you do (additional calls). The sort\_by() method (and my longhand version) generates just one set of random numbers up front, one for each element.

Because of this, they are significantly faster on moderate-to-large size datasets:

#!/usr/bin/env ruby -w

require "benchmark"

data = Array.new(100) { rand(100) }

TESTS = 10\_000  
Benchmark.bmbm do |results|  
&nbsp;&nbsp;&nbsp;results.report("sort:") { TESTS.times { data.sort { rand - 0.5 } } }  
&nbsp;&nbsp;&nbsp;results.report("map-\>sort-\>map:") do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TESTS.times do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data.map { |e| [rand, e] }.sort.map { |arr| arr.last }  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;results.report("sort\_by:") { TESTS.times { data.sort\_by { rand } } }  
end

# \>\> Rehearsal ---------------------------------------------------  
# \>\> sort: 6.290000 0.000000 6.290000 ( 6.315368)  
# \>\> map-\>sort-\>map: 2.800000 0.000000 2.800000 ( 2.800905)  
# \>\> sort\_by: 1.490000 0.000000 1.490000 ( 1.485774)  
# \>\> ----------------------------------------- total: 10.580000sec  
# \>\>  
# \>\> user system total real  
# \>\> sort: 6.330000 0.000000 6.330000 ( 6.346076)  
# \>\> map-\>sort-\>map: 2.820000 0.000000 2.820000 ( 2.825896)  
# \>\> sort\_by: 1.490000 0.000000 1.490000 ( 1.485223)

\_\_END\_\_

James Edward Gray II

> **···**
>
> On Feb 15, 2007, at 8:37 AM, Benedikt Heinen wrote:
> 
> > On Thu, 15 Feb 2007, Ruby Quiz wrote:

---

<div class="post-metadata">

**Author:** ![Michael\_Ulm](https://avatars.discourse-cdn.com/v4/letter/m/a9adbd/32.png) [@Michael\_Ulm](https://rubytalk.org/u/Michael_Ulm)\
**Post date:** [16 February 2007 08:47 UTC](https://rubytalk.org/t/summary-one-liners-113/35391/4 "2007-02-16T08:47:18Z")

</div>

Benedikt Heinen schrieb:

> > &nbsp;&nbsp;&nbsp;&nbsp;quiz.sort{rand}
> 
> > Does that even work? Let's ask IRb:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz = (1..10).to\_a  
> > &nbsp;&nbsp;&nbsp;&nbsp;=\> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  
> > &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand}  
> > &nbsp;&nbsp;&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]  
> > &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand}  
> > &nbsp;&nbsp;&nbsp;&nbsp;=\> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
> 
> [...]
> 
> > That's not looking too random to me.
> > 
> > Let's think about this. What does the above code do. sort() compares elements  
> > of the Array, arranging them based on the returned result. We are suppose to  
> > return a result of -1, 0, or 1 to indicate how the elements compare. However,  
> > rand() returns a float between 0.0 and 1.0. Ruby considers anything over 0.0 to  
> > be the 1 response, so most of the rand calls give this. You can get a 0.0  
> > result from time to time, but it will be a loner in a sea of 1s.
> > 
> > So what is the above code actually trying to do? It's trying to compare a  
> > selection of random numbers and sort on that instead. Writing the process out  
> > longhand it is:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;quiz.map { |e| [rand, e] }.sort.map { |arr| arr.last }
> 
> Hmmm... Not being aware of sort\_by before, why this complicated? What's wrong with
> 
> &nbsp;&nbsp;&nbsp;&nbsp;quiz.sort{rand-0.5}
> 
> ?
> 
> (which puts your random numbers from their range of 0.0-1.0 to a new range of -0.5 and 0.5 -- if you find it aesthetically more pleasing, you could of course use
> 
> &nbsp;&nbsp;&nbsp;&nbsp;quiz.sort{2\*rand-1.0}
> 
> to make the range -1.0..1.0.
> 
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [4, 2, 3, 9, 7, 8, 1, 6, 5, 10]  
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [9, 6, 2, 5, 1, 4, 3, 8, 7, 10]  
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [3, 8, 10, 6, 9, 5, 1, 7, 2, 4]  
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [4, 7, 10, 1, 9, 5, 2, 8, 6, 3]  
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [8, 5, 10, 9, 2, 1, 7, 3, 4, 6]  
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [6, 9, 2, 8, 1, 7, 4, 10, 3, 5]  
> &nbsp;&nbsp;&nbsp;&nbsp;\>\> quiz.sort{rand-0.5}  
> &nbsp;&nbsp;&nbsp;&nbsp;=\> [3, 4, 5, 10, 7, 1, 8, 6, 2, 9]
> 
> It does seem to find your "random" criteria, is shorter and uses less calls...

Apart from being slower, it is also not producing a random permutation.  
The results are skewed due to the way the sorting works.  
Try this:

counter = Hash.new(0)  
ar = [1, 2, 3]  
10000.times {counter[ar.sort{rand-0.5}] += 1}  
p counter

On my system, this just produced

{[2, 3, 1]=\>1200, [2, 1, 3]=\>1230, [3, 2, 1]=\>2530,  
&nbsp;&nbsp;[1, 2, 3]=\>2533, [3, 1, 2]=\>1264, [1, 3, 2]=\>1243}

HTH,

Michael

> **···**
>
> > On Thu, 15 Feb 2007, Ruby Quiz wrote:
> 
> --  
> Michael Ulm  
> R&D Team  
> ISIS Information Systems Austria  
> tel: +43 2236 27551-542, fax: +43 2236 21081  
> e-mail: michael.ulm@isis-papyrus.com  
> Visit our Website: [www.isis-papyrus.com](http://www.isis-papyrus.com)
> 
> ---------------------------------------------------------------  
> This e-mail is only intended for the recipient and not legally  
> binding. Unauthorised use, publication, reproduction or  
> disclosure of the content of this e-mail is not permitted.  
> This email has been checked for known viruses, but ISIS accepts  
> no responsibility for malicious or inappropriate content.  
> ---------------------------------------------------------------
