LoginSignup
20
14

More than 5 years have passed since last update.

find_allのindex版

Last updated at Posted at 2014-07-28

Rubyで書いてる時たまに
 配列の中から条件を満たすすべての要素のインデックス(要素自体じゃなくて)
が欲しい時があるんですが、そういうとき普通はどう書くんでしょうかね??
イメージはfind_allのインデックス版みたいなものです。

ary = [5, 4, 1, 7, 3, 8]

#案1
p ary.each_with_index.select{|e, i| e >= 5}.map{|e| e[1]} #=>[0, 3, 5]

#案2
p ary.each_with_index.each_with_object([]){|(e, i), acc| acc << i if e >= 5} #=>[0, 3, 5]

とかすることがおおいですが、どっちも、んーという感じです。
生成するオブジェクト数の違いのせいで、要素数が多くなってくると案2のほうが倍近く早いみたいですね。

2014/07/29 追記
いろいろ案頂いたので、比較してみました。
2014/07/29 追記
また追加しました。

#coding: windows-31J
require 'benchmark'

def calc_time(comment)
  val = nil
  t = Benchmark.realtime {
    val = yield
  }
  puts "#{comment} #{t} (msec)"
  val
end

size = 10000000
ary = size.times.map{rand(size)}

calc_time("#案1") {
  ary.each_with_index.select{|e, i| e >= 5}.map{|e| e[1]} #=>[0, 3, 5]
}

calc_time("#案2") {
  ary.each_with_index.each_with_object([]){|(e, i), acc| acc << i if e >= 5} #=>[0, 3, 5]
}

calc_time("#i18nさん") {
  ary.map.with_index { |e,i| e >= 5 ? i : nil }.compact
}

calc_time("#nabetaniさん1") {
  ary.size.times.select{ |x| 5<=ary[x] }  
}

calc_time("#cielavenirさん") {
  ary.each_with_index.lazy.select{|e,i|e>=5}.map(&:last).force
}
calc_time("#cielavenirさん2") {
  ary.flat_map.with_index{|e,i|e>=5 ? i : []}
}
calc_time("#nabetaniさん2") {
  r=[]
  b = ary.size
  loop{
    if b=ary[0,b].rindex{ |x| 5<=x } then r << b else break r.reverse; end
  }
}

#ruby 2.0.0p247 (2013-06-27) [x64-mingw32]
#案1 3.219184 (msec)
#案2 2.044117 (msec)
#i18nさん 1.114063 (msec)
#nabetaniさん1 1.139066 (msec)
#cielavenirさん 7.587434 (msec)
#cielavenirさん2 1.712097 (msec)
#nabetaniさん2 3.168182 (msec)

やはり、私なんかがが考えたものより頂いた案の方が数段早いですね。聞いてみて良かったです。
i18nさんの方式の「nilでも一旦作ってしまって、後からcompactで除外する」がこんなに早いのは意外でした。

20
14
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
14