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