Learn to Program, by Chris Pine

J

Jan_K

Chapter 9, exercise 3 (page 76)

Modern Roman numerals. Eventually, someone thought it would
be terribly clever if putting a smaller number before a larger one
meant you had to subtract the smaller one. As a result of this
development, you must now suffer. Rewrite your previous method
to return the new-style Roman numerals, so when someone calls
roman_numeral 4, it should return 'IV'.


Solution:
----------------------------------------------------------------
def roman_numeral input

while input < 1 || input > 3999
puts 'Please enter a number between 1 and 3999'
input = gets.chomp.to_i
end

m_mod = input%1000
d_mod = input%500
c_mod = input%100
l_mod = input%50
x_mod = input%10
v_mod = input%5

m_div = input/1000
d_div = m_mod/500
c_div = d_mod/100
l_div = c_mod/50
x_div = l_mod/10
v_div = x_mod/5
i_div = v_mod/1

m = 'M' * m_div
d = 'D' * d_div
c = 'C' * c_div
l = 'L' * l_div
x = 'X' * x_div
v = 'V' * v_div
i = 'I' * i_div

if i == 'IIII' && v != 'V'
i = 'IV'
elsif i == 'IIII'
v = 'IX'
i = ''
end

if x == 'XXXX' && l != 'L'
x = 'XL'
elsif x == 'XXXX'
l = 'XC'
x = ''
end

if c == 'CCCC' && d != 'D'
c = 'CD'
elsif c == 'CCCC'
d = 'CM'
c = ''
end

puts m + d + c + l + x + v + i

end

number = gets.chomp.to_i
roman_numeral(number)
----------------------------------------------------------------
 
H

He Fa

For interest, before I bought the book I did this exercise for the
online tuorial. That was before I knew about recursives, here is what
I came up with... it works, but it's not pretty



words_array = []
puts 'Please enter any word'
word = gets.chomp

while word != ''
words_array.push word
puts 'Great, please enter another word'
word = gets.chomp
end

sorted_array = []

words_array.each do |word|
if word.to_s.downcase > sorted_array.last.to_s.downcase
#add word to the end of sorted_array
sorted_array.push word

else
index = (sorted_array.length) - 1
initial_number = index
temp_array = []

#check word against next word in sorted_array and repeat
while word.to_s.downcase < sorted_array[index].to_s.downcase
#add the sorted_array to a temp_array up to that point
temp_array[index] = sorted_array[index]
#erase words in sorted_array up to that point
sorted_array.pop
index = index - 1
end

#add the word to sorted_array
sorted_array.push word

#add one by one the words from temp_array to sorted_array
while index < initial_number
sorted_array.push temp_array[index + 1]
index = index + 1
end
end
end

puts sorted_array
 
M

Mike Agres

I'm having some issues modifying the Civ continent example from ch. 10
to account for coordinates that are outside the array. I added the
following line:

if ((y <= 10 && y > 0) && (x <= 10 && x > 0))

before...

# So, first we count this tile...
size = 1
world[y][x] = 'counted land'


# ...then we count all of the
# neighboring eigth tiles (and,
# of course, their neighbors via recursion)
size = size + continent_size(world, x-1, y-1) #4,4
size = size + continent_size(world, x , y-1) #5,4
size = size + continent_size(world, x+1, y-1) #6,4
size = size + continent_size(world, x-1, y ) #4,5
size = size + continent_size(world, x+1, y ) #6,5
size = size + continent_size(world, x-1, y+1) #4,6
size = size + continent_size(world, x , y+1) #5,6
size = size + continent_size(world, x+1, y+1) #6,6
size
end

As a result, I get the following error:

TypeError: nil can't be coerced into Fixnum

method + in civilization.rb at line 35
method continent_size in civilization.rb at line 35
method continent_size in civilization.rb at line 35
method continent_size in civilization.rb at line 36
method continent_size in civilization.rb at line 35
method continent_size in civilization.rb at line 40
method continent_size in civilization.rb at line 39
method continent_size in civilization.rb at line 41
method continent_size in civilization.rb at line 39
method continent_size in civilization.rb at line 41
method continent_size in civilization.rb at line 39
method continent_size in civilization.rb at line 36
method continent_size in civilization.rb at line 35
at top level in civilization.rb at line 48

