检查Ruby中的数组中是否存在值

时间:2009-12-31 17:49:03

标签: ruby arrays

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

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

25 个答案:

答案 0 :(得分:1802)

您正在寻找include?

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

答案 1 :(得分:227)

由@campaterson指出,自{v3.1}以来ActiveSupport(部分Rails)中有一个in? method。所以在Rails中,或者如果你require 'active_support',你可以写:

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

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

正如其他人所指出的,对于包括EnumerableArrayHash,{{1}的所有Set,存在相反的方法include? }:

Range

请注意,如果数组中有许多值,它们将一个接一个地检查(即['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false ),而哈希的查找将是恒定时间(即O(n)) 。因此,例如,如果数组是常量,则最好使用Set。 E.g:

O(1)

quick test显示,在10个元素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?比在等效Set上调用它快3.5倍(如果找不到该元素)

最后结束语:在Array上使用include?时要小心,有细微之处,请参阅the doc并与cover?进行比较...

答案 2 :(得分:159)

尝试

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

答案 3 :(得分:48)

使用Enumerable#include

a = %w/Cat Dog Bird/

a.include? 'Dog'

或者,如果进行了多次测试, 1 你可以摆脱循环(甚至是include?)并从 O(n) to O(1) with:

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

<小时/> 1。我希望这很明显,但要避免反对意见:是的,只需几次查找,Hash []和转置操作支配配置文件,并且每个 O(n)本身。 < p>

答案 4 :(得分:44)

如果你想通过一个街区来检查,你可以试试吗?还是全部?

%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

答案 5 :(得分:31)

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类,基础O(1)的{​​{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,然后使用true检查成员身份,Hash#include?是用{O(1)实现的1}} Hash类中的访问时间。

我不会讨论其他7种方法,因为它们效率都不高。

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

不要使用这些,

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

答案 6 :(得分:29)

有几个答案提示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 。那里有很多特里实现(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

答案 7 :(得分:16)

如果您不想循环,则无法使用Arrays进行循环。你应该使用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不需要遍历集合来查找项目,因为顾名思义,它会生成键的哈希值并创建内存映射,以便每个哈希点指向某个点在记忆中。前面的例子是用Hash完成的:

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)

答案 8 :(得分:15)

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

答案 9 :(得分:9)

有趣的事实,

您可以使用*检查case表达式中的数组成员身份。

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

注意when子句中的小*,这将检查数组中的成员资格。

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

答案 10 :(得分:8)

有多种方法可以实现这一目标。其中一些如下:

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

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false

答案 11 :(得分:5)

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

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

答案 12 :(得分:4)

如果你有更多的价值......你可以尝试:

示例:如果数组中存在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')

注意:会员?并包括?是一样的。

这可以在一行中完成工作!

答案 13 :(得分:4)

对于它的价值,Ruby docs是这类问题的绝佳资源。

我还会记下你正在搜索的数组的长度。 include?方法将运行具有O(n)复杂度的线性搜索,根据数组的大小,这可能会非常难看。

如果你正在处理一个大的(有序的)数组,我会考虑编写一个binary search algorithm,它不应该太难,并且最坏的情况是O(log n)。

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

答案 14 :(得分:4)

如果您需要检查任意密钥的倍数,请将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?的表现

Parameter              Hash#has_key?                 Array#include 

Time Complexity         O(1) operation                O(n) operation 

Access Type             Accesses Hash[key] if it      Iterates through each element
                        returns any value then        of the array till it
                        true is returned to the       finds the value in Array
                        Hash#has_key? call
                        call    

使用include?进行单次检查很好

答案 15 :(得分:3)

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

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

答案 16 :(得分:3)

['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true

答案 17 :(得分:3)

还有另一种方式!

假设数组是[:edit,:update,:create,:show] - 也许整个七个致命/宁静的罪恶:)

进一步玩具的想法是从某些字符串中拉出一个有效的动作 - 说

  

我的兄弟希望我更新他的个人资料

解决方案

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}

答案 18 :(得分:2)

这样怎么样?

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

答案 19 :(得分:2)

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

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

答案 20 :(得分:0)

运行一些基准测试以查看各种操作方式的相对速度总是很有趣。

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

将数组转换为Set将造成处理时间的损失,因此,请从数组创建一次Set,或者从一开始就从Set开始。

这是基准代码:

# 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,除非可以保证第一个元素是我想要的元素,这不太可能。将元素插入到哈希中时会产生一些开销,但是搜索时间是如此之快,我认为这不是要考虑的问题。同样,如果您需要搜索它,请不要使用数组,而要使用Set。 (或哈希)。

Array越小,Array方法运行的速度就越快,但是它们仍然不会跟上,尽管在较小的数组中差异可能很小。

“第一”,“中间”和“最后”反映了first对要搜索的元素的使用size / 2lastARRAY。搜索ARRAYSET变量时将使用该元素。

> 0比较的方法进行了较小的更改,因为对于>= 0类型的测试,测试应为index

有关Fruity及其方法的更多信息,请参见其README

答案 21 :(得分:0)

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

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

如果列表中存在它,它将返回“ Dog”,否则返回nil。

答案 22 :(得分:0)

以下是另一种方法:

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

present = arr.size != (arr - [e]).size

答案 23 :(得分:0)

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

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

答案 24 :(得分:-1)

  

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

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