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

[QUIZ] One-Liners (#113)

3 views
Skip to first unread message

Ruby Quiz

unread,
Feb 9, 2007, 12:00:03 PM2/9/07
to
The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

This week's Ruby Quiz is in pop quiz format. For each of the scenarios below,
send in a one line (80 characters or less) solution that performs the task. You
may use any legal Ruby syntax including require statements and semicolons, but
the goal in finesse more than golfing.

Any input described in the problem is in a local variable called quiz.
Assigning to this variable is assumed to have happened earlier in the program
and is not part of your submitted solution.

Your line just needs to evaluate to the expected result. You do not have to
store the result in a variable or create any output.

Any edge cases not covered by the provided examples are left to your best
judgement.

* Given a Numeric, provide a String representation with commas inserted between
each set of three digits in front of the decimal. For example, 1999995.99
should become "1,999,995.99".

* Given a nested Array of Arrays, perform a flatten()-like operation that
removes only the top level of nesting. For example, [1, [2, [3]]] would become
[1, 2, [3]].

* Shuffle the contents of a provided Array.

* Given a Ruby class name in String form (like
"GhostWheel::Expression::LookAhead"), fetch the actual class object.

* Insert newlines into a paragraph of prose (provided in a String) so lines will
wrap at 40 characters.

* Given an Array of String words, build an Array of only those words that are
anagrams of the first word in the Array.

* Convert a ThinkGeek t-shirt slogan (in String form) into a binary
representation (still a String). For example, the popular shirt "you are dumb"
is actually printed as:

111100111011111110101
110000111100101100101
1100100111010111011011100010

* Provided with an open File object, select a random line of content.

* Given a wondrous number Integer, produce the sequence (in an Array). A
wondrous number is a number that eventually reaches one, if you apply the
following rules to build a sequence from it. If the current number in the
sequence is even, the next number is that number divided by two. When the
current number is odd, multiply that number by three and add one to get the next
number in the sequence. Therefore, if we start with the wondrous number 15, the
sequence is [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2,
1].

* Convert an Array of objects to nested Hashes such that %w[one two three four
five] becomes {"one" => {"two" => {"three" => {"four" => "five"}}}}.

James Edward Gray II

unread,
Feb 9, 2007, 2:46:18 PM2/9/07
to
On Feb 9, 2007, at 1:40 PM, Robert Dober wrote:

> On 2/9/07, Ruby Quiz <ja...@grayproductions.net> wrote:
>>
>> The three rules of Ruby Quiz:
>>

>> <snip>
>>
> Sounds like fun,
> Now the stupid question of the day: will a comment before the line
> be a
> problem for your tools, I guess they are counting ;)

I don't have any tools. Someone should build a test suite and post
it though, if they feel motivated to do so.

For my solutions though, I put the questions as comments before each
solution.

James Edward Gray II

James Edward Gray II

unread,
Feb 9, 2007, 4:06:09 PM2/9/07
to
On Feb 9, 2007, at 3:01 PM, Luke Ivers wrote:

> So, does the quiz variable that we're getting have to remain
> unmodified, or
> does it matter?

Either way is fine with me.

James Edward Gray II

James Edward Gray II

unread,
Feb 9, 2007, 4:15:28 PM2/9/07
to
On Feb 9, 2007, at 3:10 PM, Luke Ivers wrote:

>>
>> * Given a wondrous number Integer, produce the sequence (in an
>> Array). A
>> wondrous number is a number that eventually reaches one, if you
>> apply the
>> following rules to build a sequence from it. If the current
>> number in the
>> sequence is even, the next number is that number divided by two.
>> When the
>> current number is odd, multiply that number by three and add one
>> to get
>> the next
>> number in the sequence. Therefore, if we start with the wondrous
>> number
>> 15, the
>> sequence is [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5,
>> 16, 8,
>> 4, 2,
>> 1].
>>
>>

> One final question: you say "given a wondrous number"... does this
> mean that
> the input is guaranteed to be wondrous, and we therefore don't need
> to check
> it...

Correct.

James Edward Gray II

Jamie Macey

unread,
Feb 9, 2007, 4:19:46 PM2/9/07
to
On 2/9/07, James Edward Gray II <ja...@grayproductions.net> wrote:
> Someone should build a test suite and post
> it though, if they feel motivated to do so.

Okay. The only one I'm taking significant liberties with is the
random-line-from-a-file - the test is expecting that the contents of
the file be loaded using File#read, and uses OpenStruct to provide a
test stub with that functionality. Other than that, these tests are a
pretty direct translation from the initial description. Method stubs
for the implementation are also provided.

require 'test/unit'
require 'ostruct'

class OneLiner
class << self
def commaize(number)
end

def flatten_once(ary)
end

def shuffle(ary)
end

def get_class(name)
end

def wrap_text(paragraph)
end

def find_anagrams(words)
end

def binarize(slogan)
end

def random_line(file)
end

def wondrous_sequence(n)
end

def nested_hash(ary)
end
end
end

class TestOneLiner < Test::Unit::TestCase
# Given a Numeric, provide a String representation with commas inserted
# between each set of three digits in front of the decimal. For example,
# 1999995.99 should become "1,999,995.99".
def test_commaize
assert_equal "1,999,995.99", OneLiner.commaize(1999995.99)
end

# Given a nested Array of Arrays, perform a flatten()-like operation that
# removes only the top level of nesting. For example, [1, [2, [3]]] would
# become [1, 2, [3]].
def test_flatten_once
ary = [1, [2, [3, 4]]]
flatter_ary = [1, 2, [3, 4]]
assert_equal flatter_ary, OneLiner.flatten_once(ary)
end

# Shuffle the contents of a provided Array.
def test_shuffle
ary = [3,1,4,1,5,9]
shuffled_ary = OneLiner.shuffle(ary)
assert_not_equal ary, shuffled_ary
assert_equal ary.sort, shuffled_ary.sort
end

# Given a Ruby class name in String form (like
# "GhostWheel::Expression::LookAhead"), fetch the actual class object.
def test_get_class
assert_equal Test::Unit::TestCase,
OneLiner.get_class("Test::Unit::TestCase")
end

# Insert newlines into a paragraph of prose (provided in a String) so
# lines will wrap at 40 characters.
def test_wrap_text
wrapped = "Insert newlines into a paragraph of " + "\n" +
"prose (provided in a String) so lines " + "\n" +


"will wrap at 40 characters."

paragraph = "Insert newlines into a paragraph of " +
"prose (provided in a String) so lines " +


"will wrap at 40 characters."

assert_equal wrapped, OneLiner.wrap_text(paragraph)
end

# Given an Array of String words, build an Array of only those words that
# are anagrams of the first word in the Array.
def test_find_anagrams
anagrams = %w(cat act)
assert_equal anagrams, OneLiner.find_anagrams(%w(tac bat cat rat act))
end


# Convert a ThinkGeek t-shirt slogan (in String form) into a binary
# representation (still a String). For example, the popular shirt
# "you are dumb" is actually printed as:
# 111100111011111110101
# 110000111100101100101
# 1100100111010111011011100010
def test_binarize
output = "111100111011111110101" + "\n" +
"110000111100101100101" + "\n" +
"1100100111010111011011100010"
assert_equal output, OneLiner.binarize("you are dumb")
end

