我可以更快地制作这个Ruby代码和/或使用更少的内存吗?

我在Ruby中有一个String对象Array ,它们由下面的单词组成:

animals = ["cat horse", "dog", "cat dog bird", "dog sheep", "chicken cow"]

我想将其转换为另一个String对象Array ,但每个元素只有一个动物,只有唯一的元素。 我找到了一种方法,如下所示:

 class Array def process() self.join(" ").split().uniq end end 

但是,如果输入数组是巨大的,让我们说数百万个条目然后执行这将是非常糟糕的因为我将创建一个巨大的字符串,然后一个巨大的数组然后uniq必须处理该巨大的数组以删除重复的元素。 我考虑加快速度的一种方法是为每个单词创建一个带有条目的Hash ,这样我只会在第一遍中处理每个单词一次。 有没有更好的办法?

你有正确的想法。 但是,Ruby有一个内置类,非常适合构建一组独特的项目: Set 。

 animals = ["cat horse", "dog", "cat dog bird", "dog sheep", "chicken cow"] unique_animals = Set.new animals.each do |str| unique_animals.merge(str.split) end # => cat # horse # dog # bird # sheep # chicken # cow 

要么…

 unique_animals = animals.reduce(Set.new) do |set, str| set.merge(str.split) end 

在封面下,Set 实际上使用Hash来存储它的项目,但它更像是一个无序的Array,并响应所有熟悉的Enumerable方法( eachmapselect等)。 但是,如果你需要把它变成一个真正的数组,只需使用Set#to_a 。

令人惊讶的是(也许),我认为你不会比现在的代码更快。 我认为您的代码同时是最快且最易读的。 原因如下:您的代码表达了一个非常好的高级算法,可以直接映射到Ruby高级方法 。 这些方法经过优化和编译。 祝你在纯Ruby中实现更快的速度。 在任何情况下,我都不是Ruby大师,我会非常有兴趣在合理大小的数组上看到更高效的解决方案。

Jordan和Nathaniel实现了更精细的解决方案,并且“手动”迭代地处理输入数组。 虽然这可能会占用更少的内存,但它不会像Ruby的uniq那么快。 但是,如果您遇到大型arrays的内存问题(或达到某个阈值时出现性能问题),当然您应该考虑实现这些内容的变体。 这是我的:

 def process distincts = Hash.new self.each { |words| words.split.each { |word| distincts[word] = nil }} distincts.keys end 

这是Jordan的解决方案,使用Hash而不是Set。 这就是你打算使用的。 直接使用Hash将消除维护Set的开销(或者我认为),并且应该明显更快。 稍微更快的解决方案可能是:

 def process distincts = Hash.new self.each { |words| words.split.each { |word| distincts[word] = :present unless distincts[word] }} distincts.keys end 

再一次,我不确定(对不起,我现在无法轻易测试所有这些)。 无论如何,我怀疑这两个中的一个更接近原始代码的表现,但我怀疑它会克服它(再次,直到你达到一定的输入大小)。

为什么不自己处理每个数组元素?

 for each element in [...] if the element does not contain spaces insert it into the result array else split it up and insert its parts in the next position ahead end end 

以下是ruby实现:

 class Array def process d = dup d.each_with_object([]).each_with_index do |(element, array), index| if !element.index " " array << element if !array.include? element else d.insert index+1, *(element.split) end end end end ["cat horse", "dog", "cat dog bird", "dog sheep", "chicken cow"].process => ["cat", "horse", "dog", "bird", "sheep", "chicken", "cow"] 

好处:

  • 您不必处理长字符串
  • 尽可能接近线性时间(见缺点)
  • 维护元素顺序

缺点:

  • 比线性时间略慢(由于字符串如何拆分并向前插入)

也就是说,它比join(" ").split().uniq (更少的循环)快得多。 但是从实际意义上说它更快,而不是科学意义上的。

我已经尝试过其他人在这里提出的各种方法,但是我想出了两个比其他人建议的更快但不如原来不快的方法。

  # This one moves through the original Array using inject to process # each element containing space-separated words and appending them # to a new array. Finally uniq is called to remove duplicate words def process_new_4 self.inject([]) { |array, words| array.push(*words.split) }.uniq end # This one uses the flat_map method of Array to flatten itself, each # element is split in case it contains more than one word, then the # flattened array has duplicate elements removed with uniq def process_new_3 self.flat_map(&:split).uniq end