The goal of the puzzle was to get from one word to another in four steps only being able to change a single letter at a time. I cannot remember the exact words, but I couldnt do it because I was unfamiliar with one of the intermediate words. In a nutshell, the game is as follows.
In 3 steps, get from MOOD to GOAT, only changing one letter in the word at each step.
A possible solution would be
MOOD
MOOT
BOOT
BOAT
GOAT
though they may be other solutions. The correct solution is the fastest one possible
This is an interesting problem, and I wondered whether it would be possible to create an application that would generate such puzzles. There are a number of prerequisites
1: A list of all valid english words (this is tougher than it sounds, there are multiple lists and they dont agree)
2: A way to specify word length and step length (I want a puzzle where the words are 4 characters long and the solution is 4 steps).
I begun playing around with it and came up with the basic generation code. For example, given a starting word of POT, create a Tree with all possible combinations. Because the tree could be arbitrarily deep, specify a termination length (a given path cannot be longer than X).
For example, assume the only valid 3 letter english word were [pot,pit,pet,lot,bot,pin,tin,tan,pep,bet] and given a start word of pot and a max depth of 5, here is the Tree structure built.
The total possible paths (irrespective of path length) are
[pot, pit, pin, tin, tan]
[pot, pet, pep]
[pot, pet, bet]
[pot, lot]
[pot, bot, bet]
In fact, the Groovy unit test for this code would look as below
@Test void testGetAllPaths(){ def allowedWords = ['pot','bot','lot','pet','pit','bet','pep','pin','tin','tan'] Node node = new Node(null,'pot',allowedWords,0,5) def result = node.getAllPaths() assert result == [['pot', 'pit', 'pin', 'tin', 'tan'], ['pot', 'pet', 'pep'], ['pot', 'pet', 'bet'], ['pot', 'lot'], ['pot', 'bot', 'bet']] }
The code for the Node class is below
package com.wordPath.utilities; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * This class represents a Tree structure of word combinations with a single letter in the word changed as you progress * down the tree structure * frog -> frag -> brag -> brat * @author wkimeria * */ public class Node { /** * Create tree starting from the given start word (value), terminate branches at specified level * @param parent Parent Node * @param value Node value * @param allowedWords List of allowed words (must be valid english words of a given length) * @param level level of this node in given tree structure. Root is 0 * @param requestedDepth Level at which to terminate branches */ String value; Listchildren; Node parent; int level; boolean visited; Node(Node parent, String value, List allowedWords, int level, int requestedDepth) { List allowedWordsForPath = new ArrayList (); allowedWordsForPath.addAll(allowedWords); allowedWordsForPath.remove(value); children = new ArrayList (); this.value = value; this.level = level; this.parent = parent; if (level >= requestedDepth-1) { return; } List variations = new ArrayList (); int wordLength = value.length(); for (int i = 0; i <= wordLength; i++) { variations.addAll(getVariations(value, i)); } variations = getAllowedWords(variations, allowedWordsForPath); if (variations.size() != 0) { level++; for (String v : variations) { Node n = new Node(this, v, allowedWordsForPath, level, requestedDepth); children.add(n); } } } /** * Given a list of words generated by combinations, remove invalid words and only * return valid english words. * @param words * @param allowedWords * @return */ List getAllowedWords(List words, List allowedWords) { List allowedWordsFound = new ArrayList (); for (String w : words) { if (allowedWords.contains(w)) { allowedWordsFound.add(w); allowedWords.remove(w); } } return allowedWordsFound; } /** * For a given word, get all variations with the letter at position s cycled * @param word * @param pos * @return */ List getVariations(String word, int pos) { List variations = new ArrayList (); if (word != null && pos < word.length()) { String letter = word.substring(pos, pos + 1); char next = (char) (letter.toCharArray()[0] + 1); char stop = (char) (next - 1); while (next != stop) { String current = word.substring(0, pos) + next + word.substring(pos + 1, word.length()); variations.add(current); next++; if (next == (char) 'z' + 1) { next = (char) 'a'; } } } return variations; } /** * Get a list of lists of all possible paths (essentially capturing each depth first search from root to leaf * @return */ public List > getAllPaths() { List
> paths = new ArrayList
>(); Deque
queue = new LinkedList (); queue.clear(); queue.add(this); List currentPath = new ArrayList (); Deque currentPathQueue = new LinkedList (); while (!queue.isEmpty()) { Node n = queue.removeLast(); if (n.children != null && n.children.size() > 0) { queue.addAll(n.children); } else { currentPathQueue = new LinkedList (); currentPath = new ArrayList (); Node walker = n; while (walker != null) { currentPathQueue.add(walker.value); walker = walker.parent; } while (!currentPathQueue.isEmpty()) { currentPath.add(currentPathQueue.removeLast()); } paths.add(currentPath); } } return paths; } /** * Return string representation of Tree with value and node depth (uses Breadth First Search) */ public String toString() { String nodeValues = ""; Queue queue = new LinkedList (); queue.clear(); queue.add(this); while (!queue.isEmpty()) { Node n = queue.remove(); String parent = ""; if (n.parent != null) { parent = n.parent.value; } nodeValues += n.value + " " + n.level + " parent = " + parent + "\n"; if (n.children != null) queue.addAll(n.children); } return nodeValues; } }
This class is not perfect. You could call it and start with a particularly unlucky word and building the Node could take an inordinate amount of time, but it is a first pass. The idea is to have a running process that generates puzzles and stores them, that way one could request a puzzle that starts with x word and ends with y word of length n, and get back a list of all puzzles that fit the given criteria
int wordLength = 4 int puzzleLength = 3 ListallowedWords = new ArrayList<>() getWordsOfLength(wordLength, allowedWords) System.out.println("Words = " + allowedWords.size()) Double randomWordIndex = Math.random() * allowedWords.size() for(String s:allowedWords){ String randomWord = s; Node node = new Node(null, randomWord, allowedWords, 0, puzzleLength); println("--------------- Node built for ${s} ---------------------"); List > paths = node.getAllPaths(); for (List
path : paths) { println(path); //Save paths somewhere } System.out.println("Possible Paths = ${paths.size()}"); }
Ongoing code at
WordPath on GitHub
No comments:
Post a Comment