My wife put up a Christmas decoration which is 4 blocks, each side with a different letter, that is supposed to spell, 'NOEL', 'LOVE', 'HOPE' and 'WISH'. Of course this quickly became a challenge to find more interesting combinations of these letters, but since I'm not great at Scrabble I'll be using a script to help.
First I'll represent each block as a list of letters, and add them to a list of blocks. Extra letters are included for placing the blocks upside down, so 'M' for 'W', 'A' for 'V' and 'd' for 'P'.
val LETTERS = List( List("N", "H", "L", "W", "M"), List("O", "I"), List("E", "P", "V", "S", "d", "A"), List("L", "E", "H"))
To find the combinations of letters is easy enough, find every permutation for ordering of the blocks, and for each ordering take the cartesian product of the block lists.
def permutations(letters: List[List[String]]): Seq[String] = { letters.permutations.flatMap(combine[String](_ + _) _) .toSeq.sortWith(_.toLowerCase < _.toLowerCase) } def combine[A](f: (A, A) => A)(xs:Iterable[Iterable[A]]) = xs reduceLeft { (x, y) => for (a <- x.view; b <- y) yield f(a, b) }
It's worth allowing for some incorrect spelling where it would still be readble, like leaving an 'e' off the end of a word or using 'oo' instead of 'u', for this we'll introduce a set of transformations defined as regex.
val TRANSFORMATIONS = List( "(?i)l$" -> "le", "(?i)l$" -> "el", "(?i)^l" -> "el", "(?i)oo" -> "u", "(?i)i" -> "y") def withAlternates(s: String): List[String] = { val word = s.toLowerCase() val words = for ((x, y) <- TRANSFORMATIONS if word.matches(x)) yield (word.replaceAll(x, y)) word +: words }
Now we have all combinations, but it's too many to look at by hand. We'll use a dictionary word list (the aspell one) to filter.
def readDictionary(location: String): Set[String] = { Source.fromFile(location).getLines.map(a => a.toLowerCase()).toSet } def inDictionary(dictionary: Set[String]): String => Boolean = { dictionary.contains(_) }
And the main method to pull it all together
def main(args: Array[String]) = args match { case Array(path) => { val dictionary = readDictionary(path) print(permutations(LETTERS).filter(withAlternates(_).exists(inDictionary(dictionary)))) } case _ => { System.out.println("requires wordlist argument") System.exit(1) } } def print(it: Iterable[String]) { for (x <- it) System.out.println(x); }
There is still a lot of output but now that it's manageable we can pick out some of the more interesting words:
- EVIL
- IdLE
- ELMO
- NIPL
- MOES