Skip to content

Instantly share code, notes, and snippets.

@austintaylor
Last active September 5, 2015 01:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save austintaylor/417389 to your computer and use it in GitHub Desktop.
Save austintaylor/417389 to your computer and use it in GitHub Desktop.
Ruby vs. Haskell – to_english
# This is the version I wrote in Ruby. I considered it to be pretty tight code at the time.
# It only handles integers from 0 to 999. When I wrote this, I understood that it ought to
# be more generalized, but I couldn't figure out how to do it in Ruby.
class Integer
ONES = %w(zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen)
TENS = %w(twenty thirty fourty fifty sixty seventy eighty ninety)
def to_english
if self < 20
ONES[self]
elsif self < 100
if ones == 0
TENS[self/10-2]
else
tens.to_english + '-' + ones.to_english
end
else
if self == hundreds
(hundreds/100).to_english + ' hundred'
else
hundreds.to_english + ' and ' + (tens + ones).to_english
end
end
end
def hundreds
self - self % 100
end
def tens
self % 100 - ones
end
def ones
self % 10
end
end
-- This is basically a port of the Ruby version. This seems like tighter code to me. There's also an
-- easy, linear way to extend the upper limit. The duplication is more visible here than in Ruby.
toEnglish :: Int -> String
toEnglish x
| x < 20 = ones !! x
| x < 100 && mod x 10 == 0 = tens !! (x `div` 10 - 2)
| x < 100 = (toEnglish $ x `div` 10 * 10) ++ "-" ++ (toEnglish $ mod x 10)
| x < 1000 && mod x 100 == 0 = (toEnglish $ x `div` 100) ++ " hundred"
| x < 1000 = (toEnglish $ x `div` 100 * 100) ++ " " ++ (toEnglish $ mod x 100)
where ones = words "zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen"
tens = words "twenty thirty forty fifty sixty seventy eighty ninety"
-- This is my final version in Haskell. This is what I wanted to write in Ruby, but couldn't figure out how.
-- I imagine it would be possible to back-port this now that it is working. This version is limited only by
-- the number of large number names you put in orderNames.
toEnglish :: Int -> String
toEnglish x
| x < 0 = "negative " ++ toEnglish (-x)
| x < 20 = ones !! x
| mod x d == 0 && n == 0 = tens !! div x d
| mod x d == 0 = (toEnglish $ div x d) ++ delim ++ orderNames !! n
| otherwise = (toEnglish $ div x d * d) ++ delim ++ (toEnglish $ mod x d)
where n = length (takeWhile (x >=) orders) - 1
d = orders !! n
delim = if n == 0 then "-" else " "
orders = 10:100:[1000 ^ x | x <- [1..]]
ones = words "zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen"
tens = words "zero ten twenty thirty forty fifty sixty seventy eighty ninety"
orderNames = words "ten hundred thousand million billion trillion quadrillion quintillion sextillion septillion octillion"
-- A poor man's test case for the last implementation.
main = do
printEnglish 0
printEnglish 12
printEnglish 30
printEnglish 99
printEnglish 400
printEnglish 436
printEnglish 1000
printEnglish 465112
printEnglish 123456789123456789
printEnglish $ -123456789123456789
where printEnglish = putStrLn . show . toEnglish
Further study:
- Port the last version back to Ruby. Compare.
- Handle floats in both languages and compare how code is shared between the integer and float implementations.
- Write proper unit tests in Haskell.
; A first pass at porting the Haskell version to Clojure
(defn to-english [x]
(let [ orders (concat [10 100] (map #(Math/pow 1000 %) (range 1 8)))
n (max (dec (count (take-while #(>= x %) orders))) 0)
d (nth orders n)
delim (if (zero? n) "-" " ")
ones (.split "zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen" " ")
tens (.split "zero ten twenty thirty forty fifty sixty seventy eighty ninety" " ")
orderNames (.split "ten hundred thousand million billion trillion quadrillion quintillion sextillion septillion octillion" " ") ]
(cond (< x 0) (str "negative " (to-english (- x)))
(< x 20) (nth ones x)
(and (zero? (mod x d)) (zero? n)) (nth tens (quot x d))
(zero? (mod x d)) (str (to-english (quot x d)) delim (nth orderNames n))
true (str (to-english (* (quot x d) d)) delim (to-english (mod x d))))))
(defn print-english [x]
(println (to-english x)))
(print-english 0)
(print-english 12)
(print-english 30)
(print-english 99)
(print-english 400)
(print-english 436)
(print-english 1000)
(print-english 465112)
(print-english 123456789123456789)
(print-english -123456789123456789)
package english
import "fmt"
var (
ones = [...]string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"}
tens = [...]string{"zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"}
orders = [...]string{"ten", "hundred", "thousand", "million", "billion"}
orderMax = [...]int{10, 100, 1000, 1000000, 1000000000}
)
func English(x int) string {
if x < 0 {
return fmt.Sprint("negative ", English(-x))
}
if x < 20 {
return ones[x]
}
var n int
var delim string
for ; n < len(orderMax)-1 && x >= orderMax[n+1]; n++ {
}
if n == 0 {
delim = "-"
} else {
delim = " "
}
if d := orderMax[n]; x%d == 0 {
if n == 0 {
return tens[x/d]
} else {
return fmt.Sprint(English(x/d), delim, orders[n])
}
} else {
return fmt.Sprint(English(x/d*d), delim, English(x%d))
}
return ""
}
package english
import "testing"
func TestEnglish(t *testing.T) {
var tests = []struct {
input int
expected string
}{
{0, "zero"},
{12, "twelve"},
{30, "thirty"},
{99, "ninety-nine"},
{-99, "negative ninety-nine"},
{400, "four hundred"},
{436, "four hundred thirty-six"},
{1000, "one thousand"},
{465112, "four hundred sixty-five thousand one hundred twelve"},
{2147483647, "two billion one hundred forty-seven million four hundred eighty-three thousand six hundred forty-seven"},
{-2147483647, "negative two billion one hundred forty-seven million four hundred eighty-three thousand six hundred forty-seven"},
// current algo work with int min (-2147483648) because that number can't be negated
}
for _, c := range tests {
actual := English(c.input)
if actual != c.expected {
t.Errorf("English(%d) == %q, expected %q", c.input, actual, c.expected)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment