Last active
September 5, 2015 01:44
-
-
Save austintaylor/417389 to your computer and use it in GitHub Desktop.
Ruby vs. Haskell – to_english
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 "" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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