web-dev-qa-db-ja.com

Rubyの配列に値が存在するか確認する

'Dog'と配列['Cat', 'Dog', 'Bird']があります。

ループせずに配列に存在するかどうかを確認する方法はありますか。値が存在するかどうかをチェックする簡単な方法はありますか。

1219
user211662

探しているのは include?

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
1814
Brian Campbell

@ campatersonで指摘されているように、v3.1から in?メソッドActiveSupport(Railsの一部)にあります。だからRailsの中、あるいはrequire 'active_support'なら、次のように書くことができます。

'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false

OTOHさん、Ruby自体にはin演算子や#in?メソッドはありません、特に以前に提案されていますが 、特にRuby-coreの一流メンバーである遠藤裕介 によって。

他の人が指摘したように、EnumerableArrayHashSetを含むすべてのRangeに対して、逆のメソッド include? が存在します。

['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false

配列にたくさんの値がある場合は、それらすべてが順番にチェックされ(つまりO(n))、ハッシュの検索は一定時間(つまりO(1))行われます。たとえば、配列が定数の場合は、代わりに Set を使用することをお勧めします。例えば:

require 'set'
ALLOWED_METHODS = Set[:to_s, :to_i, :upcase, :downcase
                       # etc
                     ]

def foo(what)
  raise "Not allowed" unless ALLOWED_METHODS.include?(what.to_sym)
  bar.send(what)
end

クイックテスト は、10個の要素Setに対してinclude?を呼び出すことが、同等のArrayに対してそれを呼び出すよりも約3.5倍速いことを明らかにします(要素が見つからない場合)。

最後の最後の注意:Rangeinclude?を使うときは細心の注意が必要ですので、 doc を参照し、 cover? と比較してください。

228

やってみる

['Cat', 'Dog', 'Bird'].include?('Dog')
160
schmitzelburger

Enumerable#includeを使用してください。

a = %w/Cat Dog Bird/

a.include? 'Dog'

あるいは、いくつかのテストが行​​われた場合、1 (include?でも)ループを取り除き、O(n)からO( 1)と一緒に:

h = Hash[[a, a].transpose]
h['Dog']

1.私はこれが明白であるが異論を避けることを願っています:はい、ほんの数回の検索ではHash []と転置演算子がプロファイルを支配し、それぞれO(n)自分自身。
48
DigitalRoss

あなたがブロックで確認したい場合は、あなたはそれを試すことができますか?またはすべて?.

%w{ant bear cat}.any? {|Word| Word.length >= 3}   #=> true  
%w{ant bear cat}.any? {|Word| Word.length >= 4}   #=> true  
[ nil, true, 99 ].any?                            #=> true  

詳細はこちら: http://Ruby-doc.org/core-1.9.3/Enumerable.html
私のインスピレーションはここから来ます: https://stackoverflow.com/a/10342734/576497

44
Van

Rubyには、配列内の要素を見つけるための11の方法があります。

推奨されるものはinclude?です

繰り返しアクセスする場合は、セットを作成してからinclude?またはmember?を呼び出します。

これが全部です

array.include?(element) # preferred method
array.member?(element)
array.to_set.include?(element)
array.to_set.member?(element)
array.index(element) > 0
array.find_index(element) > 0
array.index { |each| each == element } > 0
array.find_index { |each| each == element } > 0
array.any? { |each| each == element }
array.find { |each| each == element } != nil
array.detect { |each| each == element } != nil

要素が存在する場合、それらはすべてtrueish値を返します。

include?が推奨される方法です。これは、C言語のforループを内部的に使用して、要素が内部のrb_equal_opt/rb_equal関数と一致すると中断します。メンバーシップチェックを繰り返すためのセットを作成しない限り、それほど効率的になることはありません。

VALUE
rb_ary_includes(VALUE ary, VALUE item)
{
  long i;
  VALUE e;

  for (i=0; i<RARRAY_LEN(ary); i++) {
    e = RARRAY_AREF(ary, i);
    switch (rb_equal_opt(e, item)) {
      case Qundef:
        if (rb_equal(e, item)) return Qtrue;
        break;
      case Qtrue:
        return Qtrue;
    }
  }
  return Qfalse;
}

member?Arrayクラスでは再定義されておらず、文字通りすべての要素を列挙するEnumerableモジュールからの最適化されていない実装を使用しています。

static VALUE
member_i(RB_BLOCK_CALL_FUNC_ARGLIST(iter, args))
{
  struct MEMO *memo = MEMO_CAST(args);

  if (rb_equal(rb_enum_values_pack(argc, argv), memo->v1)) {
    MEMO_V2_SET(memo, Qtrue);
    rb_iter_break();
  }
  return Qnil;
}

static VALUE
enum_member(VALUE obj, VALUE val)
{
  struct MEMO *memo = MEMO_NEW(val, Qfalse, 0);

  rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
  return memo->v2;
}

Rubyコードに翻訳すると、これは次のことについて行います。

def member?(value)
  memo = [value, false, 0]
  each_with_object(memo) do |each, memo|
    if each == memo[0]
      memo[1] = true 
      break
    end
  memo[1]
end

include?member?はどちらも、期待値の最初の出現箇所を見つけるために配列を検索するため、O(n)の時間が複雑になります。

最初に配列のハッシュ表現を作成しなければならないという犠牲を払ってO(1)アクセス時間を得るために集合を使うことができます。同じアレイのメンバーシップを繰り返し確認すると、この初期投資ですぐに成果が得られます。 SetはCでは実装されていませんが、プレーンなRubyクラスとして実装されていますが、それでも基礎となる@hashO(1)アクセス時間により、これは価値があります。

これがSetクラスの実装です。

module Enumerable
  def to_set(klass = Set, *args, &block)
    klass.new(self, *args, &block)
  end
end

class Set
  def initialize(enum = nil, &block) # :yields: o
    @hash ||= Hash.new
    enum.nil? and return
    if block
      do_with_enum(enum) { |o| add(block[o]) }
    else
      merge(enum)
    end
  end

  def merge(enum)
    if enum.instance_of?(self.class)
      @hash.update(enum.instance_variable_get(:@hash))
    else
      do_with_enum(enum) { |o| add(o) }
    end
    self
  end

  def add(o)
    @hash[o] = true
    self
  end

  def include?(o)
    @hash.include?(o)
  end
  alias member? include?

  ...
end

ご覧のとおり、Setクラスは内部の@hashインスタンスを作成し、すべてのオブジェクトをtrueにマッピングしてから、HashクラスのO(1)アクセス時間で実装されるHash#include?を使用してメンバーシップをチェックします。

他の7つの方法はすべて効率が悪いので説明しません。

O(n)の複雑さを上に挙げた11以外にも実際にはもっともっと多くのメソッドがありますが、最初のマッチで壊れるのではなく配列全体をスキャンするのでそれらをリストしないことにしました。

使わないで

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...
31
akuhn

いくつかの回答からArray#include?が提案されていますが、重要な注意点が1つあります。ソースを見ると、Array#include?でさえループを実行します。

rb_ary_includes(VALUE ary, VALUE item)
{
    long i;

    for (i=0; i<RARRAY_LEN(ary); i++) {
        if (rb_equal(RARRAY_AREF(ary, i), item)) {
            return Qtrue;
        }
    }
    return Qfalse;
}

ループせずにWordの存在をテストする方法は、配列にトライを作成することです。そこに多くのトライ実装があります(グーグル "Rubyトライ")。この例ではrambling-trieを使用します。

a = %w/cat dog bird/

require 'rambling-trie' # if necessary, gem install rambling-trie
trie = Rambling::Trie.create { |trie| a.each do |e| trie << e end }

そして、Array#include?と同じ構文の単純さで、副次的なTrie#include?を使って、O(log n)時間にそれをループすることなく、配列の中にさまざまな単語が存在するかどうかをテストする準備ができました。

trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
29
Boris Stitnicky

ループしたくない場合は、配列でそれを行う方法はありません。代わりにSetを使うべきです。

require 'set'
s = Set.new
100.times{|i| s << "foo#{i}"}
s.include?("foo99")
 => true
[1,2,3,4,5,6,7,8].to_set.include?(4) 
  => true

セットは内部的にハッシュのように動作するので、名前が示すように、各ハッシュがメモリ内の特定のポイントを指すようにキーのハッシュを生成し、メモリマップを作成するので、Rubyはコレクションをループして項目を見つける必要はありません。前の例はハッシュで行われました:

fake_array = {}
100.times{|i| fake_array["foo#{i}"] = 1}
fake_array.has_key?("foo99")
  => true

欠点は、セットとハッシュキーには一意のアイテムしか含めることができず、多くのアイテムを追加する場合は、より大きなキースペースに適した新しいマップを作成するために、特定の数のアイテムの後に全体を再ハッシュする必要があります。もっと詳しく知りたい方は、ぜひご覧ください MountainWest RubyConf 2014 - Nathan Longによる自家製ハッシュのビッグO

これがベンチマークです。

require 'benchmark'
require 'set'

array = []
set   = Set.new

10_000.times do |i|
  array << "foo#{i}"
  set   << "foo#{i}"
end

Benchmark.bm do |x|
  x.report("array") { 10_000.times { array.include?("foo9999") } }
  x.report("set  ") { 10_000.times { set.include?("foo9999")   } }
end

そしてその結果:

      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)