# Provided with an open File object, select a random line of content.
#
# NOTE: This test assumes you're using File#read to get the string data
# from the file - if doing otherwise, update the test?
def test_random_line
file = OpenStruct.new(:read => "development:
adapter: mysql
database: redvase_development
host: localhost
username: root
password:")
lines = file.read.split("\n")
line = OneLiner.random_line(file)
assert_equal true, lines.include?(line)
end

# Given a wondrous number Integer, produce the sequence (in an Array). A
# wondrous number is a number that eventually reaches one, if you apply
# the following rules to build a sequence from it. If the current number
# in the sequence is even, the next number is that number divided by two.
# When the current number is odd, multiply that number by three and add
# one to get the next number in the sequence. Therefore, if we start with
# the wondrous number 15, the sequence is [15, 46, 23, 70, 35, 106, 53,
# 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1].
def test_wondrous_sequence
seq = [23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
assert_equal seq, OneLiner.wondrous_sequence(23)
end

# Convert an Array of objects to nested Hashes such that %w[one two three
# four five] becomes {"one" => {"two" => {"three" => {"four" => "five"}}}}.
def test_nested_hash
hash = {:o => {:t => {:t => {:f => :f}}}}
assert_equal hash, OneLiner.nested_hash([:o, :t, :t, :f, :f])
end
end

James Edward Gray II

unread,
Feb 9, 2007, 4:26:57 PM2/9/07
to
On Feb 9, 2007, at 3:19 PM, Jamie Macey wrote:

> On 2/9/07, James Edward Gray II <ja...@grayproductions.net> wrote:
>> Someone should build a test suite and post
>> it though, if they feel motivated to do so.
>
> Okay. The only one I'm taking significant liberties with is the
> random-line-from-a-file - the test is expecting that the contents of

> the file be loaded using File#read...

What if it's a file too big to read into memory? ;)

James Edward Gray II

Phrogz

unread,
Feb 9, 2007, 4:52:03 PM2/9/07
to
On Feb 9, 10:00 am, Ruby Quiz <j...@grayproductions.net> wrote:
> * Given a wondrous number Integer, produce the sequence (in an Array). A
> wondrous number is a number that eventually reaches one, if you apply the
> following rules to build a sequence from it. If the current number in the
> sequence is even, the next number is that number divided by two. When the
> current number is odd, multiply that number by three and add one to get the next
> number in the sequence. Therefore, if we start with the wondrous number 15, the
> sequence is [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2,
> 1].

Here are a few more test cases, both to help others and to be sure
that mine is right:

def test_wondrous( str )
maps = [
[1, [1]],
[3,[3,10,5,16,8,4,2,1]],
[5,[5,16,8,4,2,1]],
[8,[8,4,2,1]],
[15,[15,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1]],
[31,
[31,94,47,142,71,214,107,322,161,484,242,121,364,182,91,274,137,412,206,103,310,155,466,233,700,350,175,526,263,790,395,1186,593,1780,890,445,1336,668,334,167,502,251,754,377,1132,566,283,850,425,1276,638,319,958,479,1438,719,2158,1079,3238,1619,4858,2429,7288,3644,1822,911,2734,1367,4102,2051,6154,3077,9232,4616,2308,1154,577,1732,866,433,1300,650,325,976,488,244,122,61,184,92,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1]]
]
raise "Solution too long (#{str.length} characters)" unless
str.length <= 80
maps.each{ |pair|
quiz = pair[0]
expected = pair[1]
output = eval( str )
unless expected==output
raise "quiz=#{quiz}; expected #{expected.inspect}, got
#{output.inspect}"
end
}
end

Chris Shea

unread,
Feb 9, 2007, 5:05:16 PM2/9/07
to
[snip]

> # Given an Array of String words, build an Array of only those words that
> # are anagrams of the first word in the Array.
> def test_find_anagrams
> anagrams = %w(cat act)
> assert_equal anagrams, OneLiner.find_anagrams(%w(tac bat cat rat act))
> end
[snip]

I changed anagrams to %w(tac cat act). I'm the sort of person that
thinks a word is an anagram of itself (although, I have a feeling
"tac" isn't a word).

Phrogz

unread,
Feb 9, 2007, 5:14:54 PM2/9/07
to
On Feb 9, 10:00 am, Ruby Quiz <j...@grayproductions.net> wrote:
> * Given a Numeric, provide a String representation with commas inserted between
> each set of three digits in front of the decimal. For example, 1999995.99
> should become "1,999,995.99".

More test cases:
def test_commify( str )


maps = [
[1, "1"],

[-1, "-1"],
[0.001, "0.001"],
[-0.001, "-0.001"],
[999, "999"],
[-999, "-999"],
[999.1, "999.1"],
[-999.1, "-999.1"],
[999.12, "999.12"],
[-999.12, "-999.12"],
[999.123, "999.123"],
[-999.123, "-999.123"],
[9999, "9,999"],
[-9999, "-9,999"],
[9999.1, "9,999.1"],
[-9999.1, "-9,999.1"],
[9999.12, "9,999.12"],
[-9999.12, "-9,999.12"],
[9999.123, "9,999.123"],
[-9999.123, "-9,999.123"],
[12, "12"],
[123, "123"],
[1234, "1,234"],
[12345, "12,345"],
[123456, "123,456"],
[1234567, "1,234,567"],
[12345678, "12,345,678"],
[-12, "-12"],
[-123, "-123"],
[-1234, "-1,234"],
[-12345, "-12,345"],
[-123456, "-123,456"],
[-1234567, "-1,234,567"],
[-12345678, "-12,345,678"]

Jamie Macey

unread,
Feb 9, 2007, 5:16:50 PM2/9/07
to

Neither did I, I was just looking for an easy test. And yet, lo and behold:

http://cancerweb.ncl.ac.uk/cgi-bin/omd?query=tac
A kind of customary payment by a tenant; a word used in old records.
Origin: Cf. Tack, 4.
Source: Websters Dictionary

Phrogz

unread,
Feb 9, 2007, 5:41:57 PM2/9/07
to
On Feb 9, 10:00 am, Ruby Quiz <j...@grayproductions.net> wrote:
> * Given a nested Array of Arrays, perform a flatten()-like operation that
> removes only the top level of nesting. For example, [1, [2, [3]]] would become
> [1, 2, [3]].

Yet more test cases (and a new way that I'm testing them). Used like:
s = 'your 80 char solution here'
test_solution( :flatten_once, s )


def test_solution( map_set, str )
raise "Solution too long (#{str.length} chars)" unless str.length <=
80
maps = MAPS[ map_set ]


maps.each{ |pair|
quiz = pair[0]
expected = pair[1]
output = eval( str )

unless expected==output
raise "quiz=#{quiz}; expected #{expected.inspect}, got
#{output.inspect}"
end
}
end

MAPS = {
:commify => [

],

:flatten_once => [
[ [], [] ],
[ [1], [1] ],
[ [1,2], [1,2] ],
[ [1,[2]], [1,2] ],
[ [[1],2], [1,2] ],
[ [[1,2]], [1,2] ],
[ [1,2,3], [1,2,3] ],
[ [1,[2,3]], [1,2,3] ],
[ [[1,2,3]], [1,2,3] ],
[ [1, [2, [3]]], [1, 2, [3]] ],
[ [1, [[2], 3]], [1, [2], 3] ],
[ [1,[2,[3,[4]]]], [1, 2, [3,[4]]] ],
[ [[[[[[6]]]]]], [[[[[6]]]]] ]
],

:wondrous => [

Phrogz

unread,
Feb 9, 2007, 6:33:05 PM2/9/07
to

# Built-in classes for testing #4
:class_from_string => [
["OpenSSL", OpenSSL ],
["OpenSSL::Digest", OpenSSL::Digest ],
["OpenSSL::Digest::DigestError", OpenSSL::Digest::DigestError ],
],

James Edward Gray II

unread,
Feb 9, 2007, 6:46:45 PM2/9/07
to
On Feb 9, 2007, at 5:35 PM, Phrogz wrote:

> # Built-in classes for testing #4
> :class_from_string => [
> ["OpenSSL", OpenSSL ],
> ["OpenSSL::Digest", OpenSSL::Digest ],
> ["OpenSSL::Digest::DigestError", OpenSSL::Digest::DigestError ],
> ],

Technically those are constants, not classes. For example, OpenSSL
is a module. That neatly foils a solution someone sent me off list
today. ;)

James Edward Gray II

Phrogz

unread,
Feb 9, 2007, 6:55:52 PM2/9/07
to
On Feb 9, 4:46 pm, James Edward Gray II <j...@grayproductions.net>
wrote:

I had wondered about that. The wording of the quiz says "class", but I
figure that:
a) Given the number of modules-as-namespaces over classes-as-
namespaces, a good solution to the problem allows module stops along
the way, and thus
b) If you're going to allow non-classes along the way, you generally
want the "resolve this as though I typed it" functionality, which may
end in constants.

BUT...if the quiz has the wrinkle that only Classes should be returned
(and thus you should get nil for the cases above), that would be
interesting to program, too.

Phrogz

unread,
Feb 9, 2007, 7:06:27 PM2/9/07
to
Some interesting edge cases, assuming that exactly 40 characters may
fit on a line:
(My one-liner still doesn't handle the 2nd and 3rd ones)

MAPS[ :paragraph_wrapping ] = [
[ "One\nTwo\nThree", "One\nTwo\nThree" ],

[ "It's the end of the world as we know it.",
"It's the end of the world as we know it." ],

[ "It is the end of the world as we know it",
"It is the end of the world as we know it" ],

[ "It is the end of the world as we know it and I feel fine.",
"It is the end of the world as we know it\nand I feel fine." ],

[ "It's the end of the world as we know it, and I feel fine.",
"It's the end of the world as we know it,\nand I feel fine." ],

[ "It is the end of the world as we know it, and I feel fine.",
"It is the end of the world as we know\nit, and I feel fine." ],

[ "It is not the end of the world as we know it, and I feel fine.",
"It is not the end of the world as we\nknow it, and I feel
fine." ]
]

James Edward Gray II

unread,
Feb 9, 2007, 7:08:49 PM2/9/07
to

My solution handles either.

James Edward Gray II

Dan Uznanski

unread,
Feb 10, 2007, 6:57:45 PM2/10/07
to

Not that there are any known positive numbers that are not wondrous...
indeed, it would be wondrous to find such a number. Brute force
attacks from eight years ago place any non-wondrous number, should it
exist, above 2E16.

That said, there are three known cycles of /negative/ non-wondrous
numbers.

The real name of this problem is the Collatz Problem.

http://mathworld.wolfram.com/CollatzProblem.html

Dan


Phrogz

unread,
Feb 11, 2007, 9:30:52 AM2/11/07
to
Here are my solutions to Quiz #113. For some of them I just couldn't
help but to provide a couple variations.

Because I'm a terrible golfer, most strive for elegance (in some form)
over terseness.


# 1 - Commafy Numerics
i,f=quiz.to_s.split('.'); i.gsub(/(\d)(?=\d{3}+$)/,'\\1,') + (f ?
('.'+f) : '')

# 2 - Flatten_Once
a=[]; quiz.each{ |x| Array===x ? a.concat(x) : a<<x }; a

# 3 - Shuffle Array
quiz.sort_by{ rand }

# 4 - Resolve class (and other constants) from string
quiz.split( '::' ).inject( Module ){ |r,o| r.const_get(o) }
#...or, by cheating
eval(quiz)

#5 - Paragraph Wrapping - extra work to not put a new line on the last
line
quiz.gsub( /^(.{1,40})($|[ \t]+)/ ){ $2.empty? ? $1 : "#{$1}\n" }

#6 - Anagrams - assuming that the original word shouldn't be in the
output...
a=[]; r=quiz.shift.split('').sort; quiz.each{|w|a<<w if
w.split('').sort==r}; a
#...or, if the original word should be included
a=[]; r=quiz[0].split('').sort; quiz.each{ |w| a<<w if
w.split('').sort==r }; a

#7 - String to Binary String, the geeky way
o=''; quiz.each_byte{|b| o << ( b==32 ? "\n" : ('%b' % b) ) }; o
#...or slightly more 'rubyish'...
quiz.split(' ').map{|s| o=''; s.each_byte{|b| o << b.to_s(2) };
o }.join("\n")
#...but what's more rubyish than nested #maps and pulling bytes from
strings? ;)
quiz.split(' ').map{|s| s.scan(/./).map{|c| '%b' %
c[0] }.join }.join("\n")

# By the way, I have to say that if the Think Geek t-shirts are really
in the
# form provided, they are terrible #representations of geekiness. What
geek
# would strip the leading zeros from the bits in a byte? I'd replace
"%b" with
# "%08b" above for a better representation (and use it instead of
to_s(2)).

#8 - Random line from file - if you run out of memory, go buy more
RAM ;)
all=quiz.readlines; all[ rand(all.length) ]

#9 - Wondrous number path
a=[n=quiz]; while n>1; a << ( n%2==0 ? n/=2 : n=(n*3)+1 ); end; a

#10 - Array to Nested Hash, direct indexing...
a=quiz; h={a[-2]=>a[-1]}; (a.size-3).downto(0){ |i| h={a[i]=>h} }; h
#...or a slightly different way...
a=quiz; y,z=a[-2..-1]; h={y=>z}; a[0..-3].reverse.each{ |o|
h={o=>h} }; h
#...or poppin' values for a tighter format...
a=quiz; z,y=a.pop,a.pop; h={y=>z}; a.reverse.each{ |o| h={o=>h} }; h
#...and one last, just because I love Hash.[]
a=quiz.reverse; h=Hash[a.shift,a.shift].invert; a.each{ |o|
h={o=>h} }; h

Phrogz

unread,
Feb 11, 2007, 9:43:44 AM2/11/07
to
On Feb 11, 7:30 am, "Phrogz" <g...@refinery.com> wrote:
> Here are my solutions to Quiz #113. For some of them I just couldn't
> help but to provide a couple variations.

Bah, of course the 80-char lines wrapped and got confusing with my
comments. Here's my solution again, with syntax highlighting:
http://pastie.caboo.se/39500

And here is the actual code I originally wrote to test most of the
solutions:
http://pastie.caboo.se/39499

Louis J Scoras

unread,
Feb 11, 2007, 11:27:42 AM2/11/07
to
# Largely just to play around with rspec a bit.
# The random_line solution too long as well.

require 'rubygems'
require 'spec'

def commify(quiz)
quiz.to_s.reverse.gsub(/(\d{3})(?=\d)(?!\d*\.)/) {"#$1,"}.reverse
end

context "integers to strings" do
specify "should get commas every three digits from the right" do
commify(123).should == '123'
commify(1234).should == '1,234'
commify(123456).should == '123,456'
commify(-12345).should == '-12,345'
commify(-1001001).should == '-1,001,001'
end
end

context "floats to strings" do
specify "should not get commas after decimal" do
commify(123.456).should == '123.456'
commify(123.456789).should == '123.456789'
commify(123456.789).should == '123,456.789'
commify(-123456.789).should == '-123,456.789'
end
end

def flatten1(quiz)
quiz.inject([]){|r,n| n.respond_to?(:each) ? n.each {|i| r<< i} : (r<< n) ; r}
end

context "arrays nested arrays only one level deep" do
setup do
@ary = [[1],2,[:symbol],'foo']
@random = []
10.times do
n = rand(100)
@random << (rand > 0.5 ? n : [n] )
end
end

specify "should flatten1 the same as flatten" do
flatten1(@ary).should == @ary.flatten
flatten1(@random).should == @random.flatten
end
end

context "arrays nested multiple levels" do
specify "should only loose 1 level of arrays" do
flatten1([1, [2, [3]]]).should == [1,2,[3]]
flatten1([[[[[1]]]]]).should == [[[[1]]]]
end
end

def shuffle(quiz)
quiz.sort_by { rand }
end

context "An array with several elements" do
setup do
@rands = [3,2,1,6,5,4,9,8,7,10]
@a = (1..@rands.length).to_a
x = -1

self.stub!(:rand).and_return { @rands[x+= 1] }
end

specify "should sort randomly w/ shuffle" do
shuffle(@a).should == @rands[0..@a.length-1]
end
end


module GhostWheel
module Expression
class LookAhead
end
end
end

def to_class(quiz)
quiz.split(/::/).inject(Object) {|s,c| s.const_get(c.to_sym)}
end

context %{given a "class expression"} do
specify "to_class should return that class" do
GhostWheel.should_receive(:const_get).with(:Expression).and_return(GhostWheel::Expression)
GhostWheel::Expression.should_receive(:const_get).with(:LookAhead).and_return(GhostWheel::Expression::LookAhead)

to_class("GhostWheel::Expression::LookAhead")
end

specify "to_class should work for built-in classes" do
to_class("Net::HTTP").should == Net::HTTP
end
end

def wrap(quiz)
quiz.gsub(/(.{1,40})\s+/){ "$1\n" }
end

context "A paragraph of text w/ less than 40 lines" do
setup do
@text = 'f' * 40
end

specify "should not be changed by wrap" do
wrap(@text).should == @text
end
end

context "A paragraph with more than 40 characters" do
setup do
@paragraph = <<-END_PARA.gsub(/\s+/, ' ').strip


Given a wondrous number Integer, produce the sequence (in an
Array). A wondrous number is a number that eventually
reaches one, if you apply the following rules to build a
sequence from it. If the current number in the sequence is
even, the next number is that number divided by two. When
the current number is odd, multiply that number by three and
add one to get the next number in the sequence. Therefore,
if we start with the wondrous number 15, the sequence is
[15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8,
4, 2, 1].

END_PARA
end

specify "should have no lines longer than 40 wide after wrapping" do
wrap(@paragraph).split(/\n/).each do |line|
line.length.should_not > 40
end
end
end

context "An paragraph with a word longer than 40 characters" do
setup do
@text = 'f' * 60
end

specify "should not be split mid word" do
wrap(@text).should_not_include '\n'
end
end

def anagrams(quiz)
n=quiz[0].split(//).sort; quiz.select {|i| i.split(//).sort == n }
end

context "An array of words" do
setup do
@a = %w/silly lilsi looloo yllis yuf silly2 islyl/
end

specify "anagrams should contain words with same letters same number
of times" do
anagrams(@a).should == %w/silly yllis islyl/
end
end

def to_bin(quiz)
quiz.scan(/\w/).collect {|c| sprintf "%b", c[0] }.join('')
end

context "A ascii string" do
setup do
@str = "you are dumb"
end

specify "should be converted to binary by to_bin" do
to_bin(@str).should == '111100111011111110101' +
'110000111100101100101' +
'1100100111010111011011100010'
end
end

def rand_line(quiz)
z=0;quiz.each{z+=1};quiz.seek 0;n=rand
z;quiz.each_with_index{|x,i|;return x if i==n}
end

context "an open file handle" do
setup do
require 'stringio'
@fh = StringIO.new <<-END
one
two
three
four
END
end

specify "should return a random line" do
line = rand_line(@fh).strip
%w/one two three four/.should_include(line)
end
end

def wonder(quiz)
@a=[quiz];until quiz==1;@a<< quiz=quiz%2==0?quiz/2: quiz*3+1 end;@a
end

context "The wonderous sequence for 15" do
setup { @w = wonder(15) }
specify "should end with 1" do
@w[@w.length-1].should == 1
end
specify "should match the test data" do
@w.should == [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5,


16, 8, 4, 2, 1]

end
end

def hashify(quiz)
quiz.reverse.inject(){|m,i|{i=>m}}
end

context "An array of strings" do
specify "should return nested hashes when hashified" do
hashify(%w/one two three four five/).should ==


{"one" => {"two" => {"three" => {"four" => "five"}}}}

end
end

Chris Shea

unread,
Feb 11, 2007, 12:55:59 PM2/11/07
to
I wasn't able to get down to less than 80 chars on two of the items.
For commaizing numbers and getting a random line from a file (assuming
loading the whole file into memory is against the rules) I think I was
just barking up the wrong path. I like my nested hash solution (which
I wrote in both golf and non-golf modes.

# Commaize (works for both floats and integers)
a=quiz.to_s.split('.');a[0].reverse.gsub(/
(\d{3})/,'\1,').chomp(',').reverse+"#{'.'+a[1] if a[1]}"

# Flatten once
a=[];quiz.each{|i| if i.is_a? Array;i.each{|j| a<<j};else;a<<i;end};a

# Randomize array (the obvious way)
quiz.sort_by {rand}

# Class from String (fails for some)
begin eval "#{quiz}.allocate.class" rescue nil end

# Wrap lines (no newline at the end!)
a='';b=quiz;until b.size<=40;a<<b.slice!(0..b.rindex(' ',
40))<<"\n";end;a<<b

# Find anagrams
quiz.find_all {|x| x.split('').sort == quiz[0].split('').sort}

# Binarize
a=''; quiz.each_byte {|b| a << (b == 32 ? "\n" : "%b" % b)}; a

# Random line (kludge, reads the whole file twice)
f=quiz;c=0;f.each{c+=1};r=rand(c)+1;f.pos=0;c=0;a='';f.each{|line|c
+=1;a=line if c==r};a

# Wondrous sequence
b=quiz;a=[b];while b>1;if b%2==1;b=3*b+1;else;b=b/2;end;a<<b;end;a

# Nested hash (golf and non-golf, and my best answer (I think))
a=quiz.pop;quiz.reverse_each{|i|a={i=>a}};a
hash = quiz.pop; quiz.reverse_each { |item| hash = { item => hash } };
hash

Thanks for the good time.

Ken Bloom

unread,
Feb 11, 2007, 2:08:09 PM2/11/07
to
On Sat, 10 Feb 2007 02:00:03 +0900, Ruby Quiz wrote:
#In all of this, +i+ is the input. Some solutions don't behave exactly the
same as the requested sample output, and some are too long. I think I
noted all such instances.

# * Given a Numeric, provide a String representation with commas inserted between
# each set of three digits in front of the decimal. For example, 1999995.99
# should become "1,999,995.99".

#this takes 83 characters
a=i.to_s.split('.');a[0]=a[0].reverse.scan(/.{1,3}/).join(',').reverse;a.join('.')

# * Given a nested Array of Arrays, perform a flatten()-like operation that
# removes only the top level of nesting. For example, [1, [2, [3]]] would become
# [1, 2, [3]].

i.inject([]){|cur,val| Array===val ? cur+val : cur << val}
#or
i.inject([]){|cur,val| cur+val rescue cur << val}
#(cur+val throws an error if val isn't an array)

# * Shuffle the contents of a provided Array.

i.inject([]){|cur,val| cur.insert(rand(cur.length+1),val)}

# * Given a Ruby class name in String form (like
# "GhostWheel::Expression::LookAhead"), fetch the actual class object.

eval(i)
#or
i.split("::").inject(Object){|c,v| c.const_get(v)}

# * Insert newlines into a paragraph of prose (provided in a String) so
# lines will wrap at 40 characters.

#clearly doesn't fit within 80 characters
i.split.inject([[]]){|r,w| (r[-1].inject(0){|a,b| a+b.size}+w.size)<40 ? r[-1] << w : r << [w]; r}.map{|x| x.join(' ')}.join("\n")


# * Given an Array of String words, build an Array of only those words
# that are anagrams of the first word in the Array.

i.select{|x| x.split(//).sort==i.first.split(//).sort}

# * Convert a ThinkGeek t-shirt slogan (in String form) into a binary
# representation (still a String). For example, the popular shirt "you are dumb"
# is actually printed as:


#
# 111100111011111110101
# 110000111100101100101
# 1100100111010111011011100010

i.unpack("B*")[0]
#this doesn't give me the same answer that you gave me though
#or
r="";i.each_byte{|x| r << x.to_s(2)};r

# * Provided with an open File object, select a random line of content.
x=i.readlines;x[rand(x.length)]
#or
i.find{rand<.0005 || i.eof?}
#the rules didn't say anything about the random distribution used.
#adjust the threshold as necessary

# * Given a wondrous number Integer, produce the sequence (in an Array). A
# wondrous number is a number that eventually reaches one, if you apply the
# following rules to build a sequence from it. If the current number in the
# sequence is even, the next number is that number divided by two. When the
# current number is odd, multiply that number by three and add one to get the next
# number in the sequence. Therefore, if we start with the wondrous number 15, the
# sequence is [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2,
# 1].

r=[i];while i!=1 do r << (i= i%2==0?i/2:i*3+1); end; r

#
# * Convert an Array of objects to nested Hashes such that %w[one two three four
# five] becomes {"one" => {"two" => {"three" => {"four" => "five"}}}}.

#neither of these gives the same answer asked for here
p=lambda{|h,k| h[k] = Hash.new(&p)};z=Hash.new(&p);i.inject(z){|ha,co|ha[co]};z
#or
z={};i.inject(z){|ha,co| ha[co]={}};z

--
Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/

Ken Bloom

unread,
Feb 11, 2007, 2:08:09 PM2/11/07
to
On Sun, 11 Feb 2007 09:55:59 -0800, Chris Shea wrote:

> I wasn't able to get down to less than 80 chars on two of the items.
> For commaizing numbers and getting a random line from a file (assuming
> loading the whole file into memory is against the rules) I think I was
> just barking up the wrong path. I like my nested hash solution (which
> I wrote in both golf and non-golf modes.
>
> # Commaize (works for both floats and integers)
> a=quiz.to_s.split('.');a[0].reverse.gsub(/
> (\d{3})/,'\1,').chomp(',').reverse+"#{'.'+a[1] if a[1]}"

Too long: 96 characters (but I couldn't get it under 80 either)

> # Flatten once
> a=[];quiz.each{|i| if i.is_a? Array;i.each{|j| a<<j};else;a<<i;end};a
>
> # Randomize array (the obvious way)
> quiz.sort_by {rand}
>
> # Class from String (fails for some)
> begin eval "#{quiz}.allocate.class" rescue nil end
>
> # Wrap lines (no newline at the end!)
> a='';b=quiz;until b.size<=40;a<<b.slice!(0..b.rindex(' ',
> 40))<<"\n";end;a<<b
>
> # Find anagrams
> quiz.find_all {|x| x.split('').sort == quiz[0].split('').sort}
>
> # Binarize
> a=''; quiz.each_byte {|b| a << (b == 32 ? "\n" : "%b" % b)}; a
>
> # Random line (kludge, reads the whole file twice)
> f=quiz;c=0;f.each{c+=1};r=rand(c)+1;f.pos=0;c=0;a='';f.each{|line|c
> +=1;a=line if c==r};a

89 characters

> # Wondrous sequence
> b=quiz;a=[b];while b>1;if b%2==1;b=3*b+1;else;b=b/2;end;a<<b;end;a
>
> # Nested hash (golf and non-golf, and my best answer (I think))
> a=quiz.pop;quiz.reverse_each{|i|a={i=>a}};a
> hash = quiz.pop; quiz.reverse_each { |item| hash = { item => hash } };
> hash
>
> Thanks for the good time.

--

Ken Bloom

unread,
Feb 11, 2007, 2:15:44 PM2/11/07
to
On Sun, 11 Feb 2007 06:30:52 -0800, Phrogz wrote:

> Here are my solutions to Quiz #113. For some of them I just couldn't
> help but to provide a couple variations.
>
> Because I'm a terrible golfer, most strive for elegance (in some form)
> over terseness.
>
>
> # 1 - Commafy Numerics
> i,f=quiz.to_s.split('.'); i.gsub(/(\d)(?=\d{3}+$)/,'\\1,') + (f ?
> ('.'+f) : '')

Good answer. you helped me golf mine down a little bit by getting rid of
array indexing:

i,f=i.to_s.split('.');"#{i.reverse.scan(/.{1,3}/).join(',').reverse}.#{f}"

>
> # 3 - Shuffle Array
> quiz.sort_by{ rand }

I feel stupid for taking such a longer answer. And I've used sort_by{rand}
too to do this, if not in Ruby then in SQL, so I feel stupid for not
thinking of it in this context.]

mine, as I posted elsewhere was:
i.inject([]){|cur,val| cur.insert(rand(cur.length+1),val)}


> #5 - Paragraph Wrapping - extra work to not put a new line on the last
> line
> quiz.gsub( /^(.{1,40})($|[ \t]+)/ ){ $2.empty? ? $1 : "#{$1}\n" }

I like this one a lot. My only answer was way too long, and far more
complicated.

Sharon Phillips

unread,
Feb 11, 2007, 2:43:58 PM2/11/07
to
Hi,

I liked this quiz. Bite sized pieces I could attempt between chasing
kids :)

Here's what I came up with. Note that #6 is just a bit over the 80
char limit.

Cheers,
Dave


TEXT_FILE= '/Users/sharon/Documents/Dave/RubyQuiz/english.txt'

#* Given a Numeric, provide a String representation with commas
inserted between
#each set of three digits in front of the decimal. For example,
1999995.99
#should become "1,999,995.99".
puts "-- 01 --"
quiz="1234567.89"
# soln
a=quiz.gsub(/(\d)(?=\d{3}+(\.\d*)?$)/,'\1,')
# \soln
puts a

#* Given a nested Array of Arrays, perform a flatten()-like operation
that
#removes only the top level of nesting. For example, [1, [2, [3]]]
would become


#[1, 2, [3]].

puts "\n-- 02 --"
quiz= [3, [4, 5], [2, [3]], [3, [4, 5]]]
# soln
a=quiz.inject([]){|a,q|a[a.size..a.size]=q;a}
# \soln
puts a.inspect

#* Shuffle the contents of a provided Array.
puts "\n-- 03 --"
quiz=(1..20).entries
# soln
1.upto(50){x=rand(quiz.size);quiz[x],quiz[0]=quiz[0],quiz[x]}
# \soln
puts quiz.inspect

#* Given a Ruby class name in String form (like
#"GhostWheel::Expression::LookAhead"), fetch the actual class object.
puts "\n-- 04 --"
require 'ostruct'
quiz= "OpenStruct"
# soln
a= eval(quiz).new
# \soln
puts a.class

#* Insert newlines into a paragraph of prose (provided in a String)
so lines will
#wrap at 40 characters.
puts "\n-- 05 --"
puts "---------|---------|---------|---------|"

quiz= "* Insert newlines into a paragraph of prose (provided in a

String) so lines will wrap at 40 characters."

# soln
a= quiz.gsub(/.{1,40}(?:\s|\Z)/){$&+"\n"}
# \soln
puts a

#* Given an Array of String words, build an Array of only those words
that are
#anagrams of the first word in the Array.
puts "\n-- 06 --"
quiz= %w[cat dog tac act sheep cow]
# soln
a=[];quiz[1...quiz.size].each{|x|a<<x if quiz[0].split
(//).sort==x.split(//).sort}
# /soln
puts a.inspect

#* Convert a ThinkGeek t-shirt slogan (in String form) into a binary
#representation (still a String). For example, the popular shirt
"you are dumb"
#is actually printed as:


#
# 111100111011111110101
# 110000111100101100101
# 1100100111010111011011100010

puts "\n-- 07 --"
quiz= "you are dumb"
# soln
quiz.unpack('c*').each{|c| print c==32 ? "\n" : "%b"%[c]};
# /soln

#* Provided with an open File object, select a random line of content.
puts "\n\n-- 08 --"
quiz= File.open(TEXT_FILE)
# soln
x=[];quiz.each{|line|x<<line};puts x[rand(x.size)];quiz.close
# \soln

#* Given a wondrous number Integer, produce the sequence (in an
Array). A
#wondrous number is a number that eventually reaches one, if you
apply the
#following rules to build a sequence from it. If the current number
in the
#sequence is even, the next number is that number divided by two.
When the
#current number is odd, multiply that number by three and add one to
get the next
#number in the sequence. Therefore, if we start with the wondrous
number 15, the
#sequence is [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5,
16, 8, 4, 2,
#1].
puts "\n-- 09 --"
quiz=[15]
# soln
a=quiz.last; while a>1; quiz << (a=a%2==0 ? a/2 : a==1 ? 1 : a*3+1) end
# \soln
puts quiz.inspect

#* Convert an Array of objects to nested Hashes such that %w[one two
three four
#five] becomes {"one" => {"two" => {"three" => {"four" => "five"}}}}.
puts "\n-- 10 --"
quiz= %w[one two three four five]
# soln
a=quiz.reverse[1...quiz.size].inject(quiz.last){|b,c| {c=> b}}
# \soln
puts a.inspect


Ken Bloom

unread,
Feb 11, 2007, 3:36:42 PM2/11/07
to
On Sun, 11 Feb 2007 19:15:44 +0000, Ken Bloom wrote:

> On Sun, 11 Feb 2007 06:30:52 -0800, Phrogz wrote:
>
>> Here are my solutions to Quiz #113. For some of them I just couldn't
>> help but to provide a couple variations.
>>
>> Because I'm a terrible golfer, most strive for elegance (in some form)
>> over terseness.
>>
>>
>> # 1 - Commafy Numerics
>> i,f=quiz.to_s.split('.'); i.gsub(/(\d)(?=\d{3}+$)/,'\\1,') + (f ?
>> ('.'+f) : '')
>
> Good answer. you helped me golf mine down a little bit by getting rid of
> array indexing:
>
> i,f=i.to_s.split('.');"#{i.reverse.scan(/.{1,3}/).join(',').reverse}.#{f}"

Another answer, based on yours, this one is exactly one expression (no
semicolons):
quiz.to_s.gsub(/(\d)(?=\d{3}+#{quiz.to_s=~/\./?/\./:/$/})/,'\\1,')

Phrogz

unread,
Feb 11, 2007, 5:23:06 PM2/11/07
to
On Feb 11, 9:27 am, "Louis J Scoras" <louis.j.sco...@gmail.com> wrote:
> def anagrams(quiz)
> n=quiz[0].split(//).sort; quiz.select {|i| i.split(//).sort == n }
> end

Argh! #select is exactly what I should have used. Nice.

> def hashify(quiz)
> quiz.reverse.inject(){|m,i|{i=>m}}
> end

How very elegant. I kept thinking there should be a way to bootstrap
the inner pair to be like all the outers, but couldn't find it. Well
done.


Alex Young

unread,
Feb 11, 2007, 6:40:18 PM2/11/07
to
My solutions. Be gentle, this is my first quiz :-) I haven't checked
these against any of the test cases that were posted, but I think a
couple of them are interesting.

# Commify numbers.
def one(quiz)
quiz.to_s.reverse.gsub(/(\d{3})(?=\d)/,'\1,').reverse
end

# Commify numbers again, but ignore any before the decimal point.
def one_alternate(quiz)
a,b=quiz.to_s.split('.');[a.reverse.gsub(/(\d{3})(?=\d)/,'\1,').reverse,b].join('.')
end

# One-level flatten().
def two(quiz)
r=[];quiz.each{|a|r+=[*a]};r
end

# Array shuffling the noddy way.
def three(quiz)
r={};quiz.each{|a|r[a]=nil};r.keys
end

# Array shuffling the proper way.
def three_alternate(quiz)
r=[];quiz.size.times{r<<quiz.delete_at(rand(quiz.size))};r
end

# Getting classes from strings.
def four(quiz)
quiz.split('::').inject(Object){|m,r|m=m.const_get(r)}
end

# Line wrapping.
def five(quiz)
r='';quiz.size.times{|i|r<<quiz[i].chr;i%40==39?r<<"\n":1};r
end

# Finding anagrams.
def six(quiz)
(c=quiz.map{|a|[a,a.split('').sort.join]}).select{|b|b[1]==c[0][1]}.map{|d|d[0]}
end

# Binary strings.
def seven(quiz)
quiz.split(' ').map{|s|s.unpack('B*')[0][1..-1]}*$/
end

# Random lines.
def eight(quiz)
(a=quiz.readlines)[rand(a.size)]
end

# Wondrous numbers
def nine(quiz)
a=quiz;r=[a];r<<(a=a%2==0?a/2:1+a*3)while a!=1;r
end

# Hash construction
def ten(quiz)
(a = quiz.pop;quiz).reverse.inject(a){|m,r| m = {r => m}}
end

--
Alex

James Edward Gray II

unread,
Feb 11, 2007, 6:40:57 PM2/11/07
to
On Feb 11, 2007, at 8:35 AM, Phrogz wrote:

> Here are my solutions to Quiz #113.

When I built the quiz, I used the following solutions to reality-
check myself. (Making sure I could find a viable answer.)

#
# Given a Numeric, provide a String representation with commas
inserted between
# each set of three digits in front of the decimal. For example,
1999995.99


# should become "1,999,995.99".
#

quiz.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,"\\1,").reverse

#
# Given a nested Array of Arrays, perform a flatten()-like operation
that
# removes only the top level of nesting. For example, [1, [2, [3]]]
would
# become [1, 2, [3]].
#
quiz.inject(Array.new) { |arr, a| arr.push(*a) }

# Shuffle the contents of a provided Array.
quiz.sort_by { rand }

#
# Given a Ruby class name in String form (like


# "GhostWheel::Expression::LookAhead"), fetch the actual class object.

#
quiz.split("::").inject(Object) { |par, const| par.const_get(const) }

#
# Insert newlines into a paragraph of prose (provided in a String) so
lines will
# wrap at 40 characters.
#
quiz.gsub!(/(.{1,40}|\S{41,})(?: +|$\n?)/, "\\1\n")

#
# Given an Array of String words, build an Array of only those words
that are
# anagrams of the first word in the Array.
#
quiz.select { |w| w.split("").sort == quiz.first.split("").sort }

#
# Convert a ThinkGeek t-shirt slogan (in String form) into a binary
# representation (still a String). For example, the popular shirt
"you are
# dumb" is actually printed as:


#
# 111100111011111110101
# 110000111100101100101
# 1100100111010111011011100010

#
quiz.split("").map { |c| c == " " ? "\n" : c[0].to_s(2) }.join

# Provided with an open File object, select a random line of content.
quiz.inject { |choice, line| rand < 1/quiz.lineno.to_f ? line : choice }

#
# Given a wondrous number Integer, produce the sequence (in an
Array). A
# wondrous number is a number that eventually reaches one, if you
apply the
# following rules to build a sequence from it. If the current number
in the
# sequence is even, the next number is that number divided by two.
When the
# current number is odd, multiply that number by three and add one to
get the
# next number in the sequence. Therefore, if we start with the
wondrous number
# 15, the sequence is [15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20,
10, 5, 16,
# 8, 4, 2, 1].
#
Hash.new { |h, n| n == 1 ? [1] : [n] + h[n % 2 == 0 ? n/2 : n*3+1] }
[quiz]

#
# Convert an Array of objects to nested Hashes such that %w[one two
three four
# five] becomes {"one" => {"two" => {"three" => {"four" => "five"}}}}.
#
quiz.reverse.inject { |res, wrap| {wrap => res} }

James Edward Gray II


Phrogz

unread,
Feb 12, 2007, 12:59:19 AM2/12/07
to
On Feb 11, 4:40 pm, James Edward Gray II <j...@grayproductions.net>
wrote:

> quiz.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,"\\1,").reverse
> quiz.inject(Array.new) { |arr, a| arr.push(*a) }
> quiz.sort_by { rand }

> quiz.split("::").inject(Object) { |par, const| par.const_get(const) }
> quiz.gsub!(/(.{1,40}|\S{41,})(?: +|$\n?)/, "\\1\n")
> quiz.select { |w| w.split("").sort == quiz.first.split("").sort }
> quiz.split("").map { |c| c == " " ? "\n" : c[0].to_s(2) }.join
> quiz.inject { |choice, line| rand < 1/quiz.lineno.to_f ? line : choice }
> Hash.new{|h, n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3+1] }[quiz]

> quiz.reverse.inject { |res, wrap| {wrap => res} }

What I particularly admire about your solutions is that they are all
single- or chained-statements. None of the "a=[];...;a" nonsense that
I mucked about with. I wish I had been sure that it was possible to do
this for all solutions, so that I would have looked harder.

I can't tell if I think the reverse/regexp/reverse technique you (and
many others) used for the first problem is more or less elegant than a
single regexp on the integer portion. I suspect that mine is faster at
runtime...but speed is rarely an appropriate measure of elegance.

Kudos on the memoizing wondrous number, btw. :)

Phrogz

unread,
Feb 12, 2007, 1:07:58 AM2/12/07
to
On Feb 11, 4:40 pm, Alex Young <a...@blackkettle.org> wrote:
> # One-level flatten().

> r=[];quiz.each{|a|r+=[*a]};r

Elegantly small!

Eric I.

unread,
Feb 12, 2007, 8:18:30 AM2/12/07
to

When I saw that, I thought that it could definitely be shortened by
using an inject. After all, there'd be no assignments of r, no final
returning of r, no semicolons. But when all was said and done, it
ended up being the same length. Compare:

quiz.inject([]){|r,a|r+[*a]}


r=[];quiz.each{|a|r+=[*a]};r

Oh well....

Eric

----------------
Are you interested in on-site Ruby training that uses well-designed,
real-world, hands-on exercises? http://LearnRuby.com

Krishna Dole

unread,
Feb 12, 2007, 12:14:26 PM2/12/07
to
I don't think my solutions contribute much that is new, but you can
see them below and on pastie: http://pastie.caboo.se/39741.

I thought it was interesting that many previous solutions to the
commify problem posted on lists and on RubyForge fail for numbers like
0.23423423.

Looking at others' solutions, I really like Robert Dober's
sort_by{rand} solution to the array shuffle.

cheers,
Krishna

----------------------------------------------------------------
require 'test/unit'
# test setup mostly borrowed from Jamie Macey

class OneLiner
class << self

# this was the hardest one for me. this answer is not
# entirely my own, as it was inspired by
# http://rubyforge.org/snippet/detail.php?type=snippet&id=8
# (which does not work for numbers like 0.234234234234)
def commaize(quiz)
quiz.to_s.sub(/^(-*)(\d+)/){|m| $1 + $2.gsub(/(\d)(?=\d{3}+$)/, '\1,')}
end

def flatten_once(quiz)
quiz.inject([]){|n, e| e.is_a?(Array) ? n + e : n << e }
end

def shuffle(quiz)
a = quiz.dup; Array.new(a.size).map{|i| a.delete_at(rand(a.size)) }
end

def get_class(quiz)
require quiz.downcase.split("::")[0..-2].join("/"); eval quiz
end

def wrap_text(quiz)
quiz.gsub(/(.{1,40}(\s|$))/, '\1' + "\n").chop
end

def find_anagrams(quiz)
quiz.select{|w| w.scan(/./).sort == quiz[0].scan(/./).sort}
end

def binarize(quiz)
s = ""; quiz.each_byte {|c| c == 32 ? s << "\n" : s << "%b" % c}; s
end

# using #readlines would be easiest, but unlike that, this solution
# should work fine on files that are too big to hold in memory.
# unfortunately, it is more than 80 chars when using a variable
# named 'quiz'
def random_line(quiz)
i = rand(quiz.each{|l|}.lineno); quiz.rewind; quiz.each{|l|
return l if quiz.lineno == i+1}
end

# i know. it's 6 lines, not one. and more than 80 chars :(
def wondrous_sequence(quiz)
a = [n = quiz]; while n != 1; n = (n % 2 > 0 ? n * 3 + 1 : n /
2); a << n; end; a
end

# i guess it is cheating to use recursion (two lines)
# but it worked too nicely to resist here.
def nested_hash(quiz)
quiz.size > 1 ? {quiz[0] => nested_hash(quiz[1..-1])} : quiz[0]
end
end
end

require 'tempfile'
class TestOneLiner < Test::Unit::TestCase


# Given a Numeric, provide a String representation with commas inserted

# between each set of three digits in front of the decimal. For example,
# 1999995.99 should become "1,999,995.99".
def test_commaize
assert_equal "995", OneLiner.commaize(995)
assert_equal "1,995", OneLiner.commaize(1995)
assert_equal "12,995", OneLiner.commaize(12995)
assert_equal "123,995", OneLiner.commaize(123995)
assert_equal "1,234,995", OneLiner.commaize(1234995)
assert_equal "1,234,567,890,995", OneLiner.commaize(1234567890995)
assert_equal "99,995.992349834", OneLiner.commaize(99995.992349834)
assert_equal "0.992349834", OneLiner.commaize(0.992349834)
assert_equal "-0.992349834", OneLiner.commaize(-0.992349834)
assert_equal "999,995.99", OneLiner.commaize(999995.99)
assert_equal "-1,999,995.99", OneLiner.commaize(-1999995.99)
end

# Given a nested Array of Arrays, perform a flatten()-like operation that
# removes only the top level of nesting. For example, [1, [2, [3]]] would
# become [1, 2, [3]].

def test_flatten_once
ary = [1, [2, [3, 4]]]
flatter_ary = [1, 2, [3, 4]]
assert_equal flatter_ary, OneLiner.flatten_once(ary)
end

# Shuffle the contents of a provided Array.

def test_shuffle
ary = [3,1,4,1,5,9]
shuffled_ary = OneLiner.shuffle(ary)
assert_not_equal ary, shuffled_ary
assert_equal ary.sort, shuffled_ary.sort
end

# Given a Ruby class name in String form (like
# "GhostWheel::Expression::LookAhead"), fetch the actual class object.

def test_get_class
assert_equal Test::Unit::TestCase,
OneLiner.get_class("Test::Unit::TestCase")
end

# Insert newlines into a paragraph of prose (provided in a String) so

# lines will wrap at 40 characters.
def test_wrap_text
wrapped = "Insert newlines into a paragraph of " + "\n" +
"prose (provided in a String) so lines " + "\n" +


"will wrap at 40 characters."

paragraph = "Insert newlines into a paragraph of " +
"prose (provided in a String) so lines " +


"will wrap at 40 characters."

assert_equal wrapped, OneLiner.wrap_text(paragraph)
end

# Given an Array of String words, build an Array of only those words that

# are anagrams of the first word in the Array.
def test_find_anagrams
anagrams = %w(tac cat act)


assert_equal anagrams, OneLiner.find_anagrams(%w(tac bat cat rat act))
end

# Convert a ThinkGeek t-shirt slogan (in String form) into a binary
# representation (still a String). For example, the popular shirt

# "you are dumb" is actually printed as:


# 111100111011111110101
# 110000111100101100101
# 1100100111010111011011100010

def test_binarize
output = "111100111011111110101" + "\n" +
"110000111100101100101" + "\n" +
"1100100111010111011011100010"
assert_equal output, OneLiner.binarize("you are dumb")
end

# Provided with an open File object, select a random line of content.

#
# NOTE: This test assumes you're using File#read to get the string data
# from the file - if doing otherwise, update the test?
def test_random_line
f = Tempfile.new("foo")
f.print("development:
adapter: mysql
database: redvase_development
host: localhost
username: root
password:")
f.flush
f.rewind
lines = f.readlines
line = OneLiner.random_line(f)
assert_equal true, lines.include?(line)

end

# Given a wondrous number Integer, produce the sequence (in an Array). A
# wondrous number is a number that eventually reaches one, if you apply

# the following rules to build a sequence from it. If the current number
# in the sequence is even, the next number is that number divided by two.
# When the current number is odd, multiply that number by three and add
# one to get the next number in the sequence. Therefore, if we start with
# the wondrous number 15, the sequence is [15, 46, 23, 70, 35, 106, 53,
# 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1].
def test_wondrous_sequence
seq = [23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
assert_equal seq, OneLiner.wondrous_sequence(23)
end

# Convert an Array of objects to nested Hashes such that %w[one two three

# four five] becomes {"one" => {"two" => {"three" => {"four" => "five"}}}}.
def test_nested_hash
hash = {:o => {:t => {:t => {:f => :f}}}}
assert_equal hash, OneLiner.nested_hash([:o, :t, :t, :f, :f])
end
end

Alex Young

unread,
Feb 12, 2007, 12:17:29 PM2/12/07
to
Robert Dober wrote:

> On 2/12/07, Eric I. <rubytr...@gmail.com> wrote:
>>
>> On Feb 12, 1:07 am, "Phrogz" <g...@refinery.com> wrote:
>> > On Feb 11, 4:40 pm, Alex Young <a...@blackkettle.org> wrote:
>> >
>> > > # One-level flatten().
>> > > r=[];quiz.each{|a|r+=[*a]};r
>> >
>> > Elegantly small!
>>
>> When I saw that, I thought that it could definitely be shortened by
>> using an inject. After all, there'd be no assignments of r, no final
>> returning of r, no semicolons. But when all was said and done, it
>> ended up being the same length. Compare:
>>
>> quiz.inject([]){|r,a|r+[*a]}
Given the equal lengths, I prefer this - the r=[];...;r idiom strikes me
as rather ugly.

>> r=[];quiz.each{|a|r+=[*a]};r
>>
>> Oh well....
>>
>> Eric
>
>

> Comparing this with James' solution we can it down to
> quiz.inject{|r,a|[*r]+[*a]}
Wow :-)

--
Alex

James Edward Gray II

unread,
Feb 12, 2007, 12:29:55 PM2/12/07
to
On Feb 12, 2007, at 12:00 AM, Phrogz wrote:

> What I particularly admire about your solutions is that they are all
> single- or chained-statements.

Thank you.

> I can't tell if I think the reverse/regexp/reverse technique you (and
> many others) used for the first problem is more or less elegant than a
> single regexp on the integer portion. I suspect that mine is faster at
> runtime...but speed is rarely an appropriate measure of elegance.

I'm pretty sure I learned that reverse(), gsub(), and reverse() trick
from Perl's FAQ years ago. I just checked now though and the answer
is not what I recall, so maybe I am misremembering that.

> Kudos on the memoizing wondrous number, btw. :)

It's not actually. I never assign the Hash value. ;)

James Edward Gray II

Phrogz

unread,
Feb 12, 2007, 12:56:48 PM2/12/07
to
On Feb 12, 10:29 am, James Edward Gray II <j...@grayproductions.net>
wrote:

> On Feb 12, 2007, at 12:00 AM, Phrogz wrote:
> > > Hash.new{|h, n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3+1] }[quiz]
> > Kudos on the memoizing wondrous number, btw. :)
>
> It's not actually. I never assign the Hash value. ;)

