I created a data structure based on a dictionary of words. The purpose is to link each word to all other words made up of the same letters plus one.
Example: table -> ablate, cablet, tabled, gablet, albeit, albite, etc.
For this I built a data model made of three entities: Word, Draw, Link.
A Draw is a set of letters corresponding to a Word and sorted in alphabetic order, like : HOUSE -> EHOSU. A Link is a letter that you add to a Draw to get another Draw.
So my data model looks like this:
And here is how I implemented it in Xcode:
Entity Word
(let's forget the attribute optComp that plays no role here)
Entity Draw
Entity Link
I am populating the data in two steps:
first I read a list of words from a .txt source and I populate the Word entity and at the same time the Draw entity with the corresponding relationship (function loadDic())
This first step apparently works fine. I can easily find all anagrams of any word with something like word.sort.word.spelling
I read through the Draw entity. For each draw I seek all existing +1 draws considering each letter of the alphabet. If there are, I create a Link and add the relationships (function createLinks())
Here is where something goes wrong. If the Link's and the relationship Draw.plus seem to be correctly created, the other relationship Link.gives is only partially populated, say 50%.
Moreover, I tried to apply an additional routine (updateLinks()) , focusing only on Link's with an empty Link.gives relationship and updating them. But again, only 50% of the nil relationships appear to be populated.
I could not find out why those relationships are not properly populated. If someone can help me out I would be grateful.
Here is the code:
LoadDic() function (OK) :
func loadDic() {
print("Loading dictionary...")
dataAlreadyLoaded.toggle()
guard let url = Bundle.main.url(forResource: INPUT_FILE, withExtension: "txt") else {
fatalError("\(INPUT_FILE).txt not found")
}
if let dico = try? String(contentsOf: url, encoding: String.Encoding.utf8 ) {
let lines = dico.split(separator: "\r\n")
for line in lines {
let lineArray = line.split(separator: " ")
print("\(lineArray[0])") // word
let wordSorted = String(lineArray[0].sorted())
let draw = getDraw(drawLetters: wordSorted) ?? addDraw(drawLetters: wordSorted) // look if draw already exists, otherwise create new one.
let wordItem = Word(context: viewContext) // create word entry with to-one-relationship to draw
wordItem.spelling = String(lineArray[0])
wordItem.optComp = (Int(String(lineArray[1])) == 1)
wordItem.sort = draw
do {
try viewContext.save()
} catch {
print("Errort saving ods9: \(error)")
}
}
}
print("Ods Chargé")
}
func addDraw(drawLetters: String) -> Draw {
let newDraw = Draw(context: viewContext)
newDraw.draw = drawLetters
return(newDraw)
}
func getDraw(drawLetters: String) -> Draw? {
let request: NSFetchRequest<Draw> = Draw.fetchRequest()
request.entity = Draw.entity()
request.predicate = NSPredicate(format: "draw == %@", drawLetters)
do {
let drw = try viewContext.fetch(request)
return drw.isEmpty ? nil : drw[0]
} catch {
print("Erreur recherche Tirage")
return nil
}
}
createLinks() function (NOK):
func createLinks() {
var erreur = " fetch request <Draw>"
let request: NSFetchRequest<Draw> = Draw.fetchRequest()
request.entity = Draw.entity()
request.predicate = NSPredicate(value: true)
print("Building relationships...")
do {
let draws = try viewContext.fetch(request)
count = draws.count
for draw in draws {
print("\(count) - \(draw.draw!)")
linkTable.removeAll()
for letter in ALPHABET {
print(letter)
let drawLettersPlus = String((draw.draw! + String(letter)).sorted()) // draw with one more letter
if let drawPlus = draws.first(where: { $0.draw == drawLettersPlus }) { // look for Draw entity that matches augmented draw
let linkItem = Link(context: viewContext) // if found, create new link based on letter with relationship to augmented draw
linkItem.letter = String(letter)
linkItem.gives = drawPlus
erreur = " saving \(draw.draw!) + \(letter)"
try viewContext.save()
linkTable.append(linkItem) // saves link to populate the one-to-many relationship of the initial draw, once the alphabet is through
}
}
let drawUpdate = draw as NSManagedObject // populate the one-to-many relationship of the initial draw
let linkSet = Set(linkTable) as NSSet
drawUpdate.setValue(linkSet, forKey: "plus")
erreur = " saving \(draw.draw!) links plus"
try viewContext.save()
count -= 1 // next draw
}
} catch {
print("Error " + erreur)
}
print("Graph completed")
}
updateLinks function (NOK):
func updateLinks() {
var erreur = "fetch request <Link>"
let request: NSFetchRequest<Link> = Link.fetchRequest()
request.entity = Link.entity()
print("Running patch...")
do {
request.predicate = NSPredicate(format: "gives == nil")
let links = try viewContext.fetch(request)
for link in links {
let baseDraw = link.back!.draw!
print("\(baseDraw) \(link.letter!)")
let augmDrawLetters = String((baseDraw + link.letter!).sorted())
if let augmDraw = getDraw(drawLetters: augmDrawLetters) {
viewContext.perform {
let updateLink = link as NSManagedObject
updateLink.setValue(augmDraw, forKey: "gives")
erreur = " saving \(augmDraw.draw!) \(link.letter!)"
do {
try viewContext.save()
} catch {
print("Erreur mise à jour lien")
}
}
}
}
} catch {
print("Error " + erreur)
}
}
RESULT
And this is the output showing the content of the Draw entity with relationships after createLinks() is applied:
And here after updateLinks() is applied :