Ruby

【Ruby】each文の中でinclude?メソッドを使った配列の存在チェックが全てtrueで返ってくる理由とは?

バートリーのさいとうです。

今回は

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を中心に技術ブログを更新しています。

もし参考になったなと思ったら、ブックマークをお願い致します。

最後まで読んでいただき、ありがとうございました!

それでは。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA