我有一个值'Dog'
和一个数组['Cat', 'Dog', 'Bird']
。
如何检查它是否存在于数组中而不遍历它?有没有简单的方法检查值是否存在,仅此而已?
我有一个值'Dog'
和一个数组['Cat', 'Dog', 'Bird']
。
如何检查它是否存在于数组中而不遍历它?有没有简单的方法检查值是否存在,仅此而已?
Answers:
您正在寻找include?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
%w(Cat Dog Bird).include? 'Dog'
#include?
仍然会执行循环。不过,可以通过显式编写循环来保存编码器。我添加了一个答案,该答案可以真正执行任务而无需循环。
有一种in?
方法在ActiveSupport
(滑轨的一部分),因为V3.1,如通过@campaterson指出。因此,在Rails中,或者,如果您可以require 'active_support'
,您可以编写:
'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false
OTOH,Ruby本身没有in
运算符或#in?
方法,即使以前已经提出过,特别是红宝石核心的顶尖成员远藤裕介也曾提出过。
如其他人指出的那样,反向方法include?
存在,所有Enumerable
小号包括Array
,Hash
,Set
,Range
:
['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
一个快速测试表明,调用include?
一个10元的Set
约3.5倍比调用它的等效快Array
(如果未找到该元素)。
#in?
的核心不包含Ruby ,但是如果您使用的是Rails,则可以使用它。api.rubyonrails.org/classes/Object.html#method-i-in-3F(我知道这是Ruby,而不是Rails问题,但它可能会帮助希望#in?
在Rails中使用的任何人。看起来它是在Rails中添加的3.1 apidock.com/rails/Object/in%3F
如果要按块检查,可以尝试any?
或all?
。
%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
有关更多信息,请参见Enumerable。
我的灵感来自“ 评估数组中是否有红宝石 ”
Ruby有11种方法来查找数组中的元素。
首选的是,include?
或者对于重复访问,创建一个Set,然后调用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?
是首选方法。它在for
内部使用C语言循环,当元素与内部rb_equal_opt/rb_equal
函数匹配时中断。除非您为重复的成员资格检查创建一个Set,否则它不会变得更加高效。
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)的时间复杂性,因为这两个查询的阵列的预期值的第一次出现。
我们可以使用Set来获取O(1)访问时间,但必须先创建数组的Hash表示。如果您反复检查同一阵列的成员资格,则此初始投资可以很快得到回报。Set
不是用C实现的,但作为普通的Ruby类,底层的O(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?
该成员资格是通过Hash类中的O(1)访问时间实现的。
我不会讨论其他七个方法,因为它们效率都较低。
实际上,除了上面列出的11种方法外,还有更多具有O(n)复杂度的方法,但是我决定不列出它们,因为它们扫描整个数组而不是在第一次匹配时中断。
不要使用这些:
# bad examples
array.grep(element).any?
array.select { |each| each == element }.size > 0
...
11
您列举的方式。首先,您几乎不能将index
和find_index
(或find
和detect
)视为单独的方法,因为它们只是同一方法的不同名称。其次,所有以结尾结尾的表达式> 0
都是错误的,我确信这是一个疏忽。(续)
arr.index(e)
,例如,返回0
如果arr[0] == e
。如果不存在,您将召回arr.index(e)
退货。但是,如果要在中搜索,则无法使用。(与相同的问题,未列出。)。将数组转换为集合,然后采用集合方法有点麻烦。为什么不转换为哈希(使用数组中的键和任意值),然后使用哈希方法呢?即使可以转换为集合,也可以使用其他集合方法,例如。(续)nil
e
index
nil
arr
rindex
!arr.to_set.add?(e)
arr.count(e) > 0
,arr != arr.dup.delete(e)
,arr != arr - [e]
和arr & [e] == [e]
。一个人也可以雇用select
和reject
。
有几个答案提示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。那里有许多Trie实现(谷歌“ 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 }
现在,我们准备使用sublinear O(log n)
,以与相同的语法简单性Array#include?
,测试数组中各个单词的存在,而无需在时间上进行遍历Trie#include?
:
trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
a.each do ... end
嗯...不确定那不是一个循环
Set#include?
对于那些关心效率的人,已经提到了一个答案。再加上使用符号而不是字符串,则可能是O(1)的平均情况(如果使用字符串,则仅计算哈希为O(n),其中n是字符串的长度)。或者,如果您想使用第三方库,则可以使用完美的哈希,这是O(1)最坏的情况。
Set
使用散列来为其成员建立索引,因此,对于分布良好的散列来说,实际上Set#include?
应该具有O(1)的复杂度Set
(更具体而言,对于散列而言,它的复杂度为O(input-size),对于散列而言,其复杂度为O(log(n / bucket-number)))搜索)
如果您不想循环,则无法使用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不需要遍历集合来查找项目,因为顾名思义,它会生成键的哈希并创建内存映射,从而每个哈希都指向内存中的某个点。前面的示例使用哈希完成:
fake_array = {}
100.times{|i| fake_array["foo#{i}"] = 1}
fake_array.has_key?("foo99")
=> true
缺点是Set和Hash键只能包含唯一项,如果添加很多项,Ruby将必须在一定数量的项后重新散列整个内容,以构建适合较大键空间的新映射。有关此的更多信息,我建议您观看“ MountainWest RubyConf 2014-Nathan Long的自制哈希中的Big O ”。
这是一个基准:
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)
include?
一击就停吗?
include?
停在第一个匹配项上,但是如果该匹配项在列表的末尾。...依赖数组存储的任何解决方案都会随着列表的增长而降低性能,尤其是当必须在列表的末尾查找元素时清单。哈希和Set没问题,有序列表和二进制搜索也没有。
如果您需要多次检查任何键,请转换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?
参数Hash#has_key?数组#包含 时间复杂度O(1)运算O(n)运算 访问类型如果对每个元素进行迭代,则访问Hash [key] 返回数组的任何值,直到它 返回true以在数组中查找值 Hash#has_key?呼叫 呼叫
对于单次检查使用include?
就可以了
对于它的价值,Ruby文档对于这类问题而言是一个了不起的资源。
我还要记下要搜索的数组的长度。该include?
方法将运行具有O(n)复杂度的线性搜索,根据数组的大小,该搜索可能很难看。
如果您使用的是大型(排序的)数组,我会考虑编写一种二进制搜索算法,该算法应该不太困难,并且具有O(log n)的最坏情况。
或者,如果您使用的是Ruby 2.0,则可以利用bsearch
。
<=>
,但情况并非总是如此。例如,假设数组的元素是哈希。
你可以试试:
示例:如果数组中存在猫和狗:
(['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')
注意:member?
和include?
相同。
这样可以一站式完成工作!
如果我们不想使用include?
它,也可以:
['cat','dog','horse'].select{ |x| x == 'dog' }.any?
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
['Cat', nil, 'Dog'].detect { |x| x == nil } #=> nil
。被nil
发现了吗?
如果您要在MiniTest单元测试中尝试执行此操作,则可以使用assert_includes
。例:
pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog') # -> passes
assert_includes(pets, 'Zebra') # -> fails
还有另一种解决方法。
假设阵列[ :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}[,|.| |]/}
/[,|.| |]#{v.to_s}[,|.| |]/
使我觉得您想找到“动作的名称,并用以下各项之一包围:逗号,句点,空格或什么都不做”,但其中存在一些细微的错误。"|update|"
将返回[:update]
和"update"
将返回[]
。字符类([...]
)不使用竖线(|
)分隔字符。即使我们将它们更改为组((...)
),也无法匹配空字符。因此,您可能想要的正则表达式是/(,|\.| |^)#{v.to_s}(,|\.| |$)/
/[,. ]/
这是执行此操作的另一种方法:
arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'
present = arr.size != (arr - [e]).size
arr != arr - [e]
。arr & [e] == [e]
是沿着相同路线的另一种方式。
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")
我总是发现运行一些基准测试来查看各种操作方式的相对速度很有趣。
在开始,中间或结尾处查找数组元素将影响任何线性搜索,但几乎不影响针对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 / 2
并last
用于ARRAY
为元素正在搜索。搜索ARRAY
和SET
变量时将使用该元素。
> 0
由于要>= 0
对index
类型进行测试,因此对要比较的方法进行了较小的更改。
有关Fruity及其方法的更多信息,请参见README。