Commits

Miki Tebeka committed b0eefa0

number -> english

  • Participants
  • Parent commits 37dd48d

Comments (0)

Files changed (1)

+package main
+/* Convert number to English words.
+
+Algorithm from http://mini.net/tcl/591
+*/
+
+import (
+	"flag"
+	"fmt"
+	"http"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+	"template"
+)
+
+// Tokens from 1000 and up.
+var pronounce = []string {
+    "vigintillion",
+    "novemdecillion",
+    "octodecillion",
+    "septendecillion",
+    "sexdecillion",
+    "quindecillion",
+    "quattuordecillion",
+    "tredecillion",
+    "duodecillion",
+    "undecillion",
+    "decillion",
+    "nonillion",
+    "octillion",
+    "septillion",
+    "sextillion",
+    "quintillion",
+    "quadrillion",
+    "trillion",
+    "billion",
+    "million",
+    "thousand",
+    "",
+}
+
+// Tokens up to 90.
+var small = map[string]string {
+    "0" : "",
+    "1" : "one",
+    "2" : "two",
+    "3" : "three",
+    "4" : "four",
+    "5" : "five",
+    "6" : "six",
+    "7" : "seven",
+    "8" : "eight",
+    "9" : "nine",
+    "10" : "ten",
+    "11" : "eleven",
+    "12" : "twelve",
+    "13" : "thirteen",
+    "14" : "fourteen",
+    "15" : "fifteen",
+    "16" : "sixteen",
+    "17" : "seventeen",
+    "18" : "eighteen",
+    "19" : "nineteen",
+    "20" : "twenty",
+    "30" : "thirty",
+    "40" : "forty",
+    "50" : "fifty",
+    "60" : "sixty",
+    "70" : "seventy",
+    "80" : "eighty",
+    "90" : "ninety",
+}
+
+// Get token <= 90, return '' if not matched.
+func getNum(num string) string {
+	n, ok := small[num]
+	if ok {
+		return n
+	}
+	return ""
+}
+
+func max(a, b int) int {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+// Split list to triplets (starting from end). Pad last one with "" if needed.
+func triplets(n string) []string {
+	result := []string{}
+	for i := len(n); i > 0; i -= 3 {
+		first := max(i-3, 0)
+		chunk := n[first:i]
+		for len(chunk) < 3 {
+			chunk = fmt.Sprintf("%s ", chunk)
+		}
+		result = append(result, chunk)
+	}
+
+	return result
+}
+
+// Normelize number (remove 0"s prefix). Return number and string.
+func normNum(num string) (int, string) {
+	n, _ := strconv.Atoi(strings.TrimSpace(num))
+	return n, fmt.Sprintf("%d", n)
+}
+
+// English representaion of a number <= 999.
+func small2eng(num string) string {
+	n, num := normNum(num)
+	hundred, ten := "", ""
+	if len(num) == 3 {
+		hundred = getNum(string(num[0])) + " hundred"
+		num = string(num[1:])
+		n, num = normNum(num)
+	}
+
+    if n > 20 && n != n/10*10 { // Got ones
+		tens := getNum(string(num[0]) + "0")
+		ones := getNum(string(num[1]))
+        ten = tens + " " + ones
+	} else {
+        ten = getNum(num)
+	}
+
+    if len(hundred)>0 && len(ten)>0 {
+        return hundred + " and " + ten
+	}
+
+	return hundred + ten
+}
+
+// English representation of a number.
+func num2eng(num string) (string, os.Error) {
+    if len(num)/3 >= len(pronounce) {
+		return "", os.NewError("number too big")
+	}
+
+    if num == "0" { // Zero is a special case
+        return "zero", nil
+	}
+
+    // Create reversed number
+	parts := []string{} // Result accumolator
+	ct := len(pronounce) - 1 // Current index
+	for _, chunk := range triplets(num) {
+		p := small2eng(chunk)
+        if len(p) > 0 {
+			parts = append(parts, p + " " + pronounce[ct])
+		}
+        ct -= 1
+	}
+
+	size := len(parts)
+	for i := 0; i < size/2; i++ {
+		j := size-i-1
+		parts[i], parts[j] = parts[j], parts[i]
+	}
+
+    return strings.Join(parts, ", "), nil
+}
+
+type Reply struct {
+	Num string
+	English string
+	Error string
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+	num := r.FormValue("num")
+	reply := Reply{Num: num}
+
+	// Write on return
+	defer func() {
+		formTemplate.Execute(w, reply)
+	}()
+
+	if len(num) == 0 {
+		return
+	}
+
+	ok, _ := regexp.Match("^[0-9]+$", []byte(num))
+	if !ok {
+		reply.Error = "Bad number (can be only numbers)"
+		return
+	}
+
+	english, err := num2eng(num)
+	if err != nil {
+		reply.Error = err.String()
+		return
+	}
+	reply.English = english
+}
+
+var port = flag.Int("port", 8080, "port to run on")
+
+func main() {
+	fmt.Printf("Serving on %d\n", *port)
+	http.HandleFunc("/", handler)
+	http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
+}
+
+var formTemplate = template.Must(template.New("num2eng").Parse(
+`
+<html>
+	<head>
+		<style>
+			div.error {
+				color: red;
+			}
+			div.english {
+				padding-top: 10px;
+				font-family: Georgia;
+				font-size: 1.5em;
+			}
+			body {
+				width: 80%;
+				margin-left: 10%;
+				padding: 10px;
+				border: 1px solid blue;
+			}
+			input[name="num"] {
+				width: 80%;
+			}
+		</style>
+	</head>
+	<body>
+	    <h1>Number To English</h1>
+		{{if len .Error }}
+		<div class="error">
+			{{.Error}}
+		</div>
+		{{end}}
+		<form action="/">
+		<input name="num" size="60" value="{{.Num}}" />
+		<input type="submit" value="Englishize" />
+		<br />
+		<div class="english">
+			{{.English}}
+		</div>
+		</form>
+	</body>
+</html>
+`))