Skip to content

Instantly share code, notes, and snippets.

@HamsterofDeath
Created September 22, 2012 15:21
Show Gist options
  • Save HamsterofDeath/3766508 to your computer and use it in GitHub Desktop.
Save HamsterofDeath/3766508 to your computer and use it in GitHub Desktop.
really (really!) last and final version of my scala solution for the phonecode problem
package phonecode
import tools.nsc.io.File
/*
// original "2 hours! yay!"-code without fine tuning: 50 lines
object PhoneCode {
def main(args: Array[String]) {
val evilChars = Set('-', '"', '/')
val cleanString = (s: String) => s.filterNot(evilChars)
val Array(dictEntries, phoneEntries, testResult) = args.map(fileName => File("resource/" + fileName).lines()) //that's a nice one :)
val dictEntriesDigified2Word = {
val mappingReversed = {
val map = new mutable.HashMap[Char, Int]()
val mapping = List("e", "jnq", "rwx", "dsy", "ft", "am", "civ", "bku", "lop", "ghz")
.zipWithIndex.map(_.swap).toMap
.mapValues(e => e.toCharArray ++ e.toUpperCase.toCharArray)
mapping.foreach(e => e._2.foreach(char => map += char -> e._1))
map
}
val wordToDigits = (word: String) => word.map(mappingReversed).mkString("")
val ret = new mutable.HashMap[String, mutable.Set[String]] with mutable.MultiMap[String, String]
val digitsToWords = dictEntries.toArray.map(e => (cleanString andThen wordToDigits)(e) -> e)
digitsToWords.foreach(t => ret.addBinding(t._1, t._2))
ret.toMap
}
val longestWordSize = dictEntriesDigified2Word.values.flatten.map(e => cleanString(e).length).max
case class Step(translated: String, remaining: String, original: String) {
def canFallBackToDigit = translated.lastOption.map(e => !e.isDigit).getOrElse(true)
def format = original + ": " + translated
def finished = remaining.isEmpty
}
val result = phoneEntries.flatMap(phoneNumber => {
val onlyDigits = cleanString(phoneNumber)
def collectPossibleTranslations(current: Step): Traversable[Step] = {
if (current.finished) {
Some(current)
} else {
def allMatches(matchAgainst: String): Traversable[Step] = {
def oldHead = if (current.translated.isEmpty) "" else current.translated + " "
val matchingWords = matchAgainst.take(longestWordSize).reverse.tails.map(_.reverse)
.flatMap(dictEntriesDigified2Word.get)
if (matchingWords.nonEmpty) {
matchingWords.map(matchingWord => {
val remaining = matchAgainst.drop(matchingWord.count(_.isLetter))
current.copy(oldHead + matchingWord, remaining)
})
} else if (current.canFallBackToDigit) {
List(current.copy(oldHead + matchAgainst.head, matchAgainst.tail))
} else {
Nil
}
}
allMatches(current.remaining).flatMap(collectPossibleTranslations)
}
}
collectPossibleTranslations(Step("", onlyDigits, phoneNumber))
}).toSet
*/
/**
* Developed with pleasure. made more beautiful for showing off scala
* User: hamsterofdeath
* Date: 20.09.12
* Time: 21:09
*/
object PhoneCode {
def main(args: Array[String]) {
//load all 3 files - and according to the spec, not into the memory. just open the file and have an iterator sucking lines from it - (1 line only :))
val Array(dictEntries, phoneEntries, testResult) = args.map(fileName => File("resource/" + fileName).lines())
//function to create strings without noise chars, 1 line
val cleanString = (s: String) => s.filterNot(Set('-', '"', '/'))
//prepare lookup table number -> all words, 8 lines
val dictEntriesDigified2Words = {
val wordToDigits = {
val mappingReversed = (for (chars2Digit <- Array("e", "jnq", "rwx", "dsy", "ft", "am", "civ", "bku", "lop", "ghz").zipWithIndex;
char <- (chars2Digit._1 ++ chars2Digit._1.toUpperCase)) yield (char -> chars2Digit._2)).toMap
(word: String) => word.map(mappingReversed).mkString
}
dictEntries.toArray.groupBy(cleanString andThen wordToDigits)
}
//i love to create classes nested inside methods just before i need them :) (6 lines)
case class Step(translated: String, remaining: String, original: String) {
def canFallBackToDigit = !translated.lastOption.map(_.isDigit).getOrElse(false)
def asFallback(notMatched: String) = if (canFallBackToDigit) List(copy(translated + " "+notMatched.head,notMatched.tail, original)) else Nil
def format = original + ": " + translated.trim
def ifFinished = if (remaining.isEmpty) Some(List(this)) else None
}
val result = phoneEntries.flatMap(phoneNumber => { // 16 lines
def collectPossibleTranslations(current: Step): Seq[Step] = {
current.ifFinished.getOrElse({
def allMatches(matchAgainst: String) = {
//collect all possible next translation steps of the remaining numbers
val matchingWords = (for (len <- 1 to matchAgainst.length;
opt <- dictEntriesDigified2Words.get(matchAgainst.take(len))) yield opt).flatten
if (matchingWords.nonEmpty) //spead the tree
for ((translated, remaining) <- matchingWords.map(e => e -> matchAgainst.drop(e.count(_.isLetter)))) yield
(current.copy(current.translated + " " + translated, remaining))
else current.asFallback(matchAgainst)
}
allMatches(current.remaining).flatMap(collectPossibleTranslations)
})
}
collectPossibleTranslations(Step("", cleanString(phoneNumber), phoneNumber))
}).toSet
//actual solution ends here, total non comment line code: 32 (+6 for package, import, main method)
//result checking to prove it's working
val resultFormatted = result.map(_.format)
val correctResult = testResult.toSet
val foundButNotExpected = resultFormatted.filterNot(correctResult).toList.sorted
val expectedButNotFound = correctResult.filterNot(resultFormatted).toList.sorted
println("should not be there: " + foundButNotExpected)
println("missing:" + expectedButNotFound)
println("both results really equal: " + (resultFormatted == correctResult))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment