tutorials tutorial example swift parallel-processing swift2 grand-central-dispatch

tutorial - nsoperationqueue swift 3 example



Una solución más rápida para caminar de una palabra a otra (1)

Pequeño problema muy interesante. Usar la indexación nativa en los diccionarios te permitirá hacer que esto vaya mucho más rápido. Además, puede reducir considerablemente el número de iteraciones buscando una cadena de palabras de ambos extremos al mismo tiempo.

Hice un ejemplo que tiene un tiempo de configuración más largo pero, una vez que se hace la indexación de palabras, los resultados de la cadena de palabras son prácticamente instantáneos.

// --- wordPath.swift --- // create a list of link patterns for a word // ------------------------------------------ // A link pattern is an ordered list of letters in a word where one letter is missing // // This corresponds to a combination of letters that can from the word // by adding a single letter and, conversely to combinations of letters that // are possible by removing a letter from the word // // Letters are sorted alphabetically in order to ignore permutations // // for example: // // "corner" has 6 link patterns (one of which is a duplicate) // // the sorted letters are : "cenorr" // // the links are the 6 combinations of 5 out of 6 letters of "cenorr" // with letters kept in alphabetical order. // // because letters are sorted, the combinations can be obtained by // removing one letter at each position. // // removing position 1 : enorr // removing position 2 : cnorr // removing position 3 : ceorr // removing position 4 : cenrr // removing position 5 : cenor // removing position 6 : cenor (this is the duplicate) // public func linksOfWord(_ word:String)->[String] { var result:[String] = [] // sort letters let sortedLetters = word.characters.sorted() // return one string for each combination of N-1 letters // exclude duplicates (which will always be consecutive) var wordLink = "" for skipChar in sortedLetters.indices { let nextLink = String(sortedLetters[0..<skipChar]) + String(sortedLetters[skipChar+1..<sortedLetters.count]) if nextLink == wordLink { continue } wordLink = nextLink result.append(wordLink) } return result } // Create an index of wordlinks to the words that can be fromed from them // ---------------------------------------------------------------------- // - The wordLinks dictionary contains a list of all the words that can be formed // by adding one letter to a given link pattern // - This is essentially the reverse of linksOfWord for all words in the dictionary // - note: Swift will use lazy initialization for this global variable, only executing the closure once. // public var wordsOfLink:[String:[String]] = { var result:[String:[String]] = [:] // get all words let wordList = try! String(contentsOfFile: "/usr/share/dict/words") .lowercased() .components(separatedBy:"/n") // add each word to the index of its wordLinks for word in wordList { for wordLink in linksOfWord(word) { result[wordLink] = (result[wordLink] ?? []) + [word] } } return result }() // Iteration counted, for comparison with OP public var iterations = 0 // word path seeking algorithm // --------------------------- // - Go through word paths using linksOfWord and wordsOfLink as a virtual tree structure // linksOfWord("word") -> 1:N array of links -> wordsOfLink("Link") -> 1:N array of words // - Performs a breadth-first tree search by exausting shorter path lengths before moving on to longer ones // - Simultaneously search forward and backward to minimize the exponential nature of path expansions // public func findWordPath(from fromWord:String, to toWord:String, shortestPath:Bool=false) -> [String]? { iterations = 0 // both words must be same length guard fromWord.characters.count == toWord.characters.count else { return nil } // processing in lowercase only let startWord = fromWord.lowercased() let endWord = toWord.lowercased() // keep track of links already processed (avoid infinite loops) var seenLinks = Set<String>() var seenWords = Set<String>() // path expansion from starting word forward to ending word var forwardLinks:[String:[String]] = [:] // links that have a forward path connecting to the starting word var forwardPaths = [[startWord]] // partial forward paths to examine var forwardIndex = 0 // currently examined forwardPath (index) // path expansion from ending word backward to starting word var backwardLinks:[String:[String]] = [:] // links that have a backward path connecting to the ending word var backwardPaths = [[endWord]] // partial backward paths to examine var backwardIndex = 0 // currently examined backwardPath (index) // establish initial links to start and end words // - allows logic to only check path to path connections // (and not path to word in addition to it) linksOfWord(startWord).forEach{forwardLinks[$0] = [startWord]} linksOfWord(endWord).forEach{backwardLinks[$0] = [endWord]} // Explore shorter paths in each direction before moving on to longer ones // This improves performance but does not guarantee the shortest word path // will be selected when a connection is found // e.g. forward(4)->backward(3) could be found before backward(4)->forward(2) // resulting in a 7 word path when a 6 word path exists. // Finding the shortest possible word path requires that we explore forward only // (which is what the shortestPath parameter controls) var currentLength = 1 // Examine partial word paths in multiple passes with an increasing path length (currentLength) // - For each path length, check if forward and backward paths can connect to one another. // - For each length, forwardIndex and backwardIndex advance through the paths // of the current length thus exhausting both forward and backward paths of that length // before moving on to the next length. // - As paths of the current length are examined, the partial path arrays receive new (longer) word paths // to examine. // - The added paths have 1 additional word which creates a new group of paths for the next pass // at currentLength + 1 // - When none of the partial word paths can be extended by adding a word that was not seen before // the forward or backward path array will stop growing and the index will reach the end of the array // indicating that no word path exists between start and end words while forwardIndex < forwardPaths.count && ( backwardIndex < backwardPaths.count || shortestPath ) { // Examine forward path links (of current length) for connection // to the end word or to a backward path that connects to it while forwardIndex < forwardPaths.count && forwardPaths[forwardIndex].count == currentLength { let forwardPath = forwardPaths[forwardIndex] forwardIndex += 1 iterations += 1 // expand links for last word of "forward" path for wordLink in linksOfWord(forwardPath.last!) { // link connects to a backward path, return the combined forward and backward paths if let backwardPath = backwardLinks[wordLink] { return forwardPath + backwardPath } // don''t explore links that have already been examined if !seenLinks.insert(wordLink).inserted { continue } // record forward path to allow linking from backward paths // i.e. this link is known to lead to the starting word (through forwardPath) if !shortestPath { forwardLinks[wordLink] = forwardPath } // for all words that can be created by adding one letter to the word link // add new forward paths to examine on the next length pass for word in wordsOfLink[wordLink] ?? [] { if seenWords.insert(word).inserted { forwardPaths.append(forwardPath + [word]) } } } } // Examine backward path links (of current length) for connection // to the start word or to a forward path that connects to it // allowing one length backward path to support unknown end words while !shortestPath && backwardIndex < backwardPaths.count && backwardPaths[backwardIndex].count == currentLength { let backwardPath = backwardPaths[backwardIndex] backwardIndex += 1 iterations += 1 // expand links for first word of "backward" path for wordLink in linksOfWord(backwardPath.first!) { // link connects to starting path, combine and return result if let forwardPath = forwardLinks[wordLink] { return forwardPath + backwardPath } // don''t explore links that have already been examined if !seenLinks.insert(wordLink).inserted { continue } // record backward path to allow linking from forward paths // i.e. this link is known to lead to the ending word (through backwardPath) backwardLinks[wordLink] = backwardPath // for all words that can be created by adding one letter to the word link // add new backward paths to examine on the next length pass for word in wordsOfLink[wordLink] ?? [] { if seenWords.insert(word).inserted { backwardPaths.append( [word] + backwardPath ) } } } } // all forward and backward paths of currentLength have been examined // move on to next length currentLength += 1 } // when either path list is exausted, there are no possible paths. return nil }

...

// --- Playground --- // compute word path and print result func printWordPath(_ firstWord:String, _ lastWord:String) { print(" printWordPath(/"/(firstWord)/",/"/(lastWord)/")") let startTime = ProcessInfo().systemUptime if let foundPath = findWordPath(from:firstWord, to:lastWord, shortestPath:false) { let time = String(format:"%.5f",ProcessInfo().systemUptime-startTime) print(" ///(foundPath.count) words : /(foundPath)/n // /(iterations) iterations, /(time) sec ") } else { print(" // No Path Found between /(firstWord) and /(lastWord)") } print("") } printWordPath("bat","man") // 3 words : ["bat", "amt", "man"] // 16 iterations, 6.34718 sec <-- longer time here because of initializations printWordPath("many","shop") // 5 words : ["many", "hymn", "homy", "hypo", "shop"] // 154 iterations, 0.00752 sec printWordPath("many","star") // 4 words : ["many", "maty", "mast", "star"] // 101 iterations, 0.00622 sec printWordPath("define","system") // 6 words : ["define", "fenite", "entify", "feisty", "stymie", "system"] // 574 iterations, 0.02374 sec printWordPath("defend","oppose") // 6 words : ["defend", "depend", "depone", "podeon", "pooped", "oppose"] // 336 iterations, 0.01273 sec printWordPath("alphabet","integers") // 7 words : ["alphabet", "lapithae", "apterial", "epistlar", "splinter", "sterling", "integers"] // 1057 iterations, 0.04454 sec printWordPath("Elephant","Microbes") // 8 words : ["elephant", "antelope", "lapstone", "parsonet", "somepart", "imposter", "comprise", "microbes"] // 2536 iterations, 0.10133 sec printWordPath("Microbes","Elephant") // 8 words : ["microbes", "comprise", "persicot", "petrolic", "copalite", "pectinal", "planchet", "elephant"] // 2232 iterations, 0.09649 sec printWordPath("Work","Home") // 4 words : ["work", "worm", "mohr", "home"] // 52 iterations, 0.00278 sec printWordPath("Head","Toes") // 4 words : ["head", "ahet", "ates", "toes"] // 146 iterations, 0.00684 sec printWordPath("North","South") // 3 words : ["north", "horst", "south"] // 22 iterations, 0.00189 sec printWordPath("Employee","Pesident") // 7 words : ["employee", "employed", "redeploy", "leporide", "pedelion", "disponee", "pesident"] // 390 iterations, 0.01810 sec printWordPath("Yellow","Orange") // 5 words : ["yellow", "lowery", "royale", "royena", "orange"] // 225 iterations, 0.01025 sec printWordPath("Whale","shark") // 4 words : ["whale", "hawse", "asher", "shark"] // 94 iterations, 0.00473 sec printWordPath("Police","Fellon") // 4 words : ["police", "pinole", "lionel", "fellon"] // 56 iterations, 0.00336 sec printWordPath("religion","insanity") // 6 words : ["religion", "triolein", "reinstil", "senility", "salinity", "insanity"] // 483 iterations, 0.02411 sec printWordPath("ebony","ivory") // 4 words : ["ebony", "eryon", "irony", "ivory"] // 53 iterations, 0.00260 sec printWordPath("electronic","mechanical") // 7 words : ["electronic", "citronelle", "collineate", "tellinacea", "chatelaine", "atechnical", "mechanical"] // 316 iterations, 0.01618 sec printWordPath("detrimental","appropriate") // 7 words : ["detrimental", "pentremital", "interpolate", "interportal", "prerational", "preparation", "appropriate"] // 262 iterations, 0.01319 sec

Tenga en cuenta que esto encuentra soluciones para sus dos últimos ejemplos y también noté que muchos -> mand -> main -> dijo -> ... estaba reemplazando dos letras que iban de "main" a "said", así que terminé con un camino diferente

Por cierto, puse las funciones en un archivo separado en el patio de juegos (en Fuentes) porque el recuento de ejecuciones de línea estaba ralentizando la ejecución hasta un rastreo.

[EDITAR] Las iteraciones modificadas cuentan para que sea más coherente con la forma en que contaba el código del OP. Se agregaron más ejemplos (se divirtieron demasiado con esto).

[EDIT] adaptado para Swift 3, agregó más explicación de la lógica, simplificó / optimizó aún más el código.

Tengo una tarea de tarea donde el objetivo es hacer que una palabra se convierta en otra, cambiando solo un personaje a la vez. Elijo hacerlo en Swift y usar el diccionario BSD estándar ( /usr/share/dict/words ) para mi fuente de palabras.

El siguiente código funciona como se esperaba. Sin embargo, para algunas combinaciones, funciona bastante despacio. Por ejemplo, define -> system

¿Puedo usar Grand Central Dispatch y procesamiento paralelo para hacerlo más rápido? ¡Muchas gracias!

import Foundation typealias CString = [CChar] // Get list of words from the standard BSD dictionary let wordList = try! String(contentsOfFile: "/usr/share/dict/words") .componentsSeparatedByString("/n") .map { $0.lowercaseString.cStringUsingEncoding(NSUTF8StringEncoding)! } func distance(fromCString: CString, toCString: CString) -> Int { guard fromCString.count == toCString.count else { fatalError("The two strings must have the same number of characters") } var distance = 0 for i in 0..<fromCString.count { if fromCString[i] != toCString[i] { distance++ } } return distance } func find(start: String, _ end: String) -> [String]? { guard start.characters.count == end.characters.count else { fatalError("''/(start)'' and ''/(end)'' must have the same number of characters") } // String is slow. Switch to bytes array for speed let startCString = start.cStringUsingEncoding(NSUTF8StringEncoding)! let endCString = end.cStringUsingEncoding(NSUTF8StringEncoding)! // Get list of words with same length var candidates = wordList.filter { $0.count == startCString.count } // If either start or end is not in the dictionary, fail guard (candidates.contains { $0 == startCString }) else { fatalError("''/(start)'' is not in the dictionary") } guard (candidates.contains { $0 == endCString }) else { fatalError("''/(end)'' is not in the dictionary") } // Do the search var i = 0 var iterations = 0 var queue = [[CString]]() queue.append([startCString]) while queue.count > 0, let lastWord = queue[0].last where lastWord != endCString { iterations++ i = 0 while i < candidates.count { if candidates[i] == lastWord { candidates.removeAtIndex(i) } else if distance(lastWord, toCString: candidates[i]) == 1 { queue.append(queue[0] + [candidates[i]]) candidates.removeAtIndex(i) } else { i++ } } queue.removeAtIndex(0) } if queue.isEmpty { print("Cannot go from ''/(start)'' to ''/(end)''") return nil } else { return queue[0].map { String(CString: $0, encoding: NSUTF8StringEncoding)! } } }

Ejemplos:

find("bat", "man") // bat -> ban -> man. 190 iterations, fast. find("many", "shop")) // many -> mand -> main -> said -> saip -> ship -> shop. 4322 iterations, medium find("define", "system") // impossible find("defend", "oppose") // impossible