All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.bijection.EnglishInt.scala Maven / Gradle / Ivy

The newest version!
/**
 * Convert integers in the interval [0-1 BILLION] to spoken english & vice versa
 * examples -
 * six hundred ninety one million one hundred forty five thousand eight hundred twenty five is 691145825
 * six hundred ninety one million one hundred fifty eight thousand one hundred seventy is 691158170
 * six hundred ninety one million one hundred seventy thousand five hundred fifteen is 691170515
 * 2 is two
 * 23 is twenty three
 * 234 is two hundred thirty four
 *
 * Author: Krishnan Raman, [email protected]
 */

package com.twitter.bijection

case class EnglishInt(get: String) extends AnyVal

object EnglishInt {
  implicit val bijectionToInt: Bijection[Int, EnglishInt] = new AbstractBijection[Int, EnglishInt] {
    def apply(num: Int): EnglishInt = EnglishInt(toEnglish(num).get)
    override def invert(s: EnglishInt): Int = fromEnglish(s.get).get
  }

  val (t, d, k, m, g) = (10, 100, 1000, 1000 * 1000, 1000 * 1000 * 1000)
  val units = Map(0 -> "zero", 1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four", 5 -> "five", 6 -> "six", 7 -> "seven", 8 -> "eight", 9 -> "nine")
  val tens = Map(t -> "ten", 20 -> "twenty", 30 -> "thirty", 40 -> "forty", 50 -> "fifty", 60 -> "sixty", 70 -> "seventy", 80 -> "eighty", 90 -> "ninety")
  val teens = Map(t -> "ten", 11 -> "eleven", 12 -> "twelve", 13 -> "thirteen", 14 -> "fourteen", 15 -> "fifteen", 16 -> "sixteen", 17 -> "seventeen", 18 -> "eighteen", 19 -> "nineteen")
  val tenmult = Map(d -> "hundred", k -> "thousand", m -> "million", g -> "billion")
  val all = units ++ tens ++ teens ++ tenmult
  val word2num: Map[String, Int] = (units ++ tens ++ teens ++ tenmult).map(kv => (kv._2, kv._1))
  val s = " "

  // a helper function that converts num of type Int to a String
  // num belongs to exactly one of several bins
  // [0,20], [20,d], [d,k], [k,m],[m,g]
  // given the bin, we divide by suitable divisor to obtain quotient & remainder
  // the quotient & remainder are converted to Enmglish recurisively
  private def toEnglish(num: Int): Option[String] = {
    num match {
      case num if num < 0 => None
      case num if num > g => None
      case num if num < 20 => Some(all(num))
      case num if num < d => divide(num, t)
      case num if num < k => divide(num, d)
      case num if num < m => divide(num, k)
      case num if num < g => divide(num, m)
      case _ => None
    }
  }

  // a helper function that recursively converts num of type Int to a string
  private def divide(num: Int, div: Int): Option[String] = {
    val (quo, rem) = (num / div, num % div)
    if (div == t) {
      Some(tens(quo * 10) + (if (rem > 0) (s + units(rem)) else ""))
    } else {
      val quoEng = toEnglish(quo)
      val remEng = toEnglish(rem)
      Some(quoEng.get + s + tenmult(div) + (if (rem > 0) (s + remEng.get) else ""))
    }
  }

  // a helper function that converts valid strings to Int, invalid to None
  private def fromEnglish(str: String): Option[Int] = {
    val list = str.split(s).toList // strip spaces
    val valid = list.map(word2num.keySet.contains).foldLeft(true)(_ && _)
    if (valid) {
      val ans = numlist2int(list.map(word2num(_)))
      if (toEnglish(ans).isDefined && (toEnglish(ans).get == str)) Some(ans) else None
    } else None
  }

  // a helper function that recursively converts List[Int] to Int
  private def numlist2int(numbers: List[Int]): Int = {
    val (id, ik, im) = (numbers.indexOf(d), numbers.indexOf(k), numbers.indexOf(m))
    val has_100 = id > -1
    val has_higher = (ik > -1 || im > -1)
    val hundred_before_higher = has_100 && has_higher && ((id < ik) || (id < im))
    if (hundred_before_higher) {

      val ilist = List(ik, im).filter(x => x != -1).filter(x => x > id)

      val ix = if (ilist.size > 1) {
        math.min(ilist(0), ilist(1))
      } else ilist(0)

      val (hprev, hnext) = numbers.splitAt(id - 1)
      val (prev, next) = hnext.splitAt(ix - id + 2)

      fold(hprev) + fold100(prev) + numlist2int(next)
    } else {
      fold(numbers)
    }
  }

  // folds List[Int] to Int
  private def fold(numbers: List[Int]): Int = {
    val res = numbers.foldLeft(0, 0) ((adderaccum: (Int, Int), b: Int) => {
      val (adder, accum) = (adderaccum._1, adderaccum._2)
      if (b == 100 || b == 1000 || b == 1000 * 1000 || b == 1000 * 1000 * 1000) {
        (0, accum + (adder * b))
      } else {
        (adder + b, accum)
      }
    })
    res._1 + res._2
  }

  // fold Lists where 100 occurs before 1000, or 1000,000
  // eg. (7, 100, 1000) = 700,000
  // eg. (2,100, 1000000) = 200,000,000
  private def fold100(numbers: List[Int]): Int = {
    val res = numbers.foldLeft(0, 0) ((adderaccum: (Int, Int), b: Int) => {
      val (adder, accum) = (adderaccum._1, adderaccum._2)
      if (b == 100) {
        (0, adder * b)
      } else if (b == 1000 || b == 1000 * 1000) {
        (0, (adder + accum) * b)
      } else
        (adder + b, accum)
    })
    res._1 + res._2
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy