【问题标题】:Data structure for fast lookup with multiple criteria用于多条件快速查找的数据结构
【发布时间】:2020-01-30 21:13:24
【问题描述】:

我有多个包含 n 个属性的元素。我需要创建一个数据结构,它将返回给定条件集的所有匹配元素。查找需要快速。

为了更清楚一点,想象一下下面的类:

class Element {
    let a : Int? // criterion0
    let b : Int? // criterion1
    let c : Int? // criterion2

    let value : String
}

现在我用 3,000 个这样的元素填充 dataStructure

dataStructure.add(Element(a:1,b:3,c:4, value:"val0")) // insert 1
dataStructure.add(Element(a:nil,b:1000,c:40, value:"val1")) // insert 2
dataStructure.add(Element(a:nil,b:3,c:4, value:"val2")) // insert 3
...
dataStructure.add(Element(a:10,b:3,c:23, value:"val2999")) // insert 3000

我将不得不查找可能与我插入的内容完全匹配的元素,但也可能不同。由于通配符 nil,每次查找可能有许多匹配项。我需要这些查找速度快:

dataStructure.getAllMatching(a:4, b:30, c:4)

插入元素中的nil 参数表示任何值都是匹配的。

例如,dataStructure.getAllMatching(a:1, b:3, c:4) 将返回 ["val0", "val1"] 至少考虑到我在上面插入的内容。

请注意,我对内存几乎没有限制,我可以在启动时花一些时间进行设置。只有查找需要快速。

【问题讨论】:

  • 我认为应该总是快速过滤 300 个元素,几乎独立于算法。
  • 您是否考虑过使用“镜像”协议?如果您的对象继承自此协议,则当您在数组上使用map 函数时,可以轻松快速地循环遍历对象的属性。 developer.apple.com/documentation/swift/mirror
  • @JohnAyers 谢谢。这会让它变得容易,但我认为它不会让它很快
  • 当您收到这些回复时,我会考虑的是 XCTest 和 XCTestCase 支持测量各种功能运行所需的时间。看看:developer.apple.com/documentation/xctest/xctestcase/…。它测量一段代码运行所需的秒数。
  • 谢谢,是的,我看到了。很有用。

标签: swift data-structures


【解决方案1】:

对于固定数量的字段(在您的示例中为 3 个),有一种简单的方法可以在 O(1) 时间内完成添加、删除和查询操作。它使用 O(n) 内存,但具有相对较大的常数因子;如果您对内存几乎没有限制,这没关系。因为我不使用 Swift,所以我会以非编程语言特定的方式编写。

维护一个字典,其中每个字段组合(包括通配符)映射到一组Element 对象。这可能是最容易用一个例子来描述的:假设元素是:

X = (a=1, b=2, c=3)
Y = (a=1, b=3, c=4)
Z = (a=2, b=3, c=4)

那么你的字典是这样的:

{
    // just a
    (1, nil, nil): {X, Y}, (2, nil, nil): {Z},
    // just b
    (nil, 2, nil): {X}, (nil, 3, nil): {Y, Z},
    // just c
    (nil, nil, 3): {X}, (nil, nil, 4): {Y, Z}}`.
    // a and b
    (1, 2, nil): {X}, (1, 3, nil): {Y}, (2, 3, nil): {Z},
    // a and c
    (1, nil, 3): {X}, (1, nil, 4): {Y}, (2, nil, 4): {Z},
    // b and c
    (nil, 2, 3): {X}, (nil, 3, 4): {Y, Z},
    // a, b, c
    (1, 2, 3): {X}, (1, 3, 4): {Y}, (2, 3, 4): {Z}
}

本质上,数据结构只是存储每个可能查询的答案(没有结果的除外)。

要添加或删除一个元素,需要更新属于 2^3 - 1 = 7 个键的集合,并可能使用新集合添加一个新键,或者删除一个键。如果字典和集合使用哈希表,相关的字典/集合操作都需要O(1)时间,需要完成的操作次数是O(1),所以从数据结构中添加或删除需要 O(1) 时间。

要通过字段值的某种组合进行查询,只需在字典中查找查询,并返回相应的集合(或返回不可修改的视图)。


综上所述,对于仅 300 个元素,值得比较“简单”解决方案,您只需将元素存储在列表中并检查每个查询的整个列表。 300 并不是一个很大的数字,可能不足以让 O(1) 与 O(n) 产生影响。根据编程语言,分支预测和 CPU 缓存等其他细节可能比理论复杂性产生更大的影响。我建议对这两个选项进行基准测试。

【讨论】:

  • 谢谢。我实际上编辑了它,因为我的意思是 3,000 而不是 300。我已经考虑过这个解决方案。实际上,如果我生成所有可能的情况,我认为内存占用量远远超过 O(n),因为可能集合的数量(即字典的值)是组合的总和。虽然内存不是什么大问题,但我认为我负担不起。
  • 由于Element 具有固定数量的字段(在这种情况下为3),内存使用量是线性的,因为每个元素恰好存在7个集合中,因此所有集合的总大小加在一起总是7n , 集合总数最多为 7n。它在字段数量上呈指数增长,但这是一个常数。
  • 如果您有超过 3 个字段,您可以通过仅存储 2 或 3 个字段组合的集合来使用更少的内存,然后如果查询涉及更多字段,您可以进行字典查找对查询中的前三个字段进行线性搜索,然后对查询中的其余字段进行线性搜索。第一个查询返回的集合应该是 3,000 个元素的一小部分,因此线性搜索应该很快,除非有很多冲突。
  • 嗯,问题是我要查找的元素不是数据结构中元素的子集。这是因为 nil 标准。在您的回答中,您只允许我找到完全匹配的内容。我将编辑我的问题以使我的意思更清楚。您的答案适用于稍微不同的用例。
  • @DevShark 阅读您的编辑后,我没有看到问题。例如,在我的示例中,查询 (nil, 3, nil) 将正确返回 {Y, Z}。你能在我的例子中给我一个涉及nil的查询,你认为会返回错误的结果吗?
猜你喜欢
  • 2014-06-23
  • 2011-04-09
  • 1970-01-01
  • 1970-01-01
  • 2011-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
相关资源
最近更新 更多