如何在Ruby中检查数组中是否存在值

我有一个值'Dog'和一个数组['Cat', 'Dog', 'Bird']

如何在不循环的情况下检查它是否存在于数组中?有没有一种简单的方法来检查值是否存在,仅此而已?

1140045 次浏览

您正在寻找#0

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

使用Enumerable#include

a = %w/Cat Dog Bird/
a.include? 'Dog'

或者,如果完成了考试数量,1你可以摆脱循环(即使include?也有),从O(n)O(1)

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


1.我希望这是显而易见的,但要避免反对意见:是的,对于一些查找,哈希[]和转置操作主导了配置文件,并且每个O(n)本身。

试试看

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

正如@campaterson所指出的,从v3.1开始,ActiveSupport(Rails的一部分)中有一个#0方法。所以在Rails中,或者如果你require 'active_support',你可以写:

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

OTOH,Ruby本身没有in运算符或#in?方法,尽管它之前已经被提出,特别是Yusuke Endoh是ruby-core的顶级成员。

正如其他人所指出的,相反的方法#0存在,对于所有Enumerable,包括ArrayHashSetRange

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

请注意,如果您的数组中有许多值,它们将一个接一个地检查(即O(n)),而对哈希的查找将是恒定的时间(即O(1))。因此,如果您的数组是恒定的,例如,使用设置是个好主意。例如:

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?时要小心,有微妙之处,所以参考该医生并与#2进行比较…

如果你想按块检查,你可以尝试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

有关更多信息,请参阅枚举

我的灵感来自“评估数组是否在ruby中有任何项目

还有另一条路可以绕过去。

假设数组是[ :edit, :update, :create, :show ],也许是整个七宗罪

并进一步玩弄的想法,这是来自某个字符串的有效操作:

"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}[,|.| |]/}

有几个答案建议Array#include?,但有一个重要的警告:查看源代码,即使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;}

在不循环的情况下测试单词存在的方法是为您的数组构建一个trie。那里有许多trie实现(谷歌“ruby trie”)。我将在这个例子中使用rambling-trie

a = %w/cat dog bird/
require 'rambling-trie' # if necessary, gem install rambling-trietrie = Rambling::Trie.create { |trie| a.each do |e| trie << e end }

现在我们准备好在O(log n)时间内测试数组中各种单词的存在,而无需循环,使用次线性Trie#include?,具有与Array#include?相同的语法简单性:

trie.include? 'bird' #=> truetrie.include? 'duck' #=> false

为了它的价值,rubydocs是这些问题的惊人资源。

我还会注意你正在搜索的数组的长度。include?方法将运行复杂度为O(n)的线性搜索,这可能会变得非常丑陋,具体取决于数组的大小。

如果你正在处理一个大的(排序的)数组,我会考虑写一个二分搜索算法,它不应该太难,最坏的情况是O(log n)。

或者,如果您使用的是Ruby 2.0,则可以利用bsearch

这是另一种方法:使用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”的数组中第一个单词的索引。

如果我们不想使用include?,这也有效:

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

你可以试试:

示例:如果数组中存在猫和狗:

(['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?是相同的。

这可以在一条线上完成工作!

这不仅会告诉你它存在,还会告诉你它出现了多少次:

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

如果您不想使用include?,您可以首先将元素包装在数组中,然后检查包装元素是否等于数组和包装元素的交集。这将返回一个基于相等的布尔值。

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

如果您不想循环,则无法使用数组进行循环。您应该改用Set。

require 'set's = Set.new100.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

缺点是Sets和Hash键只能包含唯一的项目,如果您添加了很多项目,Ruby将不得不在特定数量的项目之后重新散列整个项目,以构建适合更大键空间的新映射。有关更多信息,我建议您观看“Mountain tainWest 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        realarray  7.020000   0.000000   7.020000 (  7.031525)set    0.010000   0.000000   0.010000 (  0.004816)

这条路怎么样?

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

这里有另一种方法来做到这一点:

arr = ['Cat', 'Dog', 'Bird']e = 'Dog'
present = arr.size != (arr - [e]).size

有多种方法可以做到这一点。其中一些如下:

a = [1,2,3,4,5]
2.in? a  #=> true
8.in? a #=> false
a.member? 1 #=> true
a.member? 8 #=> false
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}=> "Dog"!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?=> true
array = [ 'Cat', 'Dog', 'Bird' ]array.include?("Dog")

Ruby有11种方法来查找数组中的元素。

首选的是include?,或者,对于重复访问,创建一个Set,然后调用include?member?

以下是他们所有人:

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

如果元素存在,它们都返回trueish值。

include?是首选方法。它在内部使用C语言for循环,当元素与内部rb_equal_opt/rb_equal函数匹配时中断。除非您创建一个用于重复成员检查的Set,否则它不会变得更有效。

VALUErb_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 VALUEmember_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 VALUEenum_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] = truebreakendmemo[1]end

