具有 4 个可选字符串的对象?具有加权值的属性,如何 return 与一组属性最匹配的对象

Objects with 4 optional String? properties with weighted value, how to return an object with the best match to a set of properties

所以一开始这似乎是一个非常简单的问题,但我发现了一些小问题,而且我目前的最终解决方案非常丑陋,所以我很想知道你们能解决什么想出

我有一个class,OptionalObject。 它有四个属性,

let prop1: String?
let prop2: String?
let prop3: String?
let prop4: String?

然后我有一个包含一千个这些 OptionalObject 对象的数组,我保证没有两个对象具有完全相同的属性。

EX: (OptionalObject: prop1, prop2, prop3, prop4

Obj1: ABC, 123, Hello, Goodbye
Obj2: DEF, 456, nil, nil
Obj3: nil, nil, Hello, Goodbye
Obj4: nil, nil, Hello, nil
Obj5: ABC, nil, nil, nil
Obj6: ABC, nil, Hello, Goodbye
Obj7: DEF, 123, nil, Goodbye
Obj8: DEF, nil, nil, nil
...

然后我有另一个 class 的单一对象,它具有所有 4 个字符串(非可选)

(ConcreteObject: arg1: String, arg2: String, arg3: String, arg4: String)

我想根据这四个属性找到与我的ConcreteObject最匹配OptionalObject

我可以想到两种方法,一种是对所有OptionalObjects使用filter,另一种是手动枚举所有OptionalObjects,嵌套if 语句来检查属性(实际上与 filter 相同)

如果我过滤的话,可能是这样的:

let matchingOptionalObject = optionalObjectsArray.filter {return [=14=].prop1 == arg1 && [=14=].prop2 == arg2 && [=14=].prop3 == arg3 && [=14=].prop4 == arg4}

很高兴我现在有一个匹配的对象...但前提是它是完全匹配的。

如果我的 ConcreteObject(DEF, 123, Hello, Goodbye) 我不会得到任何匹配项,因为没有 OptionalObjects 完全匹配。

我希望 return 的 Obj3, Obj4, Obj7, Obj8


所以我自然而然地认为OK,让我们先看看我们有哪些参数是非零的,然后相应地构造一个查询。

对于这个例子,我假设只有两个属性,这样更容易理解:

let matchingOptionalObject = optionalObjectsArray.filter { 
    if [=15=].prop1 != nil {
        if [=15=].prop2 != nil {
            return [=15=].prop1 == arg1 && [=15=].prop2 == arg2
        } else {
            return [=15=].prop1 == arg1
        }
    } else {
        if [=15=].prop2 != nil {
            return [=15=].prop2 == arg2
        } else {
            return false
        }
    }
}

但问题是因为有 4 个属性,所以我们需要涵盖至少 10 种不同的独特 nil 参数,这变得非常丑陋

这就是我目前拥有的,我有一堆丑陋的 if 语句检查 nil 参数的组合,然后相应地构建一个 filter 查询...


好的,我们需要继续前进,所以我想我们可以处理它并提出一个问题以便稍后找出更好的解决方案,但是还有一个要求使这个解决方案不起作用......

也就是每个属性的权重都不一样

当有多个匹配时选择最佳匹配的两个规则:
1) 选择匹配属性最多的匹配
2) 如果匹配的属性数量相同,则选择权重最高的匹配。

Prop1 权重最高
Prop2 最低

ConcreteObject为例:(DEF, 123, Hello, Goodbye)

匹配OptionalObjects

Obj3: nil, nil, Hello, Goodbye
Obj4: nil, nil, Hello, nil
Obj7: DEF, 123, nil, Goodbye
Obj8: DEF, nil, nil, nil

我们会选择 Obj7 作为最佳匹配,因为它有 3 个匹配属性


但是让我们说一些其他的例子,有一个新的 ConcreteObject 和一组新的 OptionalObjects,我们有这个匹配:

我们的新匹配OptionalObjects

New1: nil, 999, nil, NiHao
New2: XYZ, 999, nil, nil

我们会选择New2,因为即使New1New2都有2个匹配属性,New2有更高权重的匹配属性。


所以,问题来了。
我希望我只是不记得几年前在我的本科算法 class 中的一些关键概念,并且有一些干净的解决方案(甚至可能是 Swift 提供的东西),但我很接近我的智慧结束了——所以真的非常欢迎任何人提出任何建议或见解

数学,特别是基础代数,会给你答案。

您需要在您的 OptionalObject 实例上定义一个二元关系,即反对称的、可传递的但不是连接的。像 < 表示整数。

这是签名:

func myRelation(_ o1: OptionalObject, _ o2: OptionalObject) -> Bool {
  // compare the 4 member properties of o1 and o2, using weights if not nil
  // return true if o1 matches best, otherwise return false
}

在你的问题中,你已经指定了你需要在这个函数中实现的规则。但请注意,如果规则不会导致反对称、传递且不连接的二元关系,则您的问题没有明确定义:您需要有定义 total 顺序的规则,对于从数学上讲,解决方案是可用的。

现在,您的 OptionalObject 实例集必须使用 Swift 标准 func sort(by: (Element, Element) -> Bool):

optionalObjectsArray.sort(by: myRelation)

最后,返回集合中的第一个对象就是您要查找的对象:

let myBestMatch = OptionalObjectsArray.sort(by: myRelation).first()

这是一种合理的解决方案。详情见代码注释。

struct OptionalObject {
    let prop1: String?
    let prop2: String?
    let prop3: String?
    let prop4: String?
}

struct ConcreteObject {
    let prop1: String
    let prop2: String
    let prop3: String
    let prop4: String

    // Determine the score.
    // "matches" counts the number of matching properties.
    // "weight" gives 8 for the 1st property, 4 for the 2nd, 2 for the 3rd, 1 for the 4th. Adjust to suit your needs
    func score(for opt: OptionalObject) -> (matches: Int, weight: Int) {
        var matches = 0
        var weight = 0
        if opt.prop1 == self.prop1 { matches += 1; weight += 8 }
        if opt.prop2 == self.prop2 { matches += 1; weight += 4 }
        if opt.prop3 == self.prop3 { matches += 1; weight += 2 }
        if opt.prop4 == self.prop4 { matches += 1; weight += 1 }

        return (matches, weight)
    }

    // Compares two OptionalObject by getting the score of each
    // against "self".
    func compare(lhs: OptionalObject, rhs: OptionalObject) -> Bool {
        let scoreL = score(for: lhs)
        let scoreR = score(for: rhs)

        // If the number of matches are the same, compare the weight
        return scoreL > scoreR
    }
}

// Test ConcreteObject    
let concrete = ConcreteObject(prop1: "DEF", prop2: "123", prop3: "Hello", prop4: "Goodbye")

// List of OptionalObject
var optionals: [OptionalObject] = [
    OptionalObject(prop1: nil, prop2: nil, prop3: "Hello", prop4: nil),
    OptionalObject(prop1: "DEF", prop2: "456", prop3: nil, prop4: nil),
    OptionalObject(prop1: "ABC", prop2: "123", prop3: "Hello", prop4: "Goodbye"),
    OptionalObject(prop1: nil, prop2: nil, prop3: "Hello", prop4: "Goodbye"),
    OptionalObject(prop1: "DEF", prop2: "456", prop3: "Hello", prop4: "Goodbye"),
    //OptionalObject(prop1: nil, prop2: nil, prop3: nil, prop4: nil),
]

// Sort the list based on the ConcreteObject
let sorted = optionals.sorted { concrete.compare(lhs: [=10=], rhs: ) }
print(sorted)

结果按所需顺序排序。 sorted 中的第一个对象得分最高。