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

Check if a value exists in an array in Ruby

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

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


你在找include?

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


正如@campaterson指出的那样,自v3.1以来,EDOCX1(轨道的一部分)中有一个in?方法。因此,在Rails中,或者如果您使用cx1(5),您可以编写:

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

Otoh,Ruby本身没有in操作符或#in?方法,尽管之前已经提出过,特别是Ruby核心的顶级缺口成员Yusuke Endoh。

如其他人所指出的,对于所有的Enumerables,包括ArrayHashSetRange,都存在反向方法include?

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

请注意,如果您的数组中有许多值,则将逐个检查这些值(即O(n)),而对哈希的查找将是恒定时间(即O(1))。例如,如果数组是常量,那么最好使用集合。例如:

1
2
3
4
5
6
7
8
9
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?时要小心,有一些微妙之处,请参阅文档并与cover?进行比较。


尝试

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


使用Enumerable#include

1
2
3
a = %w/Cat Dog Bird/

a.include? 'Dog'

或者,如果完成了许多测试,1您可以去掉循环(甚至是include?的循环),并使用以下方法从O(n)转到O(1):

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

1。我希望这是显而易见的,但为了避免反对意见:是的,对于几个查找,hash[]和transmose操作控制了配置文件,并且每个操作本身都是O(N)。


如果你想一个街区检查,你可以尝试任何?还是全部?.

1
2
3
%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?

这些都是,

1
2
3
4
5
6
7
8
9
10
11
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函数匹配时,循环中断。除非为重复的成员资格检查创建一个集合,否则它不会变得更高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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模块中的未优化的实现,该模块逐字枚举所有元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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代码,这是关于以下内容的

1
2
3
4
5
6
7
8
9
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类的实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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)访问时间在Hash类中实现的。

我不会讨论其他7种方法,因为它们都不那么有效。

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

别用这些,

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


有几个答案表明Array#include?,但有一个重要的警告:从源头上看,即使Array#include?也会执行循环:

1
2
3
4
5
6
7
8
9
10
11
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实现(google"ruby trie")。在这个例子中,我将使用rambling-trie

1
2
3
4
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)时间内,使用亚线性Trie#include?来测试数组中各种单词的存在,其语法简单性与Array#include?相同:

1
2
trie.include? 'bird' #=> true
trie.include? 'duck' #=> false


如果不想循环,就不能用数组来实现。你应该用一套来代替。

1
2
3
4
5
6
7
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不需要通过集合循环来查找项,因为顾名思义,它生成键的散列并创建内存映射,以便每个散列都指向内存中的某个点。上一个示例使用哈希完成:

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

缺点是集合键和散列键只能包含唯一的项,如果您添加了很多项,Ruby将不得不在特定数量的项之后重新刷新整个项,以构建适合更大的键空间的新映射。关于这方面的更多信息,我建议您观看2014年的西山红宝石节——内森·朗自制土豆泥中的大O。

以下是一个基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

结果是:

1
2
3
      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)


这是另一种方法:使用数组索引方法。

它返回数组中元素第一次出现的索引。

例子:

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

index()也可以接受一个块

例如

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

在这里,返回数组中包含字母"o"的第一个单词的索引。


有趣的事实,

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

1
2
3
4
5
6
case element
when *array
  ...
else
  ...
end

注意when子句中的小*,它检查数组中的成员身份。

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


实现这一点有多种方法。其中一些如下:

1
2
3
4
5
6
7
8
9
a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false


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

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


如果你有更多的价值观…你可以试试:

示例:如果数组中存在cat和dog:

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

而不是:

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

注意:会员?包括在内?都一样。

这可以一行完成工作!


不管它有什么价值,Ruby文档对于这些问题来说都是一个令人惊奇的资源。

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

如果你使用的是一个大的(排序的)数组,我会考虑编写一个二进制搜索算法,它不应该太难,并且有一个最坏的情况O(log n)。

或者,如果您使用的是Ruby2.0,那么您可以利用bsearch


如果需要对任何键进行多次检查,请将arr转换为Hash,然后现在签入o(1)

1
2
3
4
5
6
7
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的性能有u键吗?相对于阵列包括?

1
2
3
4
5
6
7
8
9
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?是可以的。


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

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


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


还有另一条路!

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

更进一步的玩弄从某根绳子上拉出一个有效动作的想法-比如说

my brother would like me to update his profile

解决方案

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


这边怎么样?

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


如果您试图在小型测试单元测试中进行此操作,则可以使用assert_includes。例子:

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

如果要返回值而不仅仅是true或false,请使用

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

如果列表中存在"dog",则返回"dog",否则返回nil。


还有一种方法可以做到这一点:

1
2
3
4
arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'

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


1
2
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")

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

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


it has many ways to find a element in any array but the simplest way is 'in ?' method.

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