如何使用Ruby快速计算字符串中子字符串的出现次数

时间:2011-06-17 01:43:17

标签: ruby-on-rails ruby performance string benchmarking

我有一个300MB的文本文件,我想计算文件中每10,000个子字符串的出现次数。我想知道如何快速完成。

现在,我使用以下代码:


content = IO.read("path/to/mytextfile")
Word.each do |w|
  w.occurrence = content.scan(w.name).size
  w.save
end

Word是一个ActiveRecord类。

我花了差不多1天才完成计算。反正有更快的做法吗?感谢。

EDIT1: 再次感谢你。我正在运行rails 2.3.9。单词表的name字段包含我要搜索的内容,并且它仅包含唯一值。我没有使用Word.each,而是使用批量(一次1000行)加载。它应该有所帮助。

我用bpaulon的想法重写了整个代码。现在只花了几个小时才完成计数。

我分析了新的版本代码,现在最大的时间成本计算方法是utf8编码支持的字符串截断代码

def truncate(n)
  self.slice(/\A.{0,#{n}}/m)
end

和计算代码的字符

def utf8_length
  self.unpack('U*').size
end

还有其他更快的方法来替换它们吗?

3 个答案:

答案 0 :(得分:3)

您对scan的使用会创建一个数组,计算它的大小,然后将其抛弃。如果你在大文件中出现很多子字符串,你会暂时创建一个大数组,可能会耗尽内存管理的CPU时间,但即使使用300MB也应该很快运行。

因为Word是ActiveRecord类,所以它依赖于数据库中的模式和任何索引,以及数据库服务器可能遇到的任何问题。如果数据库未优化或响应缓慢或用于检索数据的查询效率不高,则迭代将很慢。您可能会发现抓取Word组的速度要快得多,因此它们位于RAM中,然后迭代它们。

而且,如果数据库和您的代码在同一台机器上运行,您可能会遇到资源限制,例如只有一个驱动器,没有足够的RAM等等。

在不了解您的环境和硬件的情况下,很难说。


编辑:

  

我可以首先将子串捕获到数组/哈希中,然后将计数结果添加到数组或哈希中,并在完成所有计数后将结果写回数据库。你认为它更快,对吧?

不,我怀疑这会有多大帮助,而且,如果不知道问题出在哪里,你可能会做的就是让问题变得更糟,因为你必须从数据库中加载10,000条记录作为对象,然后构建一个10,000元素散列或数组,它们也将与DB记录一起存储在内存中,然后将它们写出来。

Ruby目前只使用单个核心,但您可以通过使用Ruby 1.9+获得速度。我建议installing RVM并让它管理你的Ruby。请务必阅读该页面上的说明,然后运行rvm notes并按照这些说明操作。

您的Word模型和底层架构和索引是什么样的?数据库是否在同一台机器上?


编辑:通过查看您的表架构,您没有除id之外的索引,这对于正常查找实际上没有多大帮助。我建议在Stack Overflow的兄弟网站https://dba.stackexchange.com/上展示你的架构并解释你想做什么。我至少会在文本字段中添加一个键,以帮助避免对您执行的任何搜索进行全表扫描。

可能有帮助的更多内容是从“Active Record Query Interface”中读取:Retrieving Multiple Objects in Batches

另外,查看Word.each运行时发出的SQL。它是"select * from word"吗?如果是这样,Rails将提取10,000条记录,逐个迭代它们。如果它类似于"select * from word where id=1",那么对于每个记录,您都有一个数据库读取,然后在更新计数时写入。这就是“批量检索多个对象”链接将有助于修复的情况。

另外,我猜你content是你要搜索的文字,但我无法确定。您是否可能有重复的文本值导致您对同一文本进行多次扫描?如果是,请在该字段上使用unique条件选择记录,然后一次更新所有匹配记录的计数。

您是否已分析过代码以了解Ruby本身是否可以帮助您查明问题?稍微修改您的代码以处理100或1000条记录。使用-r profile标志启动应用。当应用程序退出探查器时,将输出一个表格,显示花费的时间。

你在运行什么版本的Rails?

答案 1 :(得分:1)

我认为你可以用不同的方式解决这个问题

您不需要多次扫描文件,您可以创建一个数据库,如mongomysql,对于您找到的每个单词,您可以为它获取数据库,然后增加一些“反”字段。

你可以问我“但是我必须经常扫描我的数据库,这可能会花费更多”。好吧,你肯定不会问这个,但是不会花费更多的时间,因为数据库集中在IO中,除了你总是可以index it


编辑:根本没有办法划界?假设你拥有一个Word.name字符串,你真的拥有一个(不是简单的)正则表达式。正则表达式是否包含\ n?好吧,如果正则表达式可以包含任何值,您应该估计正则表达式可以获取的字符串的最大大小,加倍,并通过该字符数量扫描文件,但将光标移动该数字。

让我们说你对正则表达式可以获取的最大值的估计就像20个字符,你的文件有0到30000个字符。你将每个正则表达式从0到40个字符传递,然后再从20到60,从40到80,等等......

您还应该保留您在较小的正则表达式中找到的位置,这样就不会重复它。

最后,这个解决方案似乎不值得努力,你的问题可能有一个更好的解决方案,基于正则表达式是什么,但它会比调用扫描Words.count倍你的300Mb字符串更快。

答案 2 :(得分:0)

您可以将整个“Word”表格加载到Trie,然后进行回溯,因为您说文本中没有分隔符。

因此,对于文本中的每个字符,请记下单词。如果您单击一个单词,则增加其计数。 “走下去”涉及三个案例:

  1. 此角色没有节点。 (如果你是搜索中期,请弹出反向跟踪堆栈)
  2. 此角色有一个节点。 (但这不是一个词)
  3. 此角色有一个节点。 (这是一个字 - 增量和“脏”)
  4. 回溯跟踪只是在你用尽Trie的“搜索”之后跟踪你想要去的地方,当你用完节点访问时。这可能是您访问的每个角色,这是Trie的根。

    完成此操作后,您可以访问您更改的所有节点,只需更新它们所代表的记录。

    这需要一些时间来实施,但肯定会比每个&更快。扫描。