我有一个值“狗”和一个数组[“猫”、“狗”、“鸟”]。
如何在不循环的情况下检查数组中是否存在它?是否有一种简单的方法来检查该值是否存在?
我有一个值“狗”和一个数组[“猫”、“狗”、“鸟”]。
如何在不循环的情况下检查数组中是否存在它?是否有一种简单的方法来检查该值是否存在?
当前回答
还有另一种方法。
假设数组是[: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?
例子:
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
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
...
我总是觉得运行一些基准测试来查看各种方法的相对速度很有趣。
在开始、中间或结束处查找数组元素将影响任何线性搜索,但几乎不会影响对集合的搜索。
将一个数组转换为一个集合会导致处理时间的减少,所以从一个数组创建一次集合,或者从一开始就创建一个集合。
以下是基准代码:
# 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,除非我能保证第一个元素是我想要的,这不太可能。在哈希表中插入元素时会有一些开销,但搜索速度要快得多,我认为这不应该是一个考虑因素。同样,如果您需要搜索它,请不要使用数组,而是使用集合。(或哈希。)
Array越小,Array方法运行得越快,但它们仍然无法跟上,尽管在小数组中,差异可能很小。
“First”、“Middle”和“Last”反映了所搜索元素在ARRAY中使用First、size/2和Last。搜索ARRAY和SET变量时将使用该元素。
对与>0进行比较的方法进行了轻微更改,因为索引类型测试的测试应为>=0。
有关Fruity及其方法的更多信息,请参见其README。
值得一提的是,Ruby文档是解决此类问题的绝佳资源。
我还要注意您正在搜索的数组的长度。包括?方法将运行一个复杂度为O(n)的线性搜索,这可能会变得非常难看,这取决于数组的大小。
如果您使用的是一个大的(排序的)数组,我会考虑编写一个二进制搜索算法,它应该不会太难,而且最坏的情况是O(logn)。
或者如果您使用的是Ruby 2.0,您可以利用bsarch。
如果要在MiniTest单元测试中执行此操作,可以使用assert_includes。例子:
pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog') # -> passes
assert_includes(pets, 'Zebra') # -> fails