[QUIZ] Roman Numerals (#22)

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.

VIII is 8

All I Ever Needed To Know I Learned from "History of the World"

···

On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz <james@grayproductions.net> wrote:

        VII is 8

--
Bill Guindon (aka aGorilla)

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

···

On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz <james@grayproductions.net> wrote:

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.

--
Brian Schröder
http://ruby.brian-schroeder.de/

* 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

···

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

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

convert.rb (745 Bytes)

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}

···

--
Bill Guindon (aka aGorilla)

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:

···

On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz <james@grayproductions.net> wrote:

[Snipped Quiz Description]

--------------

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

--
Brian Schröder
http://ruby.brian-schroeder.de/

I think the additional stipulation for "subtractors"
(other than the order of magnitude one) is that the
"subtractors" must be a power of 10 (I, X or C) *not*
V, L, or D.

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

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

···

----

Oops. Thank you.

James Edward Gray II

···

On Mar 4, 2005, at 8:10 AM, Bill Guindon wrote:

On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz > <james@grayproductions.net> wrote:

        VII is 8

VIII is 8

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

···

On Mar 4, 2005, at 9:13 AM, Brian Schröder wrote:

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

···

On Sat, 5 Mar 2005 00:13:56 +0900, Brian Schröder <ruby.brian@gmail.com> wrote:

On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz <james@grayproductions.net> wrote:
> 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.

Well, if you're new (heck, even if you're not), I'm impressed. Nice solution!

James Edward Gray II

···

On Mar 6, 2005, at 3:17 PM, Jason Bailey wrote:

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.

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

Here's my own late entry for this party. My use of a prebuilt Array means all the others are faster than mine, at least on small data sets. :wink:

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

CD is 400

and I'm still waiting for History of the World Part II

···

On Sat, 5 Mar 2005 00:33:11 +0900, James Edward Gray II <james@grayproductions.net> wrote:

IV is 4
IX is 9
XL is 40
XC is 90
CD is 4000
CM is 900

--
Bill Guindon (aka aGorilla)

> 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

400 I'd say :wink:

···

On Sat, 5 Mar 2005 00:33:11 +0900, James Edward Gray II <james@grayproductions.net> wrote:

On Mar 4, 2005, at 9:13 AM, Brian Schröder wrote:

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

--
Brian Schröder
http://ruby.brian-schroeder.de/

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

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.

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

···

On Sat, March 5, 2005 5:38 pm, Mark Hubbart said:

On Sat, 5 Mar 2005 00:13:56 +0900, Brian Schröder <ruby.brian@gmail.com> > wrote:

On Fri, 4 Mar 2005 22:53:20 +0900, Ruby Quiz <james@grayproductions.net> >> wrote:

Cheater. :wink:

···

On Mon, 7 Mar 2005 10:24:27 +0900, Nikolai Weibull <mailing-lists.ruby-talk@rawuncut.elitemail.org> wrote:

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

Thank you :slight_smile:

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

James Edward Gray II wrote:

···

On Mar 6, 2005, at 3:17 PM, Jason Bailey wrote:

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!

Nikolai Weibull <mailing-lists.ruby-talk@rawuncut.elitemail.org> writes:

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

···

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org