【问题标题】:Data structure for fast filtering (Delphi)?快速过滤的数据结构(Delphi)?
【发布时间】:2010-02-28 17:57:48
【问题描述】:

我正在优化 Delphi 应用程序的一部分,其中经常使用不同的标准过滤对象列表。对象保存在TObjectList 结构中,通常使用每个过滤器选择整个集合中非常小的百分比(例如 1%)。对象的总数可以在 100k 范围内,并且在计算期间主集不会改变。尽管过滤器仅适用于少数几个属性,但无法以优化所有可能条件的方式对列表进行排序。

我正在寻找有关如何组织对象(数据结构)或可用于解决此问题的算法的建议。谢谢!

过滤器示例:

  ((Object.A between 5 and 15) AND
  (Object.B < 20) AND
  (Object.C(AParam) > 0)) OR
  (Object.IsRoot(...))

【问题讨论】:

  • 也许您可以指定您正在执行的过滤类型?它是简单的值匹配吗?范围检查?更复杂的东西?

标签: algorithm delphi data-structures delphi-2006


【解决方案1】:

您可以使用 排序列表 使用该字段,这最多会限制您的搜索/过滤,然后执行 binary search 以获取此条件的索引/索引。

参考你上面的例子:生成一个A-sorted 列表,搜索515 的索引,这样你会得到一个(很多)更小的列表(两者之间的所有索引),您必须检查其他字段(BCIsRoot)。

Delphi (2009+) 实现排序列表:DeHL.Collections.SortedList

【讨论】:

  • 感谢您的回复。尽可能使用排序和二分搜索。问题是在同一个列表上应用了更多过滤条件,并且它们需要不同的排序顺序才能使二分搜索起作用。从性能的角度来看,每次应用过滤器时重新排序列表似乎并不明智。
  • 那么,您已经使用二分搜索来过滤第一个字段了吗?你什么时候对列表进行排序?当您设置列表时,或每次搜索/过滤列表时?请问您在这一步之后获得了多少条目(与最初的 100k 条目相比)?
  • +1 为此,按最常见/唯一的属性对列表进行排序并应用二分搜索以获得更小的集合以应用更多过滤器。
  • @ulrichb 在当前的实现中,使用“最常见”过滤器特性将列表拆分为几个较小的列表。较小的列表按“第二个常见”过滤条件排序。较小的列表最多有 10k 个项目。当过滤您在某个(指定)数量的项目后停止时,有时您可能只需要第一个匹配项。另一个问题是您需要按列表排序的不同排序顺序进行第一次匹配。
  • 我真的没明白。也许您可以在上面的问题中添加更详细的描述(带有部分代码或伪代码)。
【解决方案2】:

我知道你不使用 tiOPF,但是从这个项目的源代码中可以学到很多东西。既然您可能会遍历过滤后的对象,为什么不创建一个过滤器迭代器呢?

这个单元是一个好的开始,但我认为下载完整的源代码和浏览文件更容易:http://tiopf.svn.sourceforge.net/viewvc/tiopf/tiOPF2/Trunk/Core/tiFilteredObjectList.pas?revision=1469&view=markup

这个解决方案可能使用了很多 RTTI:只需创建一个带有过滤器(属性名称和值)的列表并遍历对象。它将为您提供最大的灵活性,但会牺牲一些速度。如果您需要速度,我认为 Ulrichb 提供的解决方案会更好。

【讨论】:

  • 过滤迭代器很好,但在这种情况下,我感觉它会引入一些开销。我正在考虑让过滤器迭代器具有某种状态的可能性,这将使它们可重用......这可能会有所帮助......
【解决方案3】:

想法 #1

在分析器中运行您的代码。找出是否有慢点。

想法 #2

您可以通过将对象顺序存储在内存中来利用缓存效果。 (我假设你是从头到尾按顺序遍历你的列表。)

一种方法可能是使用记录数组而不是对象列表。如果这在你的情况下是可能的。请记住,Delphi 2006 中的记录可以有方法(但不能有虚拟方法)。

另一个想法可能是编写自己的类分配器。我从未尝试过,but here's an article I found. 也许尝试使用指针而不是使用 TObjectList 来遍历对象。

【讨论】:

  • 虽然其他答案是相关且有用的,但分析代码最终会带来最大的改进。
【解决方案4】:

如果对象的数量很少,那么搜索的效率可能并不重要,但如果经常缓存结果可能会有所帮助。

如果对象的数量很大,我会考虑使用内存数据库并使用 SQL 进行查询。然后,数据库可以使用索引尽可能快地查找内容,并将负担转交给经过验证的工具。我个人使用 DBISAM 或 ElevateDB,但其他人也会使用内存数据库。通过使用真正的数据库工具,如果数据变得非常大,您可以轻松地将数据移动到磁盘。

【讨论】:

    【解决方案5】:

    如果要过滤的属性数量很少并且可以排序,为什么不拥有多个列表,每个列表都由另一个属性排序?每个列表的引用需要每个对象 4 个字节,加上列表本身的少量开销。当然,一切都取决于能否满足内存需求。 2 GB 不算多,如果您要处理很多对象...

    【讨论】:

      【解决方案6】:

      这是一个非常非常非常难以以通用方式解决的问题。有一种软件可以整天做这件事,它被称为 SQL 查询优化器:每个现代 SQL 引擎中的那段代码都会查看您想要的内容(查询),然后查看可用于您的数据的索引,可用索引的选择性,它必须找出使用所有这些为您提供结果集的最佳方法。为了证明这个问题非常困难,SQL 查询优化器有时会失败并产生明显低效的计划。

      我很确定你不想实现一个成熟的查询优化器,所以这里有一些关于如何让你的查询足够快的提示:

      (1) 选择查询中经常使用的一些字段,并在上面设置索引。这些字段需要提供良好的选择性:不要索引“布尔”值,当您可能以同样快(或更快)的速度查看整个列表时,您只会浪费时间遍历复杂的二分搜索结构!

      (2) 对于每个给定的查询,选择 ONE 单个索引来预过滤数据并一一应用所有其他过滤器(不进行优化)。

      在您的示例中:在“A”和“B”字段上创建索引。 “C”似乎是一个函数,因此无法索引。 "IsRoot" 似乎返回一个布尔值,不值得索引。

      用于数据的数据结构完全取决于您的数据。如果性能至关重要,请实施多个并进行测试。如果它不重要,只需您最喜欢的列表排序算法即可!

      【讨论】:

      • 这种方法的问题在于,他显然已经有了想要过滤的实际对象,所以为了使用 sql,他首先需要创建并填充内存表,这需要一些时间时间和记忆。
      • 我并没有要求任何人实际使用 SQL 引擎,因此不需要填充表。我只是展示了一个必须解决相同问题的实际应用程序:可以查看 SQL 引擎如何解决此问题并获取提示以更好地实现合适的实现。如果 OP 会给出一些真实的例子来说明搜索的样子,我们可能会建议一个更好的解决方案。但是,如果问题被定义为“搜索太复杂,无法索引”,那么就没有简单的解决方案。
      • 是的,我第二次阅读你的答案,实际上很注意......我同意,他需要的是一个自定义查询优化器,事实上,我也需要一个。
      猜你喜欢
      • 2018-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-09
      • 2015-10-27
      • 2011-05-17
      • 1970-01-01
      相关资源
      最近更新 更多