damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

Trie.swift (5008B)


      1 //
      2 //  Trie.swift
      3 //  damus
      4 //
      5 //  Created by Terry Yiu on 6/26/23.
      6 //
      7 
      8 import Foundation
      9 
     10 /// Tree data structure of all the substring permutations of a collection of strings optimized for searching for values of type V.
     11 ///
     12 /// Each node in the tree can have child nodes.
     13 /// Each node represents a single character in substrings, and each of its child nodes represent the subsequent character in those substrings.
     14 ///
     15 /// A node that has no children mean that there are no substrings with any additional characters beyond the branch of letters leading up to that node.
     16 ///
     17 /// A node that has values mean that there are strings that end in the character represented by the node and contain the substring represented by the branch of letters leading up to that node.
     18 ///
     19 /// https://en.wikipedia.org/wiki/Trie
     20 class Trie<V: Hashable> {
     21     private var children: [Character : Trie] = [:]
     22 
     23     /// Separate exact matches from strict substrings so that exact matches appear first in returned results.
     24     private var exactMatchValues = Set<V>()
     25     private var substringMatchValues = Set<V>()
     26 
     27     private var parent: Trie? = nil
     28 }
     29 
     30 extension Trie {
     31     var hasChildren: Bool {
     32         return !self.children.isEmpty
     33     }
     34 
     35     var hasValues: Bool {
     36         return !self.exactMatchValues.isEmpty || !self.substringMatchValues.isEmpty
     37     }
     38 
     39     /// Finds the branch that matches the specified key and returns the values from all of its descendant nodes.
     40     func find(key: String) -> [V] {
     41         var currentNode = self
     42 
     43         // Find branch with matching prefix.
     44         for char in key {
     45             if let child = currentNode.children[char] {
     46                 currentNode = child
     47             } else {
     48                 return []
     49             }
     50         }
     51 
     52         // Perform breadth-first search from matching branch and collect values from all descendants.
     53         var substringMatches = Set<V>(currentNode.substringMatchValues)
     54         var queue = Array(currentNode.children.values)
     55 
     56         while !queue.isEmpty {
     57             let node = queue.removeFirst()
     58             substringMatches.formUnion(node.exactMatchValues)
     59             substringMatches.formUnion(node.substringMatchValues)
     60             queue.append(contentsOf: node.children.values)
     61         }
     62 
     63         // Prioritize exact matches to be returned first, and then remove exact matches from the set of partial substring matches that are appended afterward.
     64         return Array(currentNode.exactMatchValues) + (substringMatches.subtracting(currentNode.exactMatchValues))
     65     }
     66 
     67     /// Inserts value of type V into this trie for the specified key. This function stores all substring endings of the key, not only the key itself.
     68     /// Runtime performance is O(n^2) and storage cost is O(n), where n is the number of characters in the key.
     69     func insert(key: String, value: V) {
     70         // Create root branches for each character of the key to enable substring searches instead of only just prefix searches.
     71         // Hence the nested loop.
     72         for i in 0..<key.count {
     73             var currentNode = self
     74 
     75             // Find branch with matching prefix.
     76             for char in key[key.index(key.startIndex, offsetBy: i)...] {
     77                 if let child = currentNode.children[char] {
     78                     currentNode = child
     79                 } else {
     80                     let child = Trie()
     81                     child.parent = currentNode
     82                     currentNode.children[char] = child
     83                     currentNode = child
     84                 }
     85             }
     86 
     87             if i == 0 {
     88                 currentNode.exactMatchValues.insert(value)
     89             } else {
     90                 currentNode.substringMatchValues.insert(value)
     91             }
     92         }
     93     }
     94 
     95     /// Removes value of type V from this trie for the specified key.
     96     func remove(key: String, value: V) {
     97         for i in 0..<key.count {
     98             var currentNode = self
     99 
    100             var foundLeafNode = true
    101 
    102             // Find branch with matching prefix.
    103             for j in i..<key.count {
    104                 let char = key[key.index(key.startIndex, offsetBy: j)]
    105 
    106                 if let child = currentNode.children[char] {
    107                     currentNode = child
    108                 } else {
    109                     foundLeafNode = false
    110                     break
    111                 }
    112             }
    113 
    114             if foundLeafNode {
    115                 currentNode.exactMatchValues.remove(value)
    116                 currentNode.substringMatchValues.remove(value)
    117 
    118                 // Clean up the tree if this leaf node no longer holds values or children.
    119                 for j in (i..<key.count).reversed() {
    120                     if let parent = currentNode.parent, !currentNode.hasValues && !currentNode.hasChildren {
    121                         currentNode = parent
    122                         let char = key[key.index(key.startIndex, offsetBy: j)]
    123                         currentNode.children.removeValue(forKey: char)
    124                     }
    125                 }
    126             }
    127         }
    128     }
    129 }