Created
September 22, 2012 15:21
-
-
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
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 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