This error appears regardless of whether the arguments I pass are in 5,5
(as in the example Chris Pine wrote out) or something like 10,10 (which
is on the edge of the 'continent').

Is that the right conditional statement? Could it be in the wrong place
in the method?
 
M

Mariano Kamp

Hi Mike,

I don't know the book and that is probably true for a lot of the
people here. Maybe you should include the full source code.
Especially interesting would be to see the line numbers as the
interpreter clearly indicates where the problem occurs.

As a totally wild guess ... The error message says that the right
side of an addition is nil. So looking at your code, "size" seems to
be set, maybe continent_size does return nil?

What I also think is strange is that "world" seems to be a two
dimensional array when you first mention it (world[x][y] = 'counted
land'), but later on you pass it to continent size along with two
parameters. This might be ok, if continent_size defines three
parameters, like this:

def continent_size(world, x, y); end

and you use x and y to access the array then?!

I don't know about the conditional. What are you trying to check?
That both x and y are between 1 and 10? That should work ... Even
though you wouldn't need the braces as it all of the conditionals
need to be true for the whole expression to become true. This would
be different if you use "or".

Hope that helps!

Cheers,
Mariano
I'm having some issues modifying the Civ continent example from ch. 10
to account for coordinates that are outside the array. I added the
following line:

if ((y <= 10 && y > 0) && (x <= 10 && x > 0))

before...

# So, first we count this tile...
size = 1
world[y][x] = 'counted land'


# ...then we count all of the
# neighboring eigth tiles (and,
# of course, their neighbors via recursion)
size = size + continent_size(world, x-1, y-1) #4,4
size = size + continent_size(world, x , y-1) #5,4
size = size + continent_size(world, x+1, y-1) #6,4
size = size + continent_size(world, x-1, y ) #4,5
size = size + continent_size(world, x+1, y ) #6,5
size = size + continent_size(world, x-1, y+1) #4,6
size = size + continent_size(world, x , y+1) #5,6
size = size + continent_size(world, x+1, y+1) #6,6
size
end

As a result, I get the following error:

TypeError: nil can't be coerced into Fixnum

method + in civilization.rb at line 35
method continent_size in civilization.rb at line 35
method continent_size in civilization.rb at line 35
method continent_size in civilization.rb at line 36
method continent_size in civilization.rb at line 35
method continent_size in civilization.rb at line 40
method continent_size in civilization.rb at line 39
method continent_size in civilization.rb at line 41
method continent_size in civilization.rb at line 39
method continent_size in civilization.rb at line 41
method continent_size in civilization.rb at line 39
method continent_size in civilization.rb at line 36
method continent_size in civilization.rb at line 35
at top level in civilization.rb at line 48

This error appears regardless of whether the arguments I pass are
in 5,5
(as in the example Chris Pine wrote out) or something like 10,10
(which
is on the edge of the 'continent').

Is that the right conditional statement? Could it be in the wrong
place
in the method?
 
M

Mike Agres

here's the whole code...

# These are just to make the map easier to read. "M" is
# visually more dense than "o".

M = 'land'
o = 'water'

world = [[M,o,o,o,o,o,o,o,o,o,M],
[o,M,M,o,M,M,o,o,o,M,M],
[o,o,M,M,o,o,o,o,M,M,o],
[o,o,o,M,o,o,o,o,o,M,o],
[o,o,o,M,o,M,M,o,o,o,o],
[o,o,o,o,M,M,M,M,o,o,o],
[o,o,o,M,M,M,M,M,M,M,o],
[o,o,o,M,M,o,M,M,M,o,o],
[o,o,M,M,o,o,M,M,M,o,o],
[o,M,M,M,o,M,o,o,o,M,M],
[M,o,o,o,o,o,o,o,o,o,M]]

def continent_size world, x, y
if world[y][x] != 'land'
# either it's water or we've already counted it; we don't want to
count it
# again
return 0
end

if ((y <= 10 && y > 0) && (x <= 10 && x > 0))
# So, first we count this tile...
size = 1
world[y][x] = 'counted land'


# ...then we count all of the
# neighboring eigth tiles (and,
# of course, their neighbors via recursion)
size = size + continent_size(world, x-1, y-1)
size = size + continent_size(world, x , y-1)
size = size + continent_size(world, x+1, y-1)
size = size + continent_size(world, x-1, y )
size = size + continent_size(world, x+1, y )
size = size + continent_size(world, x-1, y+1)
size = size + continent_size(world, x , y+1)
size = size + continent_size(world, x+1, y+1)
size
end
end

puts continent_size(world, 5, 5) #this should be fine; but what about
(11,11)?
 
M

Mariano Kamp

Hi Mike,

what your guard should do is prevent the recursion go across the
boundaries of your world, right?

I would put the guard at the beginning of the method to make sure
that the rest of the method can be sure of handling valid data.

if x < 0 || x > 10 || y < 0 || y > 10
return 0 # respect the world's boundaries
end

def continent_size world, x, y
if world[y][x] != 'land'
# either it's water or we've already counted it; we don't want to
count it
# again
return 0
end
That's a bit of a problem as your code might already call with out of
bounds coordinates.
Put the boundary guard (see above) before that.
if ((y <= 10 && y > 0) && (x <= 10 && x > 0))
An array starts to count at zero ... so it needs to be (y <= 10 && y
= 0), right?
You have eleven columns and eleven rows. So the array indices are 0..10.
# So, first we count this tile...
size = 1
world[y][x] = 'counted land'


# ...then we count all of the
# neighboring eigth tiles (and,
# of course, their neighbors via recursion)
size = size + continent_size(world, x-1, y-1)
size = size + continent_size(world, x , y-1)
size = size + continent_size(world, x+1, y-1)
size = size + continent_size(world, x-1, y )
size = size + continent_size(world, x+1, y )
size = size + continent_size(world, x-1, y+1)
size = size + continent_size(world, x , y+1)
size = size + continent_size(world, x+1, y+1)
size
Ok, you're returning the size here, but
what are you returning here, if you haven't been in this if-block?

nil. That is a major part of your problem...
Use the code above and put it after the definition of the method.
Remove your own "if" and your method should work.

puts continent_size(world, 5, 5) #this should be fine; but what about
(11,11)?
That's up to you. In my code (the guard) it returns 0. So that 11,11
will return 0.
You could also raise an exception that the coordinates are out-of-
bounds, but I guess that will come later in your book and is not that
straight forward applicable here.

Cheers,
Mariano
 
M

Mike Agres

He Fa:

Good Job! However, I don't fully understand how this worked.
words_array.each do |word|
if word.to_s.downcase > sorted_array.last.to_s.downcase
#add word to the end of sorted_array
sorted_array.push word

else
index = (sorted_array.length) - 1
initial_number = index
temp_array = []


The second WHILE section was where I got really confused.
#check word against next word in sorted_array and repeat
while word.to_s.downcase < sorted_array[index].to_s.downcase
#add the sorted_array to a temp_array up to that point
temp_array[index] = sorted_array[index]
#erase words in sorted_array up to that point
sorted_array.pop
index = index - 1
end

#add the word to sorted_array
sorted_array.push word

#add one by one the words from temp_array to sorted_array
while index < initial_number
sorted_array.push temp_array[index + 1]
index = index + 1
end
end
end

puts sorted_array

I've been wrapping my head around my own version of the non-recursive
sort for days and haven't made it work.
 
T

Tm Bo

Hello all. Thanks to all the posters who came before me! You've been
tremendous help. I have a quick question on Chapter 11 - Reading and
Writing, Section 11.3:

<quote>
file_name = 'TestFile.txt'
test_string = 'Nothing!'

File.open file_name, 'w' do |f|
f.write test_string
end

read_string = File.read file_name
puts(read_string == test_string)
</quote>

I poked around the web, but I can't find out what the 'w' is for. Can
anyone shed any light? Muchas gracias in advance.

-Todd
 
B

Bill Guindon

Hello all. Thanks to all the posters who came before me! You've been
tremendous help. I have a quick question on Chapter 11 - Reading and
Writing, Section 11.3:

<quote>
file_name = 'TestFile.txt'
test_string = 'Nothing!'

File.open file_name, 'w' do |f|
f.write test_string
end

read_string = File.read file_name
puts(read_string == test_string)
</quote>

I poked around the web, but I can't find out what the 'w' is for. Can
anyone shed any light? Muchas gracias in advance.

It's the file 'mode'. w = write.
http://www.zenspider.com/Languages/Ruby/QuickRef.html#15
 
C

Chad Perrin

Hello all. Thanks to all the posters who came before me! You've been
tremendous help. I have a quick question on Chapter 11 - Reading and
Writing, Section 11.3:

<quote>
file_name = 'TestFile.txt'
test_string = 'Nothing!'

File.open file_name, 'w' do |f|
f.write test_string
end

read_string = File.read file_name
puts(read_string == test_string)
</quote>

I poked around the web, but I can't find out what the 'w' is for. Can
anyone shed any light? Muchas gracias in advance.

Check out this page:

http://www.rubycentral.com/book/ref_c_file.html

Looking at it, I see that File.open is sometimes a synonym for File.new,
so I checked File.new, and that's where I found documentation of the use
of characters in that position in the method call.

The w itself basically means "write". It refers to how you open the file
(for reading, writing, or both). Hopefully this gives you a helpful
start on figuring out the rest of the details of how File.new and
File.open work.

I was surprised to find that File.open isn't in my local ri database,
though.
 
J

John Joyce

Thanks, donald. I don't know why I missed that before.
You will do well to make notes on File and read/write modes!
The basic implementation of this kind of thing is almost the same in
many lanugages, Ruby's is no exception, the style of this comes
straight from C .
You will see it in PHP too, and a host of other languages.. (all C's
children and relatives.)
Some books fail to document this stuff well for beginners. they
either don't want to go there yet, or they forget about it since they
assume you know it from other languages.
Mr. Pine just isn't going there in his book. He's focusing on other
subjects
 
K

Kelly Tanguay

This is my take on the same problem:


def roman_num number
set1 = [ 1, 5, 10, 50, 100, 500, 1000 ]
set2 = [ 'I', 'V', 'X', 'L', 'C', 'D', 'M' ]
numeral = []
while number > 0
if (number/(set1.last)) >= 1
roman = (number/(set1.last))
numeral.push((set2.pop)*roman)
number = (number%(set1.pop))
else
set2.pop
set1.pop
end
end
puts 'Old Roman Numeral is ' + numeral.join + '.'
end

puts 'Please enter a number to see what it is in old roman numerals.'
number = gets.chomp.to_i
while number < 1 || number > 3999
puts 'Please enter a number between 1 and 3999'
number = gets.chomp.to_i
end
roman_num number
 
T

Todd Benson

This is my take on the same problem:


def roman_num number
set1 = [ 1, 5, 10, 50, 100, 500, 1000 ]
set2 = [ 'I', 'V', 'X', 'L', 'C', 'D', 'M' ]
numeral = []
while number > 0
if (number/(set1.last)) >= 1
roman = (number/(set1.last))
numeral.push((set2.pop)*roman)
number = (number%(set1.pop))
else
set2.pop
set1.pop
end
end
puts 'Old Roman Numeral is ' + numeral.join + '.'
end

puts 'Please enter a number to see what it is in old roman numerals.'
number = gets.chomp.to_i
while number < 1 || number > 3999
puts 'Please enter a number between 1 and 3999'
number = gets.chomp.to_i
end
roman_num number

Just for fun. Certainly not great for speed for large numbers, but the
integer max was low so...

H = Hash[*(([1,5,10,50,100,500,1000].zip %w|I V X L C D M|).flatten)]
def roman(n, s="")
H.keys.sort.reverse.each do |k|
s << (H[k] * (n / k))
n %= k
end
s
end
puts roman(ARGV[0].to_i)

The usage would simply be "ruby <filename>.rb <number>"

I'm sure someone could come up with a one-liner, though.

Todd
 
T

Todd Benson

Just for fun. Certainly not great for speed for large numbers, but the
integer max was low so...

H = Hash[*(([1,5,10,50,100,500,1000].zip %w|I V X L C D M|).flatten)]
def roman(n, s="")
H.keys.sort.reverse.each do |k|
s << (H[k] * (n / k))
n %= k
end
s
end
puts roman(ARGV[0].to_i)

The usage would simply be "ruby <filename>.rb <number>"

I'm sure someone could come up with a one-liner, though.

Actually, better production code that keeps in line with OO concepts would be...

class Integer
ROMAN = {
1 => "I",
5 => "V",
10 => "X",
50 => "L",
100 => "C",
500 => "D",
1000 => "M"
}

def roman
number, roman_string = self, ""
ROMAN.keys.sort.reverse.each do |key|
roman_string << (ROMAN[n] * (number/key)
n %= key
end
roman_string
end
end

#usage
puts 2137.roman
# => "MMCCCXVII"

I'm pretty sure there were better and more thorough examples at
http://www.rubyquiz.com/quiz22.html.

Todd
 
T

Todd Benson

Actually, better production code that keeps in line with OO concepts would be...

class Integer
ROMAN = {
1 => "I",
5 => "V",
10 => "X",
50 => "L",
100 => "C",
500 => "D",
1000 => "M"
}

def roman
number, roman_string = self, ""
ROMAN.keys.sort.reverse.each do |key|
roman_string << (ROMAN[n] * (number/key)
n %= key
end
roman_string
end
end

#usage
puts 2137.roman
# => "MMCCCXVII"

I'm pretty sure there were better and more thorough examples at
http://www.rubyquiz.com/quiz22.html.

gsub('number', 'n') on that code...and there's a missing parenthesis
")" number/key line

Yeah, yeah

That's what I get for not using cut and paste :)

Todd
 
M

Marc Heiler

BTW a little trick for later, if you i.e. have an array, and find
yourself using a counter, you can stop using the counter and instead use
array.size and check on that value.


I like "Learn to Program" but I never bothered to make the examples.
Like with homework in school many years ago - I did this in school. Not
at home... :)
 
M

Moises Trovo

[Note: parts of this message were removed to make it a legal post.]

actually ROMAN[key] and not working with numbers having a 9.


Actually, better production code that keeps in line with OO concepts would be...

class Integer
ROMAN = {
1 => "I",
5 => "V",
10 => "X",
50 => "L",
100 => "C",
500 => "D",
1000 => "M"
}

def roman
number, roman_string = self, ""
ROMAN.keys.sort.reverse.each do |key|
roman_string << (ROMAN[n] * (number/key)
n %= key
end
roman_string
end
end

#usage
puts 2137.roman
# => "MMCCCXVII"

I'm pretty sure there were better and more thorough examples at
http://www.rubyquiz.com/quiz22.html.

gsub('number', 'n') on that code...and there's a missing parenthesis
")" number/key line

Yeah, yeah

That's what I get for not using cut and paste :)

Todd
 
T

Todd Benson

actually ROMAN[key] and not working with numbers having a 9.

Here's the copy and pasted code...

class Integer
ROMAN = {
1 => "I",
5 => "V",
10 => "X",
50 => "L",
100 => "C",
500 => "D",
1000 => "M"
}

def roman
n, roman_string = self, ""
ROMAN.keys.sort.reverse.each do |key|
roman_string << (ROMAN[key] * (n/key))
n %= key
end
roman_string
end
end

#usage
puts 2319.roman
# => "MMCCCXVIIII"


cheers,
Todd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,135
Latest member
VeronaShap
Top