バートリーのさいとうです。
今回は
each文の中でinclude?メソッドを使った配列の存在チェックの結果が、全てtrueで返ってきてしまう理由とは?
を解説していきます。
include?メソッドは以下のように、配列および文字列への存在チェックを行うことができます。
fruits = ["apple", "banana", "orange"]
p fruits.include?("apple")
#=> true
fruit = "mango"
p fruit.include?("apple")
#=> false
今回の記事では、筆者自身が
include?を繰り返し処理内で使ったときに、trueしか返ってこない状態
になったので、この理由を
- include?の機能の正しい理解
- 「処理の順番」
という2点から、解説します。
もし同じバグに悩まされている方は、この記事を読めばその理由と解決方法が分かります。
最後には、「なんだ、それだけか!!」ってなると思うので、是非解決方法を持ち帰ってください。
include?メソッドは完全一致しかtrueにならない
リファレンスマニュアルによると、include?メソッドの説明として
配列が val と == で等しい要素を持つ時に真を返します。
https://docs.ruby-lang.org/ja/latest/method/Array/i/include=3f.html
==というのは「完全に同じ!」ってことなので、つまり完全一致が求められます。
なので
nums = ["1", "2", "3"]
p nums.include?(1)
#=> false
というように、型違いの場合も、一致しているとは見なされません。
これでinclude?メソッドの理解ができたので、本題に入っていきましょう。
スーパーで買い物するときの例
例えば、このような例を考えてみましょう。
- お客さんがスーパーでカゴの中に入れた果物を表示したい
- もし同じ果物が二つあるときは、表示の仕方を変えたいので、重複チェックのフラッグを作成する
みたいな感じだとします。
この時、こんな感じに書けそうです
@fruits_in_cart = []
fruits = ["apple", "banana", "orange"]
fruits.each do |fruit|
@fruits_in_cart << fruit
@dpl_fruit_in_cart_flag = @fruits_in_cart.include?(fruit) unless @dpl_fruit_in_cart_flag
end
出力結果を見てみましょう。
p @fruits_in_cart
#=> ["apple", "banana", "orange"]
p @dpl_fruit_in_cart_flag
#=> true
カートの中に入った果物は、正しく表示されていますね。
しかし、配列の中身が重複していないのに、flagがtrueになっています。
なぜでしょうか?
この理由が、処理の順番に隠されています。
eachはそれぞれの要素をひとつずつ判定する。
もう一度、each構文を見てみましょう。
fruits.each do |fruit|
@fruits_in_cart << fruit
@dpl_fruit_in_cart_flag = @fruits_in_cart.include?(fruit) unless @dpl_fruit_in_cart_flag
end
意外と忘れやすいのですが、eachはひとつずつ順番に処理を行ってくれます。
今回の例では、fruits配列に含まれている果物をひとつずつ、順番に処理してくれています。
この機能のおかげで、1~3回目それぞれで
fruits.each do |fruit|
#カートにりんご(→バナナ→みかん)を入れます
@fruits_in_cart << fruit
#直前に入れたりんご(→バナナ→みかん)がカートに含まれているかチェックします(そりゃ入っているやろ...True!!)
@dpl_fruit_in_cart_flag = @fruits_in_cart.include?(fruit) unless @dpl_fruit_in_cart_flag
end
という、茶番が発生しています。
対応策
では、どのように対処すればいいでしょうか?
答えは、順番を入れ替える、です。
fruits.each do |fruit|
#りんごがカートに含まれているかチェックします
@dpl_fruit_in_cart_flag = @fruits_in_cart.include?(fruit) unless @dpl_fruit_in_cart_flag
#カートにりんごを入れます
@fruits_in_cart << fruit
end
これで、
p @fruits_in_cart
#=> ["apple", "banana", "orange"]
p @dpl_fruit_in_cart_flag
#=> false
と、正しい結果を得ることができました。
補足
RubyおよびRailsには、なんかたくさんincludeという名前のつくメソッドがあります。
今回説明したinclude?メソッドは、Rubyのメソッドです。(Array/String#include?)
includeは、モジュールをクラスやモジュールに埋め込むためのRubyのメソッドです。(Module#include)
そして、Railsにはincludesメソッドもあります。
頑張って見分けましょう。
まとめ
今回は、each文の中でinclude?メソッドを使った配列の存在チェックの結果が、全てtrueで返ってきてしまう理由とは?について、解説してきました。
茶番を巻き込まれないように、皆さんも気をつけましょう。
このように、月に15本目標で、Ruby, Ruby on Railsを中心に技術ブログを更新しています。
もし参考になったなと思ったら、ブックマークをお願い致します。
最後まで読んでいただき、ありがとうございました!
それでは。