値'Dog'
と配列['Cat', 'Dog', 'Bird']
があります。
ループせずに配列に存在するかどうかを確認する方法はありますか。値が存在するかどうかをチェックする簡単な方法はありますか。
探しているのは include?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
@ campatersonで指摘されているように、v3.1から in?
メソッド がActiveSupport
(Railsの一部)にあります。だからRailsの中、あるいはrequire 'active_support'
なら、次のように書くことができます。
'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false
OTOHさん、Ruby自体にはin
演算子や#in?
メソッドはありません、特に以前に提案されていますが 、特にRuby-coreの一流メンバーである遠藤裕介 によって。
他の人が指摘したように、Enumerable
、Array
、Hash
、Set
を含むすべての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倍速いことを明らかにします(要素が見つからない場合)。
最後の最後の注意:Range
でinclude?
を使うときは細心の注意が必要ですので、 doc を参照し、 cover?
と比較してください。
やってみる
['Cat', 'Dog', 'Bird'].include?('Dog')
Enumerable#include
を使用してください。
a = %w/Cat Dog Bird/
a.include? 'Dog'
あるいは、いくつかのテストが行われた場合、1 (include?
でも)ループを取り除き、O(n)からO( 1)と一緒に:
h = Hash[[a, a].transpose]
h['Dog']
あなたがブロックで確認したい場合は、あなたはそれを試すことができますか?またはすべて?.
%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
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
要素が存在する場合、それらはすべてtrue
ish値を返します。
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クラスとして実装されていますが、それでも基礎となる@hash
のO(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
...
いくつかの回答から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
ループしたくない場合は、配列でそれを行う方法はありません。代わりに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)
これは、これを行うためのもう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のインデックスを返します。
楽しい事実、
*
を使用して、case
式で配列のメンバーシップを確認できます。
case element
when *array
...
else
...
end
When節の小さな*
に注目してください。これは配列のメンバーシップをチェックします。
スプラット演算子の通常の魔法の振る舞いはすべて当てはまるので、たとえばarray
が実際には配列ではなく単一の要素である場合、それはその要素と一致します。
これを実現するには複数の方法があります。それらのいくつかは次のとおりです。
a = [1,2,3,4,5]
2.in? a #=> true
8.in? a #=> false
a.member? 1 #=> true
a.member? 8 #=> false
これはそれが存在することだけでなくそれが何回現れるのもあなたに言うでしょう:
a = ['Cat', 'Dog', 'Bird']
a.count("Dog")
#=> 1
何らかのキーについて複数回チェックする必要がある場合は、arr
をhash
に変換してから、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?
を使ったシングルタイムチェックには大丈夫です
もっと多くの値を頭に入れているなら...あなたは試すことができます:
例:配列に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
を利用することができます。
include?
を使用したくない場合は、これも機能します。
['cat','dog','horse'].select{ |x| x == 'dog' }.any?
他にも方法があります。
配列が[: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}[,|.| |]/}
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
これを MiniTest 単体テストで実行しようとしている場合は、 assert_includes
を使用できます。例:
pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog') # -> passes
assert_includes(pets, 'Zebra') # -> fails
これはどうですか?
['Cat', 'Dog', 'Bird'].index('Dog')
インクルードを使用したくない場合は最初に要素を配列でラップしてから、ラップされた要素が配列とラップされた要素の共通部分と等しいかどうかを確認できます。これは、等しいかどうかに基づいてブール値を返します。
def in_array?(array, item)
item = [item] unless item.is_a?(Array)
item == array & item
end
これを行うもう1つの方法があります。
arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'
present = arr.size != (arr - [e]).size
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")
Trueまたはfalseだけではなく、値を返したい場合は、
array.find{|x| x == 'Dog'}
リストに存在する場合は 'Dog'を返し、それ以外の場合はnilを返します。