Interesting point. (And, of course, it wouldn't be useful if it
memoized the result for a single call to the function.) I think it's
interesting because this pattern really allows you to (ab)use the
block form of Hash as a lambda that passes a reference to itself as
one of its arguments. Very convenient for one-liners.

James Edward Gray II

unread,
Feb 12, 2007, 2:04:12 PM2/12/07
to
On Feb 12, 2007, at 12:26 PM, Robert Dober wrote:

> 521/21 > cat bench.rb
> require 'benchmark'
>
> array = (1..100000).map { rand * (ARGV.first||1_000_000).to_f }
>
> def phrogz quiz


> i,f=quiz.to_s.split('.'); i.gsub(/(\d)(?=\d{3}+$)/,'\\1,') + (f ?
> ('.'+f) : '')

> end
> def james quiz


> quiz.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,"\\1,").reverse

> end
>
>
> Benchmark.bmbm do |x|
> x.report("Phrogz") {array.map{ |e| phrogz(e) }}
> x.report("James") {array.map{ |e| james(e) }}
> end

How does this code work? You pass arguments to methods we don't
see. The ones we do see don't even accept arguments.

The results look right though:

#!/usr/bin/env ruby -w

require "benchmark"

def phrogz(num)
i,f=num.to_s.split('.'); i.gsub(/(\d)(?=\d{3}+$)/,'\\1,') + (f ?
('.'+f) : '')
end