include?member?都具有O(n)时间复杂度,因为它们都在数组中搜索期望值的第一次出现。

我们可以使用Set来获得O(1)访问时间,代价是必须首先创建数组的哈希表示。如果您重复检查同一数组的成员资格,这种初始投资可以很快得到回报。Set不是在C中实现的,而是作为普通的Ruby类,底层@hash的O(1)访问时间仍然使这是值得的。

下面是Set类的实现:

module Enumerabledef to_set(klass = Set, *args, &block)klass.new(self, *args, &block)endend
class Setdef initialize(enum = nil, &block) # :yields: o@hash ||= Hash.newenum.nil? and returnif blockdo_with_enum(enum) { |o| add(block[o]) }elsemerge(enum)endend
def merge(enum)if enum.instance_of?(self.class)@hash.update(enum.instance_variable_get(:@hash))elsedo_with_enum(enum) { |o| add(o) }endselfend
def add(o)@hash[o] = trueselfend
def include?(o)@hash.include?(o)endalias member? include?
...end

如您所见,Set类只是创建一个内部@hash实例,将所有对象映射到true,然后使用Hash#include?检查成员资格,这是在Hash类中使用O(1)访问时间实现的。

我不会讨论其他七种方法,因为它们的效率都较低。

实际上,除了上面列出的11个方法之外,还有更多复杂度为O(n)的方法,但我决定不列出它们,因为它们扫描整个数组,而不是在第一次匹配时中断。

不要使用这些:

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

有趣的事实,

您可以使用*来检查case表达式中的数组成员资格。

case elementwhen *array...else...end

注意这时候子句中的小*,它检查数组中的成员资格。

spat运算符的所有常见的魔法行为都适用,例如,如果array实际上不是一个数组,而是一个元素,它将匹配该元素。

如果您需要检查任何键的倍数,请将arr转换为hash,然后签入O(1)

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

Hash#has_key?

Parameter              Hash#has_key?                 Array#include
Time Complexity         O(1) operation                O(n) operation
Access Type             Accesses Hash[key] if it      Iterates through each elementreturns any value then        of the array till ittrue is returned to the       finds the value in ArrayHash#has_key? callcall

对于单次检查,使用include?就可以了

它有很多方法可以在任何数组中找到元素,但最简单的方法是“in?”方法。

example:arr = [1,2,3,4]number = 1puts "yes #{number} is present in arr" if number.in? arr

如果您尝试在MiniTest单元测试中执行此操作,则可以使用#0。示例:

pets = ['Cat', 'Dog', 'Bird']assert_includes(pets, 'Dog')      # -> passesassert_includes(pets, 'Zebra')    # -> fails

如果您想返回的值不仅仅是true或false,请使用

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

如果列表中存在,则返回'Dog',否则返回nil。

我总是觉得运行一些基准来查看做某事的各种方式的相对速度很有趣。

在开始、中间或结束处查找数组元素将影响任何线性搜索,但几乎不会影响对Set的搜索。

将数组转换为Set将导致流转时长,因此从数组创建Set一次,或者从一开始就使用Set。

这是基准代码:

# frozen_string_literal: true
require 'fruity'require 'set'
ARRAY = (1..20_000).to_aSET = 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) >= 0end
def array_find_index(elem)ARRAY.find_index(elem) >= 0end
def array_index_each(elem)ARRAY.index { |each| each == elem } >= 0end
def array_find_index_each(elem)ARRAY.find_index { |each| each == elem } >= 0end
def array_any_each(elem)ARRAY.any? { |each| each == elem }end
def array_find_each(elem)ARRAY.find { |each| each == elem } != nilend
def array_detect_each(elem)ARRAY.detect { |each| each == elem } != nilend
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)     }endend
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)    }endend

在我的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,除非我能保证第一个元素是我想要的,这不太可能。将元素插入哈希中会有一些开销,但是搜索时间要快得多,我认为这不应该成为考虑因素。同样,如果你需要搜索它,不要使用数组,使用Set。(或哈希。)

Array越小,Array方法运行得越快,但它们仍然跟不上,尽管在小数组中差异可能很小。

“第一”、“中间”和“最后”反映了对要搜索的元素的ARRAY使用firstsize / 2last。搜索ARRAYSET变量时将使用该元素。

对与> 0相比的方法进行了轻微的更改,因为对于index类型测试,测试应该是>= 0

有关果味及其方法论的更多信息,请参阅其自述文件

支票已存在

使用include?

示例:

arr = [1, 2, 3]arr.include?(1) -> truearr.include?(4) -> false

支票不存在

使用exclude?

示例:

arr = %w(vietnam china japan)arr.exclude?('usa') -> truearr.exclude?('china') -> false

试试下面

(['Cat', 'Dog', 'Bird'] & ['Dog']).any?