2020年12月16日

プログラミング

Ruby Enumerableモジュールを理解して繰り返し処理を書く際の選択肢を増やす

目次

  1. はじめに
  2. Enumerableモジュールとは
  3. Enumerableモジュールのメソッドを使った繰り返し処理
  4. まとめ

新しい技術にチャレンジし続けるpalanのアドベントカレンダーDay16です。
昨日は「【iOS14.3リリース🎉】アプリ内ブラウザでWebAR(Webカメラ)が使用できるようになりました」という記事でした。

【iOS14.3リリース🎉】アプリ内ブラウザでWebAR(Webカメラ)が使用できるようになりました

はじめに

本日は、Enumerableモジュールの繰り返し処理について書いていきます。
最近社内のslack上で繰り返し処理の書き方について議論されることが何度かあり、その際に自身のEnumerableへの理解がふわっとしてるなと感じ、自分の考えの整理に加えて誰かの役に立てばと今回のテーマを選定しました。

なお、本記事における実行環境は、2020年12月16日時点での最新の安定版である2.7.2を使用し、Ruby 2.7.0 リファレンスマニュアルを参考とさせていただきました。

環境

  • Ruby: 2.7.2
  • rails: 6.0.3 (一部検証に使用しています)

Enumerableモジュールとは

Enumerableモジュールとは繰り返し処理を行うためのモジュールで、モジュール内のメソッドは全て each を使って定義されています。eachはArrayやHash、RangeなどEnumerableモジュールをインクルードしている各クラスで定義されており、eachを用いてEnumerableモジュールに定義されているメソッドを使うことができるという関係性にあります。

つまりEnumerableモジュールは、eachのみで書くことができる繰り返し処理をより簡潔に書くために提供されている拡張機能と考えることができます。

Enumerableモジュールの中でよく使われるメソッドとして、 map(collect)やselect(find_all, filter)があります。
普段何気なく使っているこれらのメソッドに対して「配列やハッシュに使えるメソッド」くらいの理解だったので、eachとEnumerableモジュールの関係性を考えてみるだけでも学びがありました。

本題に入る前に、ArrayやHashにEnumerableモジュールがインクルードされていることを確認します。(当たり前の前提かもしれませんが…)

Enumerableモジュールがインクルードされているがされていることにより、Array#eachやHash#each を使用してmapやselectが使えるようになっているわけですね。


# Array の場合
Array.ancestors
=> [Array, Enumerable, Object, Kernel, BasicObject]

# Hashの場合
Hash.ancestors
=> [Hash, Enumerable, Object, Kernel, BasicObject]

それではEnumerableモジュールのメソッドをいくつか見てみましょう。

Enumerableモジュールのメソッドを使った繰り返し処理

ここからは、社内で議題に上がったメソッドや、私が本記事を書くにあたってリファレンスを改めて読んで勉強した中で、便利そうなものをいくつか抜粋して紹介します。

count

countメソッドはレシーバの要素数を返すメソッドです。


 [1,2,3,4].count
=> 4

一見すると、Array#size や Hash#size に要素数を返すだけの珍しくもないメソッドです。
しかし、countの場合はブロックを定義することができ、ブロック内の条件で絞り込んだ要素数を取得することができます。
Enumerableモジュールの繰り返し処理ならではの特徴と言えます。


 [1,2,3,4].count{ |i| i > 1 }
=> 3

ただ、Array#sizeやArray#sizeではeachを使った繰り返し処理が行われていないと仮定すると、単に要素数を返すだけならsizeの方が処理速度は早いのかなと思いました。実際の数値は未調査ですが、試してみて別記事にまとめようと考えています。

ActiveRecord_Relationの場合は注意!

ActiveRecord_Relationからsizeやcountを呼び出す際は、挙動に注意が必要です。具体的にはSQLが発行されるケースとそうでないケースがあります。
順番に見てみましょう。

まず初めにActiveRecord_Relationクラスのusersを取得します。


# ユーザを取得
users = User.all

上記で取得した users に対しての挙動をそれぞれのメソッドの挙動を見ていきます。

まず初めに、sizeを使用した場合はSQLが発行されず単に要素数が返ってきます。ActiveRecordのsizeが呼び出されていると思うのですが、基本的にはArray#sizeの挙動と同じと言えそうです。


# SQL発行なし
users.size
=> 14

次にcontを使用した場合を見てみると、SQLを発行した結果の件数を返します。クラス名から直接呼び出すことも可能です。Enumerableモジュールのcountがオーバーライドされ、ActiveRecordのcountが呼び出されているようです。


# SQL発行あり
users.count
   (1.4ms)  SELECT COUNT(*) FROM `users`
=> 14


# SQL発行あり
User.count
   (1.0ms)  SELECT COUNT(*) FROM `users`
=> 14

最後に、ブロック付きのcountメソッドを見てみます。こちらはSQLが発行されず要素数の取得ができています。ブロック付きで呼び出した場合は、Enumerableモジュールのcountメソッドが呼び出されていると考えられます。


# SQL発行なし
users.count { |user| user.id == 1 }
=> 1

これらの違いを意識することで、SQLを発行せずオブジェクトの要素数を高速で取得したいケースと、SQLを発行してオブジェクトを再取得した上で要素数を取得したいケースで使い分けることができます。
lengthメソッドについては本記事では言及しませんが、sizeのエイリアスという理解で良いかと思います。

また、countに限らずEnumerableモジュールの他のメソッドにおいても、オーバーライドされることで挙動が変わることがあるので注意は必要です。

each_with_object

each_with_objectは初期値を指定して配列やハッシュを組み立てることができるメソッドです。


[1,2,3,4].each_with_object([]) { |i, a| a << i * 2 if i > 1 }
=> [4, 6, 8]