16
Kimmo Lehto

これは、これを行うためのもう1つの方法です。Array#indexメソッドを使用します。

配列内で最初に出現した要素のインデックスを返します。

例:

a = ['cat','dog','horse']
if a.index('dog')
    puts "dog exists in the array"
end

index()もブロックを取ることができます

例えば

a = ['cat','dog','horse']
puts a.index {|x| x.match /o/}

ここでは、文字 'o'を含む配列の最初のWordのインデックスを返します。

15
Zack Xu

楽しい事実、

*を使用して、case式で配列のメンバーシップを確認できます。

case element
when *array 
  ...
else
  ...
end

When節の小さな*に注目してください。これは配列のメンバーシップをチェックします。

スプラット演算子の通常の魔法の振る舞いはすべて当てはまるので、たとえばarrayが実際には配列ではなく単一の要素である場合、それはその要素と一致します。

9
akuhn

これを実現するには複数の方法があります。それらのいくつかは次のとおりです。

a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false
8
sumit

これはそれが存在することだけでなくそれが何回現れるのもあなたに言うでしょう:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1
5
user3245240

何らかのキーについて複数回チェックする必要がある場合は、arrhashに変換してから、O(1)をチェックインしてください。

arr = ['Cat', 'Dog', 'Bird']
hash = arr.map {|x| [x,true]}.to_h
 => {"Cat"=>true, "Dog"=>true, "Bird"=>true}
