如何从数组中获取第一个匹配项

时间:2015-09-07 19:46:30

标签: arrays ruby

给定一个数组:[1, 2, 3, 5, 8. 13]

我可以选择大于3的所有项目:

[1, 2, 3, 5, 8. 13].select { |num| num > 3 }

(我知道简写select(&:>)语法,这不是重点)

我现在可以轻松地返回第一个。

[1, 2, 3, 5, 8. 13].select { |num| num > 3 }.first

但是当实际比较变得越来越重要时,这并不是非常有效。我正在尝试优化我们拥有300多个项目的数组的情况,select几乎将在所有情况下返回第一个(并且数组已经排序)。此外,我们进行比较的代码非常繁重(例如,需要往返数据库)。

是否有红宝石的速记来获取第一个然后停止?类似于:

[1, 2, 3, 5, 8. 13].each do |num|
   return num if num > 3
end

3 个答案:

答案 0 :(得分:4)

只需使用find

arg.apache.avro % avro

答案 1 :(得分:3)

使用二进制搜索,它会在O(log n)

中找到匹配的元素

这是一个细分:

最慢的:

  2.1.2 :011 > [1, 2, 3, 5, 8, 13].sort.bsearch { |num| num > 3 }
 => 5 

慢:

 2.1.2 :010 > [1, 2, 3, 5, 8, 13].find { |num| num > 3 }
 => 5 

最快:

2.1.2 :012 > [1, 2, 3, 5, 8, 13].bsearch { |num| num > 3 }
 => 5 
2.1.2 :013 > 

这是我刚刚编写的用于比较所有方法的小脚本:

 $ cat ./benchmark_find_bsrch.rb
 #!/usr/bin/env ruby

require 'benchmark/ips'

DATA = Array(0..10)

def find
DATA.find { |num| num > 3 }
end

def sort_bsearch
[10,9,8,4,5,6,1,2,3,7].sort.bsearch { |num| num > 3 }
end

def bsearch
DATA.bsearch { |num| num > 3 }
end

def select 
DATA.select { |num| num > 3 }
end

def lazy_select
DATA.lazy.select { |num| num > 3 }.first
end

Benchmark.ips do |bm|
  bm.report('find'){ find }
  bm.report('sort_bsearch'){ sort_bsearch }
  bm.report('bsearch'){ bsearch }
  bm.report('select'){select }
  bm.report('lazy_select') {lazy_select}
  bm.compare!
end

其中输出以下内容:

util-scripts$ ./benchmark_find_bsrch.rb 
Calculating -------------------------------------
                find    63.607k i/100ms
        sort_bsearch    52.039k i/100ms
             bsearch    95.260k i/100ms
              select    57.218k i/100ms
         lazy_select    11.850k i/100ms
-------------------------------------------------
                find      1.130M (± 5.0%) i/s -      5.661M
        sort_bsearch    809.723k (± 6.5%) i/s -      4.059M
             bsearch      2.099M (± 6.1%) i/s -     10.479M
              select    929.578k (± 2.6%) i/s -      4.692M
         lazy_select    140.782k (± 8.1%) i/s -    711.000k

Comparison:
             bsearch:  2098632.5 i/s
                find:  1129912.5 i/s - 1.86x slower
              select:   929578.3 i/s - 2.26x slower
        sort_bsearch:   809722.5 i/s - 2.59x slower
         lazy_select:   140782.2 i/s - 14.91x slower

我希望你觉得这很有用:)

答案 2 :(得分:2)

您可以使用Enumerator#lazy

[1, 2, 3, 5, 8, 13].lazy.select { |num| num > 3 }.first
  #=> 5

对于您的问题,Enumerable#find显然是最佳选择,但假设收集量很大,并且您希望第一个n > 1元素大于3?使用lazy,您可以写:

(0..100_000_000_000).lazy.select { |num| num > 3 }.first(2)
  # => [4, 5]

在眨眼之间执行。