TDD Roman Numeral tutorial in Ruby


(Phlip) #1

Jim Rogers wrote:

assert ("I" == roman(1));

/* assert (“II” == roman(2));
assert (“III” == roman(3));
assert (“IV” == roman(4));
assert (“V” == roman(5));
assert (“VI” == roman(6));
assert (“VII” == roman(7));
assert (“VIII” == roman(8));
*/

I would change the entire approach to the following:

type Roman_Numerals is (I, II, III, IV, V, VI, VII, VIII);

Your implementation duplicates I, V, etc. If you were to resolve that
duplication, it would form little tables. Then adding L, C, M etc.
would get easier.

I ran the experiment in Ruby, and put the result here:

http://www.xpsd.org/cgi-bin/wiki?TestDrivenDevelopmentTutorialRomanNumerals

The (unfinished) implementation is here:

Symbols = {
      1=>'I', 5=>'V', 10=>'X', 50=>'L', 100=>'C',
    }

def roman(num)
    return Symbols[num]  if Symbols.has_key?(num)

    [ [100,  1],
      [100, 10],
      [ 50, 10],
      [ 10,  1],
      [  5,  1],
      [  1,  0],
          ].each do |cutPoint, subtractor|
        if num >  cutPoint then
            return roman(cutPoint) + roman(num - cutPoint)
        end

        if num >= cutPoint - subtractor and num < cutPoint then
            return roman(subtractor) + roman(num + subtractor)
        end
    end
end

That code can’t do 49 (IL) yet, and it obviously can’t do D or M yet.
But it’s just as obvious how to add those.

That’s the point: Removing duplication while growing a design in ways
that improve its extensibility. There’s plenty of “duplication” of
various sorts in the above code. But merging it wouldn’t make the
design more extensible in the direction of more advanced roman numeral
codes. It turned out that merging the components of the output strings

  • I, V etc. - focused the design directly on what was important.
···


Phlip
http://www.xpsd.org/cgi-bin/wiki?TestFirstUserInterfaces


(Josef 'Jupp' SCHUGT) #2

Hi!

Wrote a simple conversion tool. Takes one command line argument.
Interpretes it as a decimal number. Prints roman number for it.
Returns 1 on error, 0 on success. Conversion of single digits has two
cases

a) digit is 4 or 9. In this case append n1[i] followed by n5[i] if
digit is 4 and by n1[i+1] if digit is 9.

b) digit is neither 4 nor 9. In this case append n5[i] if digit is
greater or equals 5. Then append d modulo 5 times n1[i].

As far as tables are concerned uses bare necessities - symbols need
to be stored in any case. Converting tool to a function is left as an
exercise :->

The algorithm is not new. It is a formal representation of what one
does when manually writing roman numbers. It can easily be expanded
to additional symbols by simply adding them to the lists.


#!/usr/bin/env ruby

n1 = [ ‘I’, ‘X’, ‘C’, ‘M’ ]
n5 = [ ‘V’, ‘L’, ‘D’, nil ]

max = 0

n1.each_index { |i| max += 3 * 10 ** i }
n5.each_index { |i| max += 5 * 10 ** i unless i.nil? }

exit 1 if ARGV.length != 1

x = ARGV.first.to_i
r = “”

exit 1 if x > max or x < 1

(n5.length - 1).downto(0) { |i|
d = x / 10 ** i
x -= d * 10 ** i
if d == 4 or d == 9
r << n1[i] + (d == 4 ? n5[i] : n1[i+1])
else
if d >= 5
r << n5[i]
d -= 5
end
r << n1[i] * (d % 5)
end
}

puts r
exit 0


Didn’t find a simpler solution. Improvements are welcome.

Josef ‘Jupp’ SCHUGT

···


E-Mail: .— …- .–. .–. .–.-. --. – -…- .-.-.- -… .
http://oss.erdfunkstelle.de/ruby/ - German comp.lang.ruby FAQ
http://rubyforge.org/users/jupp/ - Ruby projects at Rubyforge


(David A. Black) #3

Hi –

···

On Sat, 28 Feb 2004, Josef ‘Jupp’ SCHUGT wrote:

#!/usr/bin/env ruby

n1 = [ ‘I’, ‘X’, ‘C’, ‘M’ ]
n5 = [ ‘V’, ‘L’, ‘D’, nil ]

max = 0

n1.each_index { |i| max += 3 * 10 ** i }
n5.each_index { |i| max += 5 * 10 ** i unless i.nil? }

i will never be nil – it will always be an integer. (Did you mean
"unless n5[i].nil?’?)

David


David A. Black
dblack@wobblini.net


(Karsten Meier) #4

Hello Romans :slight_smile:

I have done a solution to work with romans for the pleac cookbook
project, see:

http://pleac.sourceforge.net/pleac_ruby/numbers.html
section “Working with Roman Numerals”

It does also the opposite conversation. I have looked at other solutions
in other languages before writing this, and was quite happy to came up
with this algorithm, because it is so simple. And ruby lets me write
things like “2004.to_roman”.

The pleac project is also a great way to compare phyton and ruby, if
somebody is still interrested in this.

Karsten Meier


(Josef 'Jupp' SCHUGT) #5

Hi!

  • David A. Black:

n5.each_index { |i| max += 5 * 10 ** i unless i.nil? }

i will never be nil – it will always be an integer. (Did you mean
"unless n5[i].nil?’?)

You are right. Stupid mistake.

Josef ‘Jupp’ SCHUGT

···


E-Mail: .— …- .–. .–. .–.-. --. – -…- .-.-.- -… .
http://oss.erdfunkstelle.de/ruby/ - German comp.lang.ruby FAQ
http://rubyforge.org/users/jupp/ - Ruby projects at Rubyforge


(Carlos) #6

Hello Romans :slight_smile:

I have done a solution to work with romans for the pleac cookbook
project, see:

http://pleac.sourceforge.net/pleac_ruby/numbers.html
section “Working with Roman Numerals”

It does also the opposite conversation. I have looked at other solutions
in other languages before writing this, and was quite happy to came up
with this algorithm, because it is so simple. And ruby lets me write
things like “2004.to_roman”.

Hi,

A trivial readability improvement:

Instead of

    for entry in @@romanlist
        sym, num = entry[0], entry[1]

I think it’s better to write

    for sym, num in @@romanlist

Bye.


(Mark J. Reed) #7

angus angus@quovadis.com.ar writes:

A trivial readability improvement:

Instead of

   for entry in @@romanlist
       sym, num = entry[0], entry[1]

I think it’s better to write
for sym, num in @@romanlist

Or even

    @@romanlist.each do
        >sym, num|
        ...
    end

I tend to forget that Ruby even has a “for” keyword. :slight_smile:

-Mark