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で除外する」がこんなに早いのは意外でした。