def james(num)
num.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,"\\1,").reverse
end

TESTS = Array.new(100_000) { rand(1_000_000) + 1.to_f / (rand(1_000)
+ 1) }
Benchmark.bmbm do |results|
results.report("Phrogz:") { TESTS.each { |n| phrogz(n) } }
results.report("James:") { TESTS.each { |n| james(n) } }
end
# >> Rehearsal -------------------------------------------
# >> Phrogz: 1.690000 0.010000 1.700000 ( 1.700985)
# >> James: 1.550000 0.010000 1.560000 ( 1.557882)
# >> ---------------------------------- total: 3.260000sec
# >>
# >> user system total real
# >> Phrogz: 1.690000 0.010000 1.700000 ( 1.703213)
# >> James: 1.520000 0.000000 1.520000 ( 1.528621)

__END__

James Edward Gray II

Ken Bloom

unread,
Feb 13, 2007, 12:08:24 PM2/13/07
to
>>> Hash.new{|h, n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3+1] }[quiz]
>> Kudos on the memoizing wondrous number, btw. :)
> It's not actually. I never assign the Hash value. ;)

So it's equivalent to:
(h=lambda {|n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3+1] })[23]

--Ken

James Edward Gray II

unread,
Feb 13, 2007, 12:41:35 PM2/13/07
to
On Feb 13, 2007, at 11:10 AM, Ken Bloom wrote:

>>>> Hash.new{|h, n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3+1] }
>>>> [quiz]
>>> Kudos on the memoizing wondrous number, btw. :)
>> It's not actually. I never assign the Hash value. ;)
>
> So it's equivalent to:
> (h=lambda {|n| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3+1] })[23]

Exactly.

James Edward Gray II

James Edward Gray II

unread,
Feb 13, 2007, 1:04:40 PM2/13/07
to
On Feb 13, 2007, at 11:56 AM, Robert Dober wrote:

> That really puts the spotlight on it, this will go away in 2.0
> though if
> I understand correctly :) and one would need to write:
>
> (h=lambda {|n,lmb| n==1 ? [1] : [n] + h[n%2 == 0 ? n/2 : n*3
> +1,lmb] })[23,h]
>
>
> is that right?

Hmm, I wasn't aware of this. What makes you say that?

James Edward Gray II

Simon Kröger

unread,
Feb 13, 2007, 2:32:41 PM2/13/07
to
Alex Young wrote:
> Robert Dober wrote:

Hi All! Hi James! Hi Robert!

just wanted to note that there seems to be not a single message
from Robert on the newsgroup side of life.

(i'm not using googles interface, but to clarify)

comp.lang.ruby
http://groups.google.de/group/comp.lang.ruby/browse_thread/thread/90223b4082d864fa/5eaa58d92745fda3?lnk=gst&q=Re%3A+%5BQUIZ%5D+One-Liners+(%23113)&rnum=1&hl=de#5eaa58d92745fda3

ruby-talk-google
http://groups.google.de/group/ruby-talk-google/browse_thread/thread/90223b4082d864fa/b42536dc92412aeb?lnk=gst&q=Re%3A+%5BQUIZ%5D+One-Liners+(%23113)&rnum=1&hl=de#b42536dc92412aeb

If this is old news, feel free to ignore me.

cheers

Simon

James Edward Gray II

unread,
Feb 13, 2007, 2:44:59 PM2/13/07
to
On Feb 13, 2007, at 1:30 PM, Simon Kröger wrote:

> Alex Young wrote:
>> Robert Dober wrote:
>
> Hi All! Hi James! Hi Robert!
>
> just wanted to note that there seems to be not a single message
> from Robert on the newsgroup side of life.

Yes, Robert's messages are sent as multipart/alternative. Here's the
header from his latest one:

Content-Type: multipart/alternative; boundary="----
=_Part_86388_20605457.1171389368901"

The Gateway's current Usenet host doesn't support this message
format. (It's not technically a legal Usenet post.) You can read
more about this at:

http://blog.grayproductions.net/articles/2006/12/01/what-is-the-ruby-
talk-gateway

James Edward Gray II


Simon Kröger

unread,
Feb 13, 2007, 5:54:20 PM2/13/07
to
James Edward Gray II schrieb:

Sorry for not reading that before, i wasn't aware of that resource.

So Robert, would you please configure your mail-client?
(I would really like to read your posts)

cheers

Simon

Robert Dober

unread,
Feb 14, 2007, 1:51:29 AM2/14/07
to
Thanx
Did you get this one?

I just switched off "Rich formatting" I did not find much else to do :(

Robert

--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous

Simon Kröger

unread,
Feb 14, 2007, 2:59:43 AM2/14/07
to
Robert Dober wrote:

> Thanx
> Did you get this one?

Yes!

> I just switched off "Rich formatting" I did not find much else to do :(

You do not have to do any more than that. Thanks.

> Robert

cheers

Simon

Robert Dober

unread,
Feb 14, 2007, 3:10:21 AM2/14/07
to
Great; great, thx to you and apologies to the list for the noise.
But it might be a good thing to know for all fellow g-mailers :)

Cheers

Ruby Quiz

unread,
Feb 15, 2007, 8:32:10 AM2/15/07
to
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:

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:

quiz.sort{rand}

Yikes!

Does that even work? Let's ask IRb:

>> quiz = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>> quiz.sort{rand}
=> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
>> quiz.sort{rand}
=> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
>> quiz.sort{rand}
=> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
>> quiz.sort{rand}
=> [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
>> quiz.sort{rand}
=> [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:

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:

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:

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:

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:

(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:

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:

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:

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:

(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:

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:

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...

Benedikt Heinen

unread,
Feb 15, 2007, 9:37:42 AM2/15/07
to
On Thu, 15 Feb 2007, Ruby Quiz wrote:
> quiz.sort{rand}

> Does that even work? Let's ask IRb:
>
> >> quiz = (1..10).to_a
> => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
> >> quiz.sort{rand}
> => [10, 6, 1, 7, 3, 8, 5, 9, 4, 2]
> >> quiz.sort{rand}
> => [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:
>
> 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

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

quiz.sort{2*rand-1.0}

to make the range -1.0..1.0.

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

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


James Edward Gray II

unread,
Feb 15, 2007, 9:53:50 AM2/15/07
to

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:

quiz.sort_by { rand }

> >> quiz.sort{rand-0.5}
> => [4, 2, 3, 9, 7, 8, 1, 6, 5, 10]
> >> quiz.sort{rand-0.5}
> => [9, 6, 2, 5, 1, 4, 3, 8, 7, 10]
> >> quiz.sort{rand-0.5}
> => [3, 8, 10, 6, 9, 5, 1, 7, 2, 4]
> >> quiz.sort{rand-0.5}
> => [4, 7, 10, 1, 9, 5, 2, 8, 6, 3]
> >> quiz.sort{rand-0.5}
> => [8, 5, 10, 9, 2, 1, 7, 3, 4, 6]
> >> quiz.sort{rand-0.5}
> => [6, 9, 2, 8, 1, 7, 4, 10, 3, 5]
> >> quiz.sort{rand-0.5}
> => [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|
results.report("sort:") { TESTS.times { data.sort { rand - 0.5 } } }
results.report("map->sort->map:") do
TESTS.times do
data.map { |e| [rand, e] }.sort.map { |arr| arr.last }
end
end
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)

Michael Ulm

unread,
Feb 16, 2007, 3:47:18 AM2/16/07
to
Benedikt Heinen schrieb:

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,
[1, 2, 3]=>2533, [3, 1, 2]=>1264, [1, 3, 2]=>1243}

HTH,

Michael

--
Michael Ulm
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-542, fax: +43 2236 21081
e-mail: micha...@isis-papyrus.com
Visit our Website: 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.
---------------------------------------------------------------

0 new messages