Search code examples
swiftalgorithmpattern-matchingstring-matching

A Set of Objects with optional String properties, Improving a function that takes String arguments and finding the best match within the Set


We're trying to optimize some "weighted" matching algorithm we've been using, and decided to consult the internet for some more ideas

We have a Struct MyStruct, with 5 optional properties (in swift, this just means that the property can be nil):

prop1: String?
prop2: String?
prop3: String?
prop4: String?
prop5: String?

then we have an set of MyStruct (guaranteed that there aren't 2 instances that have the same exact properties),

structArray: Set<MyStruct>

we have a function that takes this array, as well as the 1-5 properties in a dictionary, to return 1 single instance that best matches. If any of the properties don't match, the instance is taken out of contention immediately

func findBestMatch(forSet set:Set<MyStruct>, andArgs argDict:[String:String]) -> MyStruct? {
  //Will use this to store any matches, as well as an associated match score
  var bestMatch: MyStruct?
  var topScore = 0
  for element in set {
    var score = 0
    if let p1 = argDict["p1"] {
      if p1 == element.prop1 {
        score += 16 //p1 match has highest weight
      } else {
        continue
      }
    }

    if let p2 = argDict["p2"] {
      if p2 == element.prop2 {
        score += 8 //p2 match has second-highest weight
      } else {
        continue
      }
    }

    //etc for the other 3 properties

    if score > topScore {
      topScore = score
      bestMatch = element 
    }
  }
  return bestMatch
}

EXAMPLE:

exInstance1
  prop1 = "no good"
  prop2 = nil
  prop3 = "goodbye

exInstance2
  prop1 = "hello"
  prop2 = "noproblem"
  prop3 = "goodbye"

exInstance3
  prop1 = nil
  prop2 = nil
  prop3 = "goodbye"

exampleSet: Set<MyStruct> = [exInstance1, exInstance2, exInstance3]

matchingProperties: [String:String] = {
  "p1": "hello",
  "p3": "goodbye"
}


findBestMatch(forSet: exampleSet, andArgs: matchingProperties)

exInstance1 only has 1 match, on prop3, but because prop1 doesn't match at all, exInstance is not given a score

exInstance2 matches on both properties, and is given a score of 20

exInstance3 matches on one property, and is given a score of 4

exInstance2 is chosen and returned


Question: Is there a better way to do this? If not, are there any ways we could improve this algorithm?


Solution

  • I would only see a slight improvement if you factor out the dictionary accesses outside the for loop, e.g.

    func findBestMatch(forSet set:Set<MyStruct>, andArgs argDict:[String:String]) -> MyStruct? {
        //Will use this to store any matches, as well as an associated match score
        var bestMatch: MyStruct?
        var topScore = 0
        let p1 = argDict["p1"]
        let p2 = argDict["p2"]  // and so on
        for element in set {
            var score = 0
            if let p1 = p1 {
                if p1 == element.prop1 {
                    score += 16 //p1 match has highest weight
                } else {
                    continue
                }
            }
    
            //etc for the other properties
    
            if score > topScore {
                topScore = score
                bestMatch = element
            }
        }
        return bestMatch
    }