yak shaving life

遠回りこそが最短の道

Rubyで配列の全要素に対する繰り返し処理の「正しい」書き方(Ruby 2.6.0 Ver.)

久々にRubyを触っていて、色々調べることが多い。

この間見つけた10年前のstack overflowがなんとなく面白かったので適当に言及してみる。「Rubyで配列の全要素に繰り返し処理する正しい方法はなんですか?」

stackoverflow.com   上から二番目の回答が好きなので、適当に訳してみる。

多分「正しい」方法はないんじゃないかな。イテレート(反復)の方法はたくさんあって、それらは適材適所だからね。

  • each は色んな場面で使えるよね。インデックスとかどうでもいいことが多いし。
  • each_ with _index は Hash#each に似てる。値とインデックスを扱える。
  • each_index はインデックスだけ。あんまり使わない。"length.times"と同じだね。
  • map はイテレートするもう一つの方法。配列を別の配列に変換したいときに便利。
  • select は配列の一部を抜き出してイテレートしたいときに使うやつ。
  • inject は全要素の和とか積を出すのに便利。あとはある一つの結果にまとめるようなとき。

なんかたくさん覚えなきゃいけないっぽいけど、心配ない。別に全部覚えなくてもなんとかなる。ただ、複数の異なるやり方を覚えてそれを使い始めれば、コードがより綺麗になる。そしてそれは、Rubyをマスターする道のりの一つでもあるんだ。 


どんな言語でもイテレート処理は微妙に違うので、こういうシンプルかつ適切なアドバイスができる人はすごくいいなあと思う。新しい言語を触るときにこういう記事があるととても助かる。(どうでもいいけど、injectよりreduceの方がしっくりくるんだけどな…と思ったらエイリアスされてるらしい。よかった)


ところで、この回答がされたのは2008年12月。当時のRubyの最新バージョンはいくつだろう。Release Noteを見てみると、どうやらRuby 1.9.1-preview1が最新で、普通に考えたらRuby 1.8.7-p72がプロダクションで使える実質最新版だったと思われる。

つまり、この回答にはRuby 1.9以降で追加されたメソッドが考慮されていない。Rubyに詳しい人ならそんなの当たり前だろという感じかもしれないが、僕みたいな未熟者はリファレンスを見ないと安心できないので、Arrayクラスと親のEnumerableクラスのメソッドを確認してみる。

プログラミング言語 Ruby リファレンスマニュアルのArrayとEnumerableのページについて、Ruby 1.8.7Ruby 2.6.0を比較してみる。2.6.0にのみ存在するメソッドは以下の通り。

# Arrayクラスのメソッド
append
push
bsearch
bsearch_index
difference
dig
filter!
select!
to_s
keep_if
max
min
prepend
unshift
repeated_combination
repeated_permutation
rotate
rotate!
sample
sort_by!
sum
to_h
union

# Enumerableクラスのメソッド
chunk
chunk_while
collect_concat
flat_map
each_entry
each_with_object
filter
grep_v
lazy
slice_after
slice_before
slice_when
sum
to_h
uniq

この中で配列の全要素探索に関係しそうなのはeach_with_object、sum/max/minあたりだろうか。

each_with_objectはinjectに似ているけどチョットチガウ的な感じ。https://blog.arkency.com/inject-vs-each-with-object/にいい感じの解説が書いてあった。個人的にはExample 2がわかりやすいと思う。(Example 1はなんかこう、それmapでええんちゃうの…と思ったので)

sum/max/mixは、この辺使えば全探索はいらないよという感じ。シンプルに便利。特にsumについては、上の記事で「総和はinjectで出せるよ」と書いてあるものの、sumの方がinjectで足し合わせるより早いらしいので覚えておくべきだろう。というかむしろ、以前はわざわざ全要素探索して加算や比較しないと出せなかったのかな。C言語かよ。もともとRuby 1.8を使っていたけど全然覚えてないな…。



まあそんなわけで、Ruby 2.6.0で配列の全要素探索するときは以下の7個+3個くらいのメソッドをおさえておけばよさそうです。なんか足りてなかったら誰か教えてください。

全探索

  • each:インデックスいらないとき
  • each_with_index:インデックスほしいとき
  • each_index:インデックスだけほしいとき
  • map:別の配列に変換したいとき
  • select:一部の要素だけ抜き出して全探索したいとき
  • inject (reduceでも可):全探索しながら結果を積み上げたいとき
  • each_with_object :初期値ありでmapみたいなことがしたいとき(※個人の意見です)

以下を使えば全探索が不要になるよ

  • sum
  • max
  • min



補足:バージョン間のメソッド一覧差分の出し方

リファレンス上でいい感じに見れるかと思ったけどなんか無理っぽかったしGithub上でdiffを見ようにもちょっと辛かった。ので、リファレンスの目次のメソッド一覧をコピペした上でこんな感じのことをした。Macです。

# 1.8.7のメソッド一覧をコピペ
pbpaste | sed -e 's/ /\'$'\n/g' > rb_array_1.8.7
# 2.6.9のメソッド一覧をコピペ
pbpaste | sed -e 's/ /\'$'\n/g' > rb_array_2.6.0

# 2.6.0にだけあるメソッドの一覧表示
diff rb_array_1.8.7 rb_array_2.6.0 |grep '>' |sed -e 's/> //g'

もっと簡単な差分の出し方があったら誰か教えてください。



蛇足

https://docs.ruby-lang.org/ja/2.6.0/class/Array.htmlインスタンスメソッド一覧、アルファベット順に並んでると見せかけてunshift だけ真ん中の方にあるのはなぜだろう。誰か教え(略)