下記の、map + compactで書くケースと、select + mapで書くケースで書き換えることができます。

map + compactから見ていきます。mapはブロック内のすべての結果を要素とした配列を返すため、そのままでは条件に合致しない要素はnilとして配列に入り込んでしまいます。そのため、条件で絞り込んだ配列を取得するにはcompactでnilを除去する必要があります


[1,2,3,4].map { |i| i * 2 if i > 1 }.compact
=> [4, 6, 8]

select + mapで書くケースです。
これは見たまんまですが、selectで絞り込んでから、mapで配列を加工する方法です。


[1,2,3,4].select { |i| i > 1 }.map { |i| i * 2 }
=> [4, 6, 8]

上記のような簡潔な処理であれば、each_with_objectの恩恵は見えにくく、むしろ、select + map のほうが簡潔に見えます。
each_with_objectは絞り込みや加工の条件が複雑になった際に、有効なメソッドだと考えています。
each_with_object と select + map の2つの選択肢を持っておいて、状況で使い分けるのが良いかと思います。

また、配列からハッシュを組み立てたい場合もeach_with_objectを使うことができます(具体的は使用ケースは思い浮かびません…)

filter_map

最後にruby2.7から追加されたfilter_mapです。
filter_mapはブロックを評価した値のうち、真の値を要素とした配列を返します。つまり、条件で絞り込んだ場合にnilが配列入り込まないため、select + map や each_with_objectの代用として使うことができ、簡潔に書くことができます。
ruby2.7を使っている方はぜひ試してみてください。


[1,2,3,4].filter_map { |i| i * 2 if i > 1 }
=> [4, 6, 8]

まとめ

以上、今回の記事ではEnumerableモジュールとメソッドについてご紹介してきました。

記事を書いてみて自分自身の勉強になり、タイトル通り、繰り返し処理を書く際の選択肢を増やすことができたかなと思っています。

Enumerableモジュールはメソッド数もそれほど多くないので、リファレンスマニュアルを一読してみるのも良いかもしれません。

この記事が何かのお役に立てば幸いです。

Rubyのお仕事に関するご相談

Bageleeの運営会社、palanではRubyに関するお仕事のご相談を無料で承っております。
zoomなどのオンラインミーティング、お電話、貴社への訪問、いずれも可能です。
ぜひお気軽にご相談ください。

無料相談フォームへ

0

0

AUTHOR

kobori

こぼり ともろう エンジニア

サーバーサイドエンジニア。SIerを経て2019年7月に入社。日々学習しながらRuby on Railsを使った開発を行っています。

アプリでもっと便利に!気になる記事をチェック!

記事のお気に入り登録やランキングが表示される昨日に対応!毎日の情報収集や調べ物にもっと身近なメディアになりました。

簡単に自分で作れるWebAR

「palanAR」はオンラインで簡単に作れるWebAR作成ツールです。WebARとはアプリを使用せずに、Webサイト上でARを体験できる新しい技術です。

palanARへ
palanar

palanはWebARの開発を
行っています

弊社では企画からサービスの公開終了まで一緒に関わらせていただきます。 企画からシステム開発、3DCG、デザインまで一貫して承ります。

webar_waterpark

palanでは一緒に働く仲間を募集しています

正社員や業務委託、アルバイトやインターンなど雇用形態にこだわらず、
ベテランの方から業界未経験の方まで様々なかたのお力をお借りしたいと考えております。

話を聞いてみたい

運営メンバー

eishis

Eishi Saito 総務

SIerやスタートアップ、フリーランスを経て2016年11月にpalan(旧eishis)を設立。 マーケター・ディレクター・エンジニアなど何でも屋。 COBOLからReactまで色んなことやります。

sasakki デザイナー

アメリカの大学を卒業後、日本、シンガポールでデザイナーとして活動。

しまだ

しまだ デザイナー

WebAR/VRのデザインと3DCG制作がメインです。 肩書きは「アニメ案件に関わりたいデザイナー」。

Miu マーケター

ドイツでWEBマーケティングしています。

しんのき エンジニア

主に React Native を使ったアプリ開発と AWS や Firebase を使ったサーバーレスアーキテクチャを担当しています。元々はインフラとかPHPをやっていました。

yamakawa

やまかわたかし デザイナー

フロントエンドデザイナー。デザインからHTML / CSS、JSの実装を担当しています。最近はReactやReact Nativeをよく触っています。

furuya エンジニア

サーバーサイド、フロントエンド、Unityと色々手を出してる雑食系エンジニア。ReactNativeが最近のマイブーム。

Sayaka Osanai デザイナー

Sketchだいすきプロダクトデザイナー。シンプルだけどちょっとかわいいデザインが得意。 好きな食べものは生ハムとお寿司とカレーです。

はらた

はらた エンジニア

サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

うえまつゆい エンジニア

サーバーサイドエンジニアからフロントエンドエンジニアになりました。主にReact Nativeでのアプリ開発をしています。

kobori

こぼり ともろう エンジニア

サーバーサイドエンジニア。SIerを経て2019年7月に入社。日々学習しながらRuby on Railsを使った開発を行っています。

sasai

ささい エンジニア

フロントエンドエンジニア WebGLとReactが強みと言えるように頑張ってます。

damien

Damien

WebAR/VRを中心に企画・マークアップ・開発をやっています。森に住んでいます。

デザイナーゲスト

ゲスト デザイナー

かっきー

かっきー

まりな

まりな

suzuki

suzuki

taro

taro

xR界隈のビズをやっています。新しいガジェットとか使うのが好きです。あとお寿司は玉子のお寿司が好きです。

miyagi

ogawa

ogawa

雑食デザイナー。UI/UXデザインやコーディング、時々フロントエンドやってます。最近はARも。

いわもと

いわもと

CONTACT PAGE TOP