【问题标题】:What is the most efficient Ruby data structure to track progress?跟踪进度的最有效的 Ruby 数据结构是什么?
【发布时间】:2015-01-16 05:33:16
【问题描述】:

我正在做一个小项目,该项目逐渐增加链接列表,然后通过队列处理它们。存在链接可能会两次进入队列的可能性,我想跟踪我的进度,以便我可以跳过已经处理的任何内容。我估计最多有大约 10k 个独立链接。

对于较大的项目,我会使用数据库,但这对于我正在处理的数据量来说似乎有点过头了,并且如果我想保存运行过程中的进度,我更喜欢某种形式的内存解决方案,这种解决方案可能会被序列化。

哪种数据结构最适合这种需求?

更新:我已经在使用哈希来跟踪我已完成处理的链接。这是最有效的方法吗?

def process_link(link)
  return if @processed_links[link]
  # ... processing logic
  @processed_links[link] = Time.now # or other state
end

【问题讨论】:

  • 使用散列或集合。您只能在哈希中拥有一个键的单个实例。一个 Set 是建立在 Hash 键之上的,所以你会得到类似的行为。
  • 要考虑的另一件事是,您是否必须跟踪中断/崩溃/重启的进度?如果是这样,您根本不能使用内存跟踪,可能应该使用数据库。

标签: ruby performance data-structures


【解决方案1】:

如果您不关心内存,那么只需使用哈希来检查包含;插入和查找时间是 O(1) 平均情况。序列化很简单(Ruby 的 Marshal 类应该会为您处理好,或者您可以使用 JSON 之类的格式)。 Ruby 的Set 是一个类似数组的对象,它以哈希为后盾,因此如果您愿意,可以直接使用它。

但是,如果内存是一个问题,那么这对于Bloom filter 来说是个大问题!您可以在恒定时间内实现集合包含测试,并且过滤器使用的内存比哈希要少得多。权衡是布隆过滤器是概率性的——你可以获得错误的包含阳性。您可以使用正确的布隆过滤器参数消除大多数误报的可能性,但如果重复是例外而不是规则,您可以实现类似:

  1. 检查布隆过滤器中是否包含集合 [O(1)]
  2. 如果布隆过滤器报告找到该条目,则对输入数据执行 O(n) 检查,以查看之前是否已在输入数据数组中找到该条目。

这将使您对常见情况进行非常快速且节省内存的查找,并且您可以选择接受假阴性的可能性(以保持整个事情小而快),或者您可以执行集合验证报告重复时包含(仅在绝对必要时才进行昂贵的工作)。

https://github.com/igrigorik/bloomfilter-rb 是我过去使用过的布隆过滤器实现;它工作得很好。如果您需要可以跨多个应用程序实例执行集合成员跟踪和测试的东西,还有 redis 支持的 Bloom 过滤器。

【讨论】:

  • 谢谢,这正是我正在寻找的信息。我没有考虑过布隆过滤器,因为通常情况下保证不存在,检查包含的潜在误报。当你被击中时添加 O(n) 检查是一个很好的轻量级添加。
【解决方案2】:

如何设置并将链接转换为值对象(而不是引用对象),例如 Structs。通过创建一个值对象,Set 将能够检测到它的唯一性。或者,您可以使用散列并按其 PK 存储链接。

【讨论】:

  • A Hash 似乎是最明显的,但我不确定密钥散列在内存和处理方面是否能支持超过 10k 的插入。
  • 密钥散列会很好。一个更好的问题是 Ruby 将如何处理 10K 键和关联值?也许一个像样的后端数据库会更好?
  • 运行我的脚本的早期版本最终会产生比预期更多的链接。当我在Queue 对象中达到 300k,并使用Hash 对象跟踪看到的所有链接时,我决定是时候使用数据库来跟踪运行和崩溃了。此外,令我惊喜的是 Ruby 进程只使用了大约 15MB 的 RAM,而且我的 CPU 几乎没有出现任何问题(大部分时间都花在锁定网络上)。
【解决方案3】:

数据结构可以是哈希:

current_status = { links: [link3, link4, link5], processed: [link1, link2, link3] }

跟踪您的进度(百分比):

links_count = current_status[:links].length + current_status[:processed].length
progress = (current_status[:processed].length * 100) / links_count # Will give you percent of progress

处理您的链接:

  • push 您需要处理的任何新链接到current_status[:links]
  • 使用shiftcurrent_status[:links] 获取下一个要处理的链接。
  • 处理完一个链接后,push它到current_status[:processed]

编辑

在我看来(并理解您的问题),处理您的链接的逻辑是:

# Add any new link that needs to be processed to the queue unless it have been processed
def add_link_to_queue(link)
  current_status[:to_process].push(link) unless current_status[:processed].include?(link)
end

# Process next link on the queue
def process_next_link
  link = current_status[:to_process].shift # return first link on the queue
  # ... login process the link
  current_status[:processed].push(link)
end

# shift method will not only return but also remove the link from the original array to avoid duplications

【讨论】:

  • 同时使用链接和处理,我需要检查两个数组中的链接是否存在,每个数组都通过#include?,我相信它是作为一个简单的扫描操作实现的。当我开始为每个数组添加大量链接时,处理时间会迅速增加。
  • 另外,我已经在使用Queue 来处理实际的处理部分。我只是在寻找一种有效的方法来跟踪我已完成的条目,因此如果它们被放回队列中,我不会再次处理它们。
  • @SethV 我刚刚编辑了我的答案。正如我所看到的,您只需要检查一个数组(已处理链接的数组)中的存在。看看并告诉我,也许我不太了解你想要什么。
猜你喜欢
  • 2022-01-18
  • 2023-03-23
  • 1970-01-01
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
  • 2012-11-04
  • 2012-05-03
  • 1970-01-01
相关资源
最近更新 更多