値'Dog'
と配列があり['Cat', 'Dog', 'Bird']
ます。
ループせずに配列に存在するかどうかを確認するにはどうすればよいですか?値が存在するかどうかを確認する簡単な方法はありますか?
値'Dog'
と配列があり['Cat', 'Dog', 'Bird']
ます。
ループせずに配列に存在するかどうかを確認するにはどうすればよいですか?値が存在するかどうかを確認する簡単な方法はありますか?
回答:
あなたが探していますinclude?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
%w(Cat Dog Bird).include? 'Dog'
#include?
まだループを実行していることに注意してください。ただし、コーダーはループを明示的に記述する必要がありません。ループせずに本当にタスクを実行する答えを追加しました。
あるin?
方法は、中ActiveSupport
@campatersonにより指摘したように、V3.1以降(レールの一部)。Rails内で、またはの場合はrequire 'active_support'
、次のように記述できます。
'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false
OTOH、Ruby コアのトップノッチメンバーである遠藤裕介によって以前に提案されたにもかかわらず、Ruby自体にはin
演算子または#in?
メソッドはありません。
他の人が指摘したように、逆の方法がinclude?
存在し、全てのためEnumerable
などのArray
、Hash
、Set
、Range
。
['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
簡単なテストを呼び出すことが明らかになったinclude?
10の要素にすることはSet
速く同等の上でそれを呼び出すよりも3.5倍程度であるArray
(要素が見つからない場合)。
最終クロージングノートでは:使用しているとき警戒するinclude?
上でRange
、これを参照してください、微妙な点があるドキュメントとと比較しますcover?
...
#in?
そのコアには含まれていませんが、Railsを使用している場合は使用できます。api.rubyonrails.org/classes/Object.html#method-i-in-3F(これはRubyであり、Railsの質問ではありませんが、Railsでの使用#in?
を検討している人には役立つかもしれません。Railsに追加されたようです3.1 apidock.com/rails/Object/in%3F
試す
['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']
ブロックごとにチェックしたい場合は、any?
またはを試してくださいall?
。
%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
詳細については、Enumerableを参照してください。
私のインスピレーションは、「配列にルビーのアイテムがあるかどうかを評価する」
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?
推奨される方法です。for
要素が内部rb_equal_opt/rb_equal
関数と一致すると中断する内部でC言語ループを使用します。メンバーシップチェックを繰り返すためのセットを作成しない限り、効率を上げることはできません。
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
両方とも、配列が最初に出現する期待値を探すため、O(n)時間の複雑さinclude?
をmember?
持っています。
Setを使用してO(1)アクセス時間を取得できますが、最初に配列のハッシュ表現を作成する必要があります。同じアレイのメンバーシップを繰り返し確認する場合、この初期投資はすぐに報われることがあります。Set
Cには実装されていませんが、単純なRubyクラスとして、基礎となるO(1)アクセス時間@hash
がこれに値します。
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
インスタンスを作成し、すべてのオブジェクトをマッピングし、HashクラスのO(1)アクセス時間で実装されているtrue
メンバーシップをチェックしますHash#include?
。
他の7つの方法は効率が悪いため、説明しません。
上記の11よりも複雑なO(n)のメソッドは実際にはさらに多くありますが、最初の一致でブレークするのではなく配列全体をスキャンするため、リストしないことにしました。
これらは使用しないでください。
# bad examples
array.grep(element).any?
array.select { |each| each == element }.size > 0
...
11
あなたが列挙した方法について質問させてください。1つ目は、index
and find_index
(またはfind
and detect
)を別々のメソッドとして数えることはほとんどできません。これらは同じメソッドの単なる異なる名前であるためです。第二に、で終わるすべての表現> 0
が間違っています。これは間違いだったと思います。(続き)
arr.index(e)
、たとえば、0
ifを返しますarr[0] == e
。arr.index(e)
返品がないnil
場合e
は返品を呼び戻します。index
1が探している場合は、しかし、使用することはできませんnil
の中でarr
。(rindex
リストされていないと同じ問題。)配列をセットに変換してから、setメソッドを使用するのは少し難しいです。次に、ハッシュ(配列のキーと任意の値を使用)に変換してから、ハッシュメソッドを使用しないのはなぜですか。セットへの変換で問題がなくても、など、使用できる他のセットメソッドがあります!arr.to_set.add?(e)
。(続き)
arr.count(e) > 0
、arr != arr.dup.delete(e)
、arr != arr - [e]
とarr & [e] == [e]
。select
とを採用することもできreject
ます。
いくつかの回答が示唆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;
}
ループなしで単語の存在をテストする方法は、配列のトライを作成することです。トライの実装は数多くあります(google "ruby trie")。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 }
そして今、我々はで、それをループせずに、アレイ内のさまざまな言葉の存在をテストする準備ができているO(log n)
と同じ構文シンプルで、時間Array#include?
サブリニアを使用して、Trie#include?
:
trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
a.each do ... end
Set#include?
、効率性を懸念する人々のためにすでに述べた回答よりも注意してください。文字列の代わりに記号を使用すると、それはO(1)の平均的なケースになります(文字列を使用する場合、ハッシュを計算するだけでO(n)になります(nは文字列の長さ))。または、サードパーティのライブラリを使用したい場合は、O(1)の最悪のケースである完全なハッシュを使用できます。
Set
そのメンバーにインデックスをSet#include?
付けるためにハッシュを使用します。したがって、実際には、十分に分散されている場合はO(1)Set
(より具体的にはハッシュの場合はO(input-size)、複雑な場合はO(log(n / bucket-number)))検索)
ループしたくない場合は、配列でそれを行う方法はありません。代わりに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
欠点は、セットキーとハッシュキーに一意のアイテムのみを含めることができ、多数のアイテムを追加した場合、Rubyは特定の数のアイテムの後ですべてを再ハッシュして、より大きなキースペースに適した新しいマップを構築する必要があることです。詳しくは、「MountainWest RubyConf 2014-Big O in a Homemade Hash by Nathan Long」をご覧になることをお勧めします。
ここにベンチマークがあります:
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)
include?
最初のヒットで停止しませんか?
include?
最初のヒットで停止しますが、そのヒットがリストの最後にある場合...ストレージ用に配列に依存するソリューションは、リストが大きくなるにつれて、特に要素の最後に要素を見つける必要がある場合に、パフォーマンスが低下します。リスト。ハッシュとセットにはその問題はなく、順序付きリストと二分探索もありません。
これは、これを行う別の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」を含む配列の最初の単語のインデックスを返します。
index
それでも配列を反復処理し、要素の値を返すだけです。
楽しい事実、
を使用*
して、case
式の配列メンバーシップを確認できます。
case element
when *array
...
else
...
end
*
when句の小さな部分に注意してください。これにより、配列のメンバーシップがチェックされます。
splat演算子の通常のすべての魔法の動作が適用されます。たとえば、array
実際には配列ではなく単一の要素である場合、その要素と一致します。
when
ため、可能な限り最後のチェックで使用するので、他のより高速なチェックですぐに取り除かれます。
キーを何回もチェックする必要がある場合は、に変換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
Hash#has_keyのパフォーマンス?対Array#include?
パラメータHash#has_key?Array#include 時間の複雑さO(1)演算O(n)演算 アクセスタイプが各要素を反復処理する場合、ハッシュ[キー]にアクセスします それまでの配列の値を返します 配列内の値を検索するとtrueが返されます Hash#has_key?コール コール
一度のチェックで使用してinclude?
も問題ありません
価値のあることとして、Rubyのドキュメントは、この種の質問に対するすばらしいリソースです。
また、検索している配列の長さに注意します。このinclude?
メソッドは、配列のサイズによってはかなり醜くなる可能性のあるO(n)の複雑さで線形検索を実行します。
大規模な(並べ替えられた)配列を使用している場合は、難しくないはずのO(log n)の最悪の場合のバイナリ検索アルゴリズムを作成することを検討します。
または、Ruby 2.0を使用している場合は、を利用できますbsearch
。
<=>
ですが、常にそうであるとは限りません。たとえば、配列の要素がハッシュだったとします。
あなたが試すことができます:
例:猫と犬が配列に存在する場合:
(['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')
注:member?
とinclude?
同じです。
これは1行で作業できます。
これを使用したくない場合include?
も機能します:
['cat','dog','horse'].select{ |x| x == 'dog' }.any?
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
['Cat', nil, 'Dog'].detect { |x| x == nil } #=> nil
。たnil
見つかりましたか?
MiniTest単体テストでこれを行う場合は、を使用できますassert_includes
。例:
pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog') # -> passes
assert_includes(pets, 'Zebra') # -> fails
これには別の方法があります。
配列が[ :edit, :update, :create, :show ]
であると仮定します。おそらく、7つすべての致命的/静穏な罪です。
そして、いくつかの文字列から有効なアクションを引き出すというアイデアをさらに楽しみます:
"my brother would like me to update his profile"
次に:
[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}
/[,|.| |]#{v.to_s}[,|.| |]/
、あなたが「カンマ、ピリオド、スペース、または何もないもののいずれかに囲まれたアクションの名前」を見つけたかったと思いますが、いくつかの微妙なバグがあります。"|update|"
戻る[:update]
と戻る"update"
だろう[]
。文字クラス([...]
)では、パイプ(|
)を使用して文字を区切りません。それらをグループ((...)
)に変更しても、空の文字とは一致しません。だからあなたがおそらく望んだ正規表現は/(,|\.| |^)#{v.to_s}(,|\.| |$)/
/[,. ]/
これを行うもう1つの方法を次に示します。
arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'
present = arr.size != (arr - [e]).size
arr != arr - [e]
。arr & [e] == [e]
同じ線に沿った別の方法です。
何かを行うさまざまな方法の相対的な速度を確認するために、いくつかのベンチマークを実行することは常に興味深いことです。
開始、中間、または終了で配列要素を検索すると、線形検索に影響しますが、セットに対する検索にはほとんど影響しません。
配列をセットに変換すると、処理時間に影響が出るため、配列からセットを一度作成するか、最初からセットから始めます。
ベンチマークコードは次のとおりです。
# frozen_string_literal: true
require 'fruity'
require 'set'
ARRAY = (1..20_000).to_a
SET = ARRAY.to_set
DIVIDER = '-' * 20
def array_include?(elem)
ARRAY.include?(elem)
end
def array_member?(elem)
ARRAY.member?(elem)
end
def array_index(elem)
ARRAY.index(elem) >= 0
end
def array_find_index(elem)
ARRAY.find_index(elem) >= 0
end
def array_index_each(elem)
ARRAY.index { |each| each == elem } >= 0
end
def array_find_index_each(elem)
ARRAY.find_index { |each| each == elem } >= 0
end
def array_any_each(elem)
ARRAY.any? { |each| each == elem }
end
def array_find_each(elem)
ARRAY.find { |each| each == elem } != nil
end
def array_detect_each(elem)
ARRAY.detect { |each| each == elem } != nil
end
def set_include?(elem)
SET.include?(elem)
end
def set_member?(elem)
SET.member?(elem)
end
puts format('Ruby v.%s', RUBY_VERSION)
{
'First' => ARRAY.first,
'Middle' => (ARRAY.size / 2).to_i,
'Last' => ARRAY.last
}.each do |k, element|
puts DIVIDER, k, DIVIDER
compare do
_array_include? { array_include?(element) }
_array_member? { array_member?(element) }
_array_index { array_index(element) }
_array_find_index { array_find_index(element) }
_array_index_each { array_index_each(element) }
_array_find_index_each { array_find_index_each(element) }
_array_any_each { array_any_each(element) }
_array_find_each { array_find_each(element) }
_array_detect_each { array_detect_each(element) }
end
end
puts '', DIVIDER, 'Sets vs. Array.include?', DIVIDER
{
'First' => ARRAY.first,
'Middle' => (ARRAY.size / 2).to_i,
'Last' => ARRAY.last
}.each do |k, element|
puts DIVIDER, k, DIVIDER
compare do
_array_include? { array_include?(element) }
_set_include? { set_include?(element) }
_set_member? { set_member?(element) }
end
end
私のMac OSラップトップで実行すると、次のようになります。
Ruby v.2.7.0
--------------------
First
--------------------
Running each test 65536 times. Test will take about 5 seconds.
_array_include? is similar to _array_index
_array_index is similar to _array_find_index
_array_find_index is faster than _array_any_each by 2x ± 1.0
_array_any_each is similar to _array_index_each
_array_index_each is similar to _array_find_index_each
_array_find_index_each is faster than _array_member? by 4x ± 1.0
_array_member? is faster than _array_detect_each by 2x ± 1.0
_array_detect_each is similar to _array_find_each
--------------------
Middle
--------------------
Running each test 32 times. Test will take about 2 seconds.
_array_include? is similar to _array_find_index
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 2x ± 0.1
_array_member? is faster than _array_index_each by 2x ± 0.1
_array_index_each is similar to _array_find_index_each
_array_find_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each
--------------------
Last
--------------------
Running each test 16 times. Test will take about 2 seconds.
_array_include? is faster than _array_find_index by 10.000000000000009% ± 10.0%
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 3x ± 0.1
_array_member? is faster than _array_find_index_each by 2x ± 0.1
_array_find_index_each is similar to _array_index_each
_array_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each
--------------------
Sets vs. Array.include?
--------------------
--------------------
First
--------------------
Running each test 65536 times. Test will take about 1 second.
_array_include? is similar to _set_include?
_set_include? is similar to _set_member?
--------------------
Middle
--------------------
Running each test 65536 times. Test will take about 2 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 1400x ± 1000.0
--------------------
Last
--------------------
Running each test 65536 times. Test will take about 4 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 3000x ± 1000.0
基本的に、最初の要素が必要な要素であることを保証できない場合を除いて、包含を検索する場合、結果はすべてにSetを使用するように指示しますが、そうではありません。要素をハッシュに挿入するとオーバーヘッドが多少発生しますが、検索時間が非常に速くなるので、これを考慮する必要はないと思います。繰り返しますが、検索する必要がある場合は、配列を使用せず、セットを使用してください。(またはハッシュ。)
Arrayが小さいほど、Arrayメソッドの実行速度は速くなりますが、それでも追いつくことはできませんが、小さな配列では違いはわずかです。
"First"、 "Middle"、および "Last"はfirst
、検索対象の要素に対する、size / 2
およびlast
forの使用を反映していますARRAY
。その要素はARRAY
およびSET
変数を検索するときに使用されます。
> 0
テストは型テスト>= 0
用でなければならないため、比較対象のメソッドに小さな変更が加えられましたindex
。
Fruityとその方法論の詳細については、READMEを参照してください。