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:
3. Enjoy!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
This week's quiz is to write a converter to and from Roman numerals.
The script should be a standard Unix filter, reading from files specified on the
command-line or STDIN and writing to STDOUT. Each line of input will contain
one integer (between 1 and 3999) expressed as an Arabic or Roman numeral. There
should be one line of output for each line of input, containing the original
number in the opposite format.
For example, given the following input:
III
29
38
CCXCI
1999
The correct output is:
3
XXIX
XXXVIII
291
MCMXCIX
If you're not familiar with or need a refresher on Roman numerals, the rules are
simple. First, there are seven letters associated with seven values:
I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000
You can combine letters to add values, by listing them largest to smallest from
left to right:
II is 2
VII is 8
XXXI is 31
However, you may only list three consecutive identical letters. That requires a
special rule to express numbers like 4 and 900. That rule is that a single
lower value may proceed a larger value, to indicate subtraction. This rule is
only used to build values not reachable by the previous rules:
IV is 4
CM is 900
But 15 is XV, not XVX.
> VII is 8
VIII is 8
All I Ever Needed To Know I Learned from "History of the World"
--
Bill Guindon (aka aGorilla)
> On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz
> <ja...@grayproductions.net> wrote:
>
>> VII is 8
>
> VIII is 8
Oops. Thank you.
James Edward Gray II
Hello James,
I know that in reality roman numbers didn't always follow the
conventions given here, so everything is a little bit more complex,
but this are more or less clear rules. Maybe one should add the
additional rule that shorter numbers are preferred over longer ones to
disambiguate a bit more. But the question I'm after:
When I follow your rules, I calculate MIM for 1999, why do you propose
the slightly less readable: MCMXCIX for this purpose? Also this does
not seem to be consistent with XXIX for 29.
best regards,
Brian
--
Brian Schröder
http://ruby.brian-schroeder.de/
These are the "new" roman numerals, the old ones were not expressed in
this manner, rather repeating four identical "letters" (the L, C, and M
are actually derived from older non-letter symbols but became
letter-shaped in the end) if necessary,
nikolai
--
::: name: Nikolai Weibull :: aliases: pcp / lone-star / aka :::
::: born: Chicago, IL USA :: loc atm: Gothenburg, Sweden :::
::: page: www.pcppopper.org :: fun atm: gf,lps,ruby,lisp,war3 :::
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}
> When I follow your rules, I calculate MIM for 1999, why do you propose
> the slightly less readable: MCMXCIX for this purpose? Also this does
> not seem to be consistent with XXIX for 29.
This is just me tripping myself up it seems. I'm not trying to be
clever. This is the standard Roman Numerals challenge.
I was trying to make the rules as simple as possible in plain language.
I tried to cover myself here with:
"This rule is only used to build values not reachable by the previous
rules"
That probably wasn't very clear though. Let me try again
IV is 4
IX is 9
XL is 40
XC is 90
CD is 4000
CM is 900
Those are the only cases where a lower value proceeds a bigger value.
Hopefully that clears up my intentions. Sorry for the confusion.
James Edward Gray II
> IV is 4
> IX is 9
> XL is 40
> XC is 90
> CD is 4000
> CM is 900
CD is 400
and I'm still waiting for History of the World Part II
400 I'd say ;)
> CM is 900
>
> Those are the only cases where a lower value proceeds a bigger value.
> Hopefully that clears up my intentions. Sorry for the confusion.
>
> James Edward Gray II
>
>
> On Sat, 5 Mar 2005 00:33:11 +0900, James Edward Gray II
> <ja...@grayproductions.net> wrote:
>
>> IV is 4
>> IX is 9
>> XL is 40
>> XC is 90
>> CD is 4000
>> CM is 900
>
> CD is 400
Egad. Good thing I have all of you to look after me!
Thanks again.
James Edward Gray II
> This week's quiz is to write a converter to and from Roman numerals.
>
> The script should be a standard Unix filter, reading from files specified on
> the
> command-line or STDIN and writing to STDOUT. Each line of input will contain
> one integer (between 1 and 3999) expressed as an Arabic or Roman numeral.
> There
> should be one line of output for each line of input, containing the original
> number in the opposite format.
Hey, isn't the solution to this quiz already in the Pickaxe II book? :-)
--
Karl von Laudermann - karlvonl(a)rcn.com - http://www.geocities.com/~karlvonl
#!/usr/bin/env ruby
c=" .,:;i|+=ahHME8";def l(a,b,c)x=b-a;y=c-a;Math.sqrt(x*x+y*y)end;25.times{|y|
50.times{|x|print(l(12,x/2,y)<=12?((c[l(8,x/2,y).to_i]||36).chr):" ")};puts""}
The rule of thumb as I remember it is that you can't prefix a symbol
that is greater than one order of magnitude (base ten). So, IX is
okay, where IC and IM are not.
HTH,
Mark
This is the correct modern stipulation. The Romans were slightly less
formal about it as long as the intention was clear (there's the famous
example of IIII instead of IV, as IV was forbidden because it appears
in the name of IVPITER).
> HTH,
> Mark
E
Afaik, this was the reason for Christians not to use IV (especially on
church clocks), not for the Romans.
--
Christian Neukirchen <chneuk...@gmail.com> http://chneukirchen.org
##########
class RomanNumerals
Roman_array = [
[ 1000, 'M' ],
[ 900, 'CM' ],
[ 500, 'D' ],
[ 400, 'CD' ],
[ 100, 'C' ],
[ 90, 'XC' ],
[ 50, 'L' ],
[ 40, 'XL' ],
[ 10, 'X' ],
[ 9, 'IX' ],
[ 5, 'V' ],
[ 4, 'IV' ],
[ 1, 'I' ]
]
def self.to_roman(val)
if val < 0 or val > 5000
"out of range '#{val}'"
else
s = ""
Roman_array.each { |pair|
while val >= pair[0]
s << pair[1]
val -= pair[0]
end
}
s
end
end
def self.from_roman(str)
value = 0
s = str.upcase
while s =~ /^(M|CM|D|CD|C|XC|L|XL|X|IX|V|IV|I)/
value += Roman_array.find() { |pair| pair[1] == $1 }[0]
s = $' #postmatch
end
if !s.empty?
value = value.to_s + " with invalid characters '#{s}'"
end
value
end
def self.translate(s)
if s.to_i > 0
to_roman(s.to_i)
else
from_roman(s)
end
end
end
$<.each_line { |line| puts RomanNumerals.translate(line.chomp) }
##########
-- Timothy
> I'm new to ruby so this was quite a fun little thing to help me get up
> to speed on what you can do with the language.
Well, if you're new (heck, even if you're not), I'm impressed. Nice
solution!
James Edward Gray II
Very nice.
Time for a shameless plug:
For the arabic-to-roman case, one could have done
require 'lisp/format';$stdin.each{|l|Lisp.format("~@R",l.to_i)}
But the Lisp module isn't part of the standard library...,
Cheater. ;)
//I'm new to ruby so this was quite a fun little thing to help
//me get up
//to speed on what you can do with the language.
it is fun to be a nuby. i'm nuby always :-)
//
//since this quite small and simple I've attached it.
i assume this is your version 1.
>convert.rb
4
IV
IIII
4
IIIIIII
7
I see that it does not do checking yet..
I will wait for your version 2 with the needed checks as i myself do not
know the rules of arabic to roman conversion.
kind regards -botp
//
//thanks
//jason
//
require 'optparse'
TITLE = "\nRoman Nvmeral Converter Version: I.IV\n"
HELP = "
Roman Nvmerals shall be rendered vnto Decimal.
Decimal Nvmerals shall be rendered vnto Caesar.
"
ARGV.options do |opts|
opts.banner = "Usage: ruby #{__FILE__} [options] [input files]"
opts.on('Options:')
opts.on("--help", "-h", "This text") { puts TITLE, '', opts, HELP; exit 0 }
opts.parse!
end
DEC_MAP = %w( 0 A AA AAA AB B BA BAA BAAA AC )
DEC_DIGIT = []
DEC_DIGIT << {'A' => 'I', 'B' => 'V', 'C' => 'X'}
DEC_DIGIT << {'A' => 'X', 'B' => 'L', 'C' => 'C'}
DEC_DIGIT << {'A' => 'C', 'B' => 'D', 'C' => 'M'}
DEC_DIGIT << {'A' => 'M', 'B' => '?', 'C' => '?'}
ROMAN={ 'I' => 1 , 'V' => 5 , 'X' => 10 , 'L' => 50 , 'C' => 100 , 'D'
=> 500 , 'M' => 1000 }
results = []
ARGF.each { |number|
number = number.upcase.chop
roman = number =~ /^[IVXLCDM]*$/
decimal = number =~ /^[0-9]*$/
case
when roman
skip = false
number = number.split('')
total = 0
number.each_with_index do |char, idx|
nextchar = number[idx + 1]
if skip
skip = false
else
if nextchar && (ROMAN[nextchar] > ROMAN[char])
total += ROMAN[nextchar] - ROMAN[char]
skip = true
else
total += ROMAN[char]
end
end
end
results << total
when decimal
number = number.split('').reverse
number.each_with_index do |place, idx|
number[idx] = DEC_MAP[place.to_i].split('')
number[idx].collect! do |char|
char = DEC_DIGIT[idx][char]
end
number[idx].join
end
number = number.reverse.join('')
results << number
else
results << "Nothing"
end
}
results.each {|result| puts result}
My solution is available here:
http://www.dave.burt.id.au/ruby/roman_numerals.rb
It can be used as a library (it pollutes String and Integer :) or as
required from the command line, filtering integers to roman numeral strings
and vice-versa.
An outline:
# Contains methods to convert integers to roman numeral strings and
vice-versa.
module RomanNumerals
# Maps roman numeral digits to their integer values
DIGITS = {
# The largest integer representable as a roman numerable by this module
MAX = 3999
# Maps some integers to their roman numeral values
@@digits_lookup = DIGITS.inject({
# Converts +int+ to a roman numeral
def self.from_integer(int)
# main loop:
@@digits_lookup.keys.sort.reverse.each do |digit_value|
while remainder >= digit_value
# Converts +roman_string+, a roman numeral, to an integer
def self.to_integer(roman_string)
# main loop:
roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|
class String
# Considers string a roman numeral numeral,
# and converts it to the corresponding integer.
def to_i_roman
class Integer
# Converts this integer to a roman numeral.
def to_s_roman
# Processes ARGF as per Quiz requirement - converts numbers to roman
numerals and vice versa
if __FILE__ == $0
Cheers,
Dave
Very cool, Bill!
Hello Group,
I attach my solution here, but as always the full color version can be found at:
http://ruby.brian-schroeder.de/quiz/roman/
The solution:
--------------
I chose to change the Integer class for this.
class Integer
First I set up two constants. The first is needed for conversion from
integer to roman, the second for conversion from roman to integer.
@@roman_values_assoc = %w(I IV V IX X XL L XC C CD D CM
M).zip([1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000]).reverse
@@roman_values = @@roman_values_assoc.inject({}) { |h, (r,a)|
h[r] = a; h }
Then we will come to the core. I implemented integer to roman as a
recursive process, where in each iteration the biggest possible part
of the number is matched by a roman number, then the rest is done the
same way. Negative roman numbers did not exist, but why not simply
prefix a roman number with a minus sign?
def roman
return "-#{(-self).roman}" if self < 0
return "" if self == 0
@@roman_values_assoc.each do | (i, v) |
return(i+(self-v).roman) if v <= self end
end
Converting romans to integer is done by reading the roman number from
right to left, detecting if we are in the subtraction or the addition
case and summing up the results. Nothing complicated.
def Integer.roman(roman)
last = roman[-1,1]
roman.reverse.split('').inject(0) { | result, c |
if @@roman_values[c] < @@roman_values[last]
result -= @@roman_values[c]
else
last = c
result += @@roman_values[c]
end
}
end
Thats all there is to roman numbers.
end
cheers,
Brian
That's what I coded.
Puzzled over the subtractor vs the repeats, until
I figured out one need to do both.
Here's my solution - now I'll be able to look
at everyone elses :)
Vance
--- roman.rb ---
#! /usr/bin/env ruby
def is_arabic?(n)
return n =~ /^[0-9]*$/
end
def to_roman(n)
r = ''
rd = %w{M D C L X V I}
val = [1000, 500, 100, 50, 10, 5, 1]
val.each_with_index { |v, i|
c = (n - (n % v))/v
if c < 4 && c > 0
c.times { r << rd[i] }
n -= (c * v)
end
s = (i % 2 == 0) ? i+2 : i+1
if (s < 7) && (n >= (val[i] - val[s]))
r << rd[s] << rd[i]
n -= (val[i] - val[s])
end
}
return r
end
def to_arabic(n)
val = { 'I' => 1, 'V' => 5, 'X' => 10, 'L' => 50,
'C' => 100, 'D' => 500, 'M' => 1000 }
sum = 0
lastval = 1000
n.each_byte {|c|
sum += val[c.chr]
sum -= (lastval * 2) if val[c.chr] > lastval
lastval = val[c.chr]
}
return sum
end
f = File.open(ARGV[0])
while line = f.gets
line.chomp!
val = is_arabic?(line) ? to_roman(line.to_i) : to_arabic(line)
puts "#{line} -> #{val}"
end
----
//Then we will come to the core. I implemented integer to roman
//as a recursive process, where in each iteration the biggest
// def roman
// return "-#{(-self).roman}" if self < 0
// return "" if self == 0
// @@roman_values_assoc.each do | (i, v) |
//return(i+(self-v).roman) if v <= self end
// end
clever. thanks for the tip.
-botp
I like to see how small and concise I can make my code and with a new
language its quite a challenge.
As a side commentary I did get annoyed with ruby a couple of times. An
example being the zero based comparison check.
I kept going down paths such as
rom.index(key).zero?
Since that made a lot of sense, then realizing that it wouldn't work
because the index method could return a nil as well as a number.
I'm definitly looking forward to the next one now.
jason
>4
>IV
>IIII
>4
>IIIIIII
>7
>
>I see that it does not do checking yet..
>I will wait for your version 2 with the needed checks as i myself do not
>know the rules of arabic to roman conversion.
>
>kind regards -botp
>
>
nope :) this is it.
if I was writing this as a proper class for use in an application I
would be doing things such as error checking and logging. However the
rules for this particular quiz only stated that the output has to be
formatted correctly. Since I am inherently lazy I will stand by the
rules and do only what the quiz told me I had to do ;)
jason
Dave
-----
Hi,
My solution is available here:
http://www.dave.burt.id.au/ruby/roman_numerals.rb
It can be used as a library (it pollutes String and Integer :) or as
required from the command line, filtering integers to roman numeral strings
and vice-versa.
An outline:
# Contains methods to convert integers to roman numeral strings and
vice-versa.
module RomanNumerals
# Maps roman numeral digits to their integer values
DIGITS = {
# The largest integer representable as a roman numerable by this module
MAX = 3999
# Stolen from O'Reilly's Perl Cookbook 6.23. Regular Expression Grabbag
REGEXP = /^M*(D?C{0,3}|C[DM])(L?X{0,3}|X[LC])(V?I{0,3}|I[VX])$/i
# Maps some integers to their roman numeral values
@@digits_lookup = DIGITS.inject({
# Converts +int+ to a roman numeral
def self.from_integer(int)
# main loop:
@@digits_lookup.keys.sort.reverse.each do |digit_value|
while remainder >= digit_value
# Converts +roman_string+, a roman numeral, to an integer
def self.to_integer(roman_string)
# main loop:
roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|
# Returns true iif +string+ is a roman numeral.
def self.is_roman_numeral?(string)
class String
# Considers string a roman numeral numeral,
# and converts it to the corresponding integer.
def to_i_roman
# Returns true iif the subject is a roman numeral.
def is_roman_numeral?
> Hello mailing list. This has already been posted on Usenet, sorry for
> the duplication. (The gateway seems to be one-way at the moment.)
Thanks for copying it over here so I would see it, Dave. I went back
and browsed the newsgroup and picked up Timothy Byrd's solution as
well. That misbehaving gateway is hard on me. :D
James Edward Gray II
> * Jason Bailey (Mar 06, 2005 22:20):
>> I'm new to ruby so this was quite a fun little thing to help me get up
>> to speed on what you can do with the language.
>>
>> since this quite small and simple I've attached it.
>
> Very nice.
>
> Time for a shameless plug:
>
> For the arabic-to-roman case, one could have done
>
> require 'lisp/format';$stdin.each{|l|Lisp.format("~@R",l.to_i)}
>
> But the Lisp module isn't part of the standard library...,
> nikolai
Does it exist at all? I *want* that!
> Does it exist at all? I *want* that!
I'll create a gem one of these days...
http://rubyforge.org/frs/?group_id=109
enjoy,
> * Christian Neukirchen (Mar 07, 2005 16:50):
>> > ... the Lisp module isn't part of the standard library...
>
>> Does it exist at all? I *want* that!
>
> I'll create a gem one of these days...
No need for that.
> http://rubyforge.org/frs/?group_id=109
'(THANKS A LOT)
> enjoy,
> nikolai
Being a 10-days-old Rubyist, I have to confest that I spent a lot of time
solving this problem and trying just for fun to make it more and more
concise at the price of clarity and no error handling. I've learned a lot
during the process. A very good problem to solve for beginners.
Yannick
--------------------
ARABIC = {"I" => 1, "IV" => 4, "V" => 5, "IX" => 9, "X" => 10, "XL" => 40,
"L" => 50, "XC" => 90, "C" => 100, "CD" => 400, "D" => 500, "CM" => 900,
"M" => 1000}
ROMAN = ARABIC.invert
def toInt(roman)
roman.reverse.split(//).inject(0) do |acc, letter|
acc += ARABIC[letter] * (acc > ARABIC[letter] * 3 ? -1 : 1)
end
end
def toRoman(number)
ARABIC.values.sort.reverse.inject("") do |acc, value|
mult, number = number.divmod(value)
acc << ROMAN[value] * mult
end
end
STDIN.readlines.each{|line| line.chomp!; print line, " = ", line.to_i > 0 ?
toRoman(line.to_i) : toInt(line), "\n"}
-------------------
Or Object Oriented:
-------------------
ARABIC = {"I" => 1, "IV" => 4, "V" => 5, "IX" => 9, "X" => 10, "XL" => 40,
"L" => 50, "XC" => 90, "C" => 100, "CD" => 400, "D" => 500, "CM" => 900,
"M" => 1000}
ROMAN = ARABIC.invert
class String
def roman_to_i()
self.reverse.split(//).inject(0) do |acc, letter|
acc += ARABIC[letter] * (acc > ARABIC[letter] * 3 ? -1 : 1)
end
end
end
class Integer
def to_roman()
remainder = self
ARABIC.values.sort.reverse.inject("") do |acc, value|
mult, remainder = remainder.divmod(value)
acc << ROMAN[value] * mult
end
end
end
STDIN.readlines.each{|line| line.chomp!; print line, " = ", line.to_i > 0 ?
line.to_i.to_roman : line.roman_to_i, "\n"}
ps: would've replied sooner, but seems the ml/usenet gateway is down
for a bit.
Me too... having to join a hundred-plus-messages-per-day mailing list that
floods my mailbox just to submit my quiz solution... life's tough, isn't it?
Anyway, I have an update quiz-solution-wise.
I had a niggling feeling something was missing from my solution before I
submitted. And everyone else's once I'd read them all. And here it is:
# Enables uppercase roman numerals to be used interchangeably with integers.
# They are auto-vivified RomanNumeral constants
# Synopsis:
# 4 + IV #=> VIII
# VIII + 7 #=> XV
# III ** III #=> XXVII
# VIII.divmod(III) #=> [II, II]
def Object.const_missing sym
raise NameError.new("uninitialized constant: #{sym}") unless
RomanNumerals::REGEXP === sym.to_s
const_set(sym, RomanNumeral.get(sym))
end
Of course, this requires a RomanNumeral class, which acts like an Integer but
looks like a... well, roman numeral.
Go and download http://www.dave.burt.id.au/ruby/roman_numerals.rb again!
Cheers,
Dave
That is a neat trick! Thanks a lot for sharing.
Regards,
Wow - I never knew about const_missing! This language never ceases to
surprise me :)
martin
> Go and download http://www.dave.burt.id.au/ruby/roman_numerals.rb again!
Nice, now Ruby has them, too, like Perl did before. Small patch here:
--- roman_numerals.rb.1 Tue Mar 8 08:27:13 2005
+++ roman_numerals.rb Tue Mar 8 13:39:07 2005
@@ -135,7 +135,7 @@
else
to_i = value.to_s.to_i_roman
end
- @@all_roman_numerals[to_i] || RomanNumeral.new(to_i)
+ @@all_roman_numerals[to_i] || new(to_i)
end
def inspect
You can do funny things with those Roman numbers, BTW:
>> I += 1 ; I == II
(irb):1: warning: already initialized constant I
=> true
;)
--
Florian Frank
James Edward Gray II
#!/usr/local/bin/ruby -w
ROMAN_MAP = { 1 => "I",
4 => "IV",
5 => "V",
9 => "IX",
10 => "X",
40 => "XL",
50 => "L",
90 => "XC",
100 => "C",
400 => "CD",
500 => "D",
900 => "CM",
1000 => "M" }
ROMAN_NUMERALS = Array.new(3999) do |index|
target = index + 1
ROMAN_MAP.keys.sort { |a, b| b <=> a }.inject("") do |roman, div|
times, target = target.divmod(div)
roman << ROMAN_MAP[div] * times
end
end
IS_ROMAN = /^#{ ROMAN_MAP.keys.sort { |a, b| b <=> a }.inject("") do
|exp, n|
num = ROMAN_MAP[n]
exp << if num.length == 2 then "(?:#{num})?" else num + "{0,3}" end
end }$/
IS_ARABIC = /^(?:[123]\d{3}|[1-9]\d{0,2})$/
if __FILE__ == $0
ARGF.each_line() do |line|
line.chomp!
case line
when IS_ROMAN then puts ROMAN_NUMERALS.index(line) + 1
when IS_ARABIC then puts ROMAN_NUMERALS[line.to_i - 1]
else raise "Invalid input: #{line}"
end
end
end