首页 文章

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

提问于
浏览
1147

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

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

22 回答

  • 5

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

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

    这样怎么样?

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

    对于它的 Value ,Ruby docs是这类问题的绝佳资源 .

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

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

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

  • 29

    这是另外一种方法:

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

    如果您需要检查任何键的倍数,请将 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
    

    Performance of Hash#has_key? versus 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? 很好

  • 4

    如果您不想循环,则无法使用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)
    
  • 1720

    尝试

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

    有趣的事实,

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

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

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

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

  • 2

    使用 Enumerable#include

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

    或者,如果完成了许多测试,1你可以摆脱循环(甚至 include? )并从O(n)转到O(1):

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

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

  • 157

    几个答案建议 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
    
  • 24

    @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的顶级成员 .

    正如其他人所指出的,对于所有 Enumerable ,包括 ArrayHashSetRange ,反向方法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
    

    quick test显示在10个元素 Set 上调用 include? 比在等效的 Array 上调用它快3.5倍(如果找不到该元素) .

    最后的结束注释:在 Range 上使用 include? 时要小心,有细微之处,所以请参考the doc并与cover?进行比较......

  • 7
    array = [ 'Cat', 'Dog', 'Bird' ]
    array.include?("Dog")
    
  • 0

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

    %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

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

  • 212

    如果你有更多的想法 Value ......你可以尝试:

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

    注意:会员?并包括?是相同的 .

    This can do the work in one line!

  • 8

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

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

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

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

    这不仅会告诉您它存在,还会告诉您它出现的次数:

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

    还有另一种方式!

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

    还有玩具的想法是从一些字符串中拉出一个有效的动作 - 比如说

    我哥哥希望我更新他的 Profiles

    解决方案

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

    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类,底层 @hashO(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#include? 检查成员资格,这是在 Hash 类中使用 O(1) 访问时间实现的 .

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

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

    不要使用这些,

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

    你在找include?

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

相关问题