hash["Dog"]
 => true
hash["Insect"]
 => false

ハッシュ#has_key?Array#include?のパフォーマンス

パラメータハッシュ#has_key? Array#include 
 
時間計算量O(1)操作O(n)操作
 
アクセスタイプHash [key]が各要素
を繰り返すと、それまでの配列の任意の値が返されます。コールコール    

include?を使ったシングルタイムチェックには大丈夫です

4
aqfaridi

もっと多くの値を頭に入れているなら...あなたは試すことができます:

例:配列にCatとDogが存在する場合:

(['Cat','Dog','Bird'] & ['Cat','Dog'] ).size == 2   #or replace 2 with ['Cat','Dog].size

の代わりに:

['Cat','Dog','Bird'].member?('Cat') and ['Cat','Dog','Bird'].include?('Dog')

注:メンバー?そして含める?同じだ。

これで一行で作業ができます!

それが価値があるもののために、 Rubyのドキュメント はこれらの種類の質問のための素晴らしいリソースです。

検索している配列の長さにも注目します。 include?メソッドはO(n)の複雑さで線形検索を実行しますが、これは配列のサイズによっては非常に見苦しくなることがあります。

もしあなたが大規模な(ソートされた)配列を使って作業しているのなら、 二分探索アルゴリズム を書くことを考えます。 O(log n)の場合.

あるいは、Ruby 2.0を使っているのなら、bsearchを利用することができます。

4
davissp14

include?を使用したくない場合は、これも機能します。

['cat','dog','horse'].select{ |x| x == 'dog' }.any?
3
xlembouras

他にも方法があります。

配列が[:edit、:update、:create、:show]であるとします - おそらく、全体の7つの致命的/安らかな罪:)

そしてある文字列から有効なアクションを引っ張るという考えを持ったさらなる玩具 - と言う

私の兄は私に彼のプロフィールを更新してほしいと思います

溶液

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}
3
walt_die
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
3
Rahul Patel

これを MiniTest 単体テストで実行しようとしている場合は、 assert_includes を使用できます。例:

pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog')      # -> passes
assert_includes(pets, 'Zebra')    # -> fails 
2
Jon Schneider

これはどうですか?

['Cat', 'Dog', 'Bird'].index('Dog')
2
ajahongir

インクルードを使用したくない場合は最初に要素を配列でラップしてから、ラップされた要素が配列とラップされた要素の共通部分と等しいかどうかを確認できます。これは、等しいかどうかに基づいてブール値を返します。

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end
0
mgidea

これを行うもう1つの方法があります。

arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'

present = arr.size != (arr - [e]).size
0
Wand Maker
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")
0
Matthew Maurice

Trueまたはfalseだけではなく、値を返したい場合は、

array.find{|x| x == 'Dog'}

リストに存在する場合は 'Dog'を返し、それ以外の場合はnilを返します。

0
gitb