我有一个值“狗”和一个数组[“猫”、“狗”、“鸟”]。
如何在不循环的情况下检查数组中是否存在它?是否有一种简单的方法来检查该值是否存在?
我有一个值“狗”和一个数组[“猫”、“狗”、“鸟”]。
如何在不循环的情况下检查数组中是否存在它?是否有一种简单的方法来检查该值是否存在?
当前回答
Ruby有十一种方法来查找数组中的元素。
首选项包括?或者,对于重复访问,创建一个Set,然后调用include?或成员?。
以下是所有这些:
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
如果元素存在,它们都会返回真实值。
包括是优选的方法。它在内部使用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;
}
成员未在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)时间复杂度。
我们可以使用Set来获得O(1)访问时间,代价是必须首先创建数组的Hash表示。如果您重复检查同一阵列的成员资格,那么最初的投资可以很快得到回报。Set没有在C中实现,而是作为一个普通的Ruby类实现,但底层@hash的O(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)访问时间实现。
我不会讨论其他七种方法,因为它们都效率较低。
实际上,除了上面列出的11种方法之外,还有更多的方法具有O(n)复杂性,但我决定不列出它们,因为它们扫描整个阵列,而不是在第一次匹配时中断。
不要使用这些:
# bad examples
array.grep(element).any?
array.select { |each| each == element }.size > 0
...
其他回答
还有另一种方法。
假设数组是[: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}[,|.| |]/}
如果我们不想使用include?这也适用于:
['cat','dog','horse'].select{ |x| x == 'dog' }.any?
在下面尝试
(['Cat', 'Dog', 'Bird'] & ['Dog']).any?
有几个答案建议阵列#包括?,但有一个重要的警告:查看源代码,甚至是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”)。我将在本例中使用随机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
检查是否存在
使用include?
例子:
arr = [1, 2, 3]
arr.include?(1) -> true
arr.include?(4) -> false
支票不存在
使用排除?
例子:
arr = %w(vietnam china japan)
arr.exclude?('usa') -> true
arr.exclude?('china') -> false