[QUIZ] Roman Numerals (#22)

R

Ruby Quiz

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!

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

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

Brian Schröder

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!

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

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.

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
 
N

Nikolai Weibull

* Ruby Quiz (Mar 04, 2005 15:00):
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.

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
 
J

James Edward Gray II

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
 
B

Brian Schröder

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

400 I'd say ;)
 
K

Karl von Laudermann

Ruby Quiz said:
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? :)
 
M

Mark Hubbart

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.

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
 
T

Timothy Byrd

Since I'm new to Ruby, I may be more enthusiastic than skillful, but
here is my solution.

##########

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
 
J

Jason Bailey

--------------020503000008070704030306
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

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.

thanks
jason

--------------020503000008070704030306
Content-Type: text/plain;
name="convert.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="convert.rb"

#!/usr/bin/env ruby

@data = [
["M" , 1000],
["CM" , 900],
["D" , 500],
["CD" , 400],
["C" , 100],
["XC" , 90],
["L" , 50],
["XL" , 40],
["X" , 10],
["IX" , 9],
["V" , 5],
["IV" , 4],
["I" , 1]
]

@roman = %r{^[CDILMVX]*$}
@arabic = %r{^[0-9]*$}

def toRoman(num)
reply = ""
for key, value in @data
count, num = num.divmod(value)
reply << (key * count)
end
reply
end

def toArabic(rom)
reply = 0
for key, value in @data
while rom.index(key) == 0
reply += value
rom.slice!(key)
end
end
reply
end

$stdin.each do |line|
case line
when @roman
puts toArabic(line)
when @arabic
puts toRoman(line.to_i)
end
end


--------------020503000008070704030306--
 
J

James Edward Gray II

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
 
N

Nikolai Weibull

* 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
 
L

Logan Capaldo

* 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

--
::: 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);}

Cheater. ;)
 
B

Bill Guindon

Well, it's an odd approach, but I had fun with it:

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}
 
D

Dave Burt

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

# 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
 
D

Dave Burt

Bill Guindon said:
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' => '?'}

Very cool, Bill!
 
B

Brian Schröder

[Snipped Quiz Description]

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
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top