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
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 }
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;
}
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
22 回答
如果你不想使用include?您可以先将元素包装在一个数组中,然后检查包装的元素是否等于数组和包装元素的交集 . 这将返回基于相等性的布尔值 .
这样怎么样?
对于它的 Value ,Ruby docs是这类问题的绝佳资源 .
我还会记下你正在搜索的数组的长度 .
include?
方法将运行具有O(n)复杂度的线性搜索,这可能会非常难看,具体取决于数组的大小 .如果你正在处理一个大的(有序的)数组,我会考虑编写一个binary search algorithm,它应该不会太难并且最坏的情况是O(log n) .
或者,如果您使用的是Ruby 2.0,则可以利用
bsearch
.这是另外一种方法:
如果您需要检查任何键的倍数,请将
arr
转换为hash
,然后检入O(1)Performance of Hash#has_key? versus Array#include?
对于单次检查使用
include?
很好如果您不想循环,则无法使用Arrays进行循环 . 你应该使用Set代替 .
在内部设置工作就像哈希一样,因此Ruby不需要遍历集合来查找项目,因为顾名思义,它会生成键的哈希值并创建一个内存映射,以便每个哈希都指向内存中的某个点 . 前面的示例使用Hash完成:
缺点是集合和散列键只能包含唯一的项目,如果你添加了很多项目,Ruby必须在特定数量的项目之后重新整理整个事物,以构建适合更大键空间的新映射 . 有关这方面的更多信息,我建议你看MountainWest RubyConf 2014 - Big O in a Homemade Hash by Nathan Long
这是一个基准:
结果如下:
尝试
有趣的事实,
您可以使用
*
检查case
表达式中的数组成员资格 .注意when子句中的小
*
,这将检查数组中的成员资格 .splat运算符的所有常见魔术行为都适用,例如,如果
array
实际上不是数组而是单个元素,它将匹配该元素 .使用
Enumerable#include
:或者,如果完成了许多测试,1你可以摆脱循环(甚至
include?
)并从O(n)转到O(1):1.我希望这是显而易见的,但要避免反对意见:是的,对于一些查找,Hash []和转置操作支配配置文件并且每个都是O(n) .
几个答案建议
Array#include?
,但有一个重要的警告:看源,甚至Array#include?
确实执行循环:在没有循环的情况下测试单词存在的方法是为数组构建一个trie . 那里有很多特里实现(google "ruby trie") . 我将在此示例中使用
rambling-trie
:现在我们已准备好在
O(log n)
时间内测试数组中各种单词的存在而不循环,使用与Array#include?
相同的句法简洁性,使用次线性Trie#include?
:@campaterson指出,自v3.1以来
ActiveSupport
(Rails的一部分)中有一个in? method . 所以在Rails中,或者如果你require 'active_support'
,你可以写:OTOH,Ruby本身没有
in
运算符或#in?
方法,尽管之前已经提出过,in particular by Yusuke Endoh是ruby-core的顶级成员 .正如其他人所指出的,对于所有
Enumerable
,包括Array
,Hash
,Set
,Range
,反向方法include?存在:请注意,如果数组中有许多值,它们将一个接一个地检查(即
O(n)
),而对哈希的查找将是恒定时间(即O(1)
) . 因此,例如,如果数组是常量,则最好使用Set . 例如:quick test显示在10个元素
Set
上调用include?
比在等效的Array
上调用它快3.5倍(如果找不到该元素) .最后的结束注释:在
Range
上使用include?
时要小心,有细微之处,所以请参考the doc并与cover?进行比较......如果你想通过街区检查,你可以试试吗?还是全部?
详情如下:http://ruby-doc.org/core-1.9.3/Enumerable.html
我的灵感来自这里:https://stackoverflow.com/a/10342734/576497
这是另一种方法:使用Array#index方法 .
它返回数组中第一次出现元素的索引 .
例:
index()也可以占用一个块
例如
在这里,返回包含字母'o'的数组中第一个单词的索引 .
如果你有更多的想法 Value ......你可以尝试:
示例:如果数组中存在Cat和Dog:
代替:
注意:会员?并包括?是相同的 .
This can do the work in one line!
有多种方法可以实现这一目标 . 其中一些如下:
如果我们不想使用
include?
,这也有效:这不仅会告诉您它存在,还会告诉您它出现的次数:
还有另一种方式!
假设数组是[:edit,:update,:create,:show] - 也许是整个七个致命/宁静的罪恶:)
还有玩具的想法是从一些字符串中拉出一个有效的动作 - 比如说
解决方案
Ruby有11种方法可以在数组中查找元素 .
首选的是
include?
或者重复访问,创建一个集合,然后调用
include?
或member?
以下是所有这些,
如果元素存在,它们都返回
true
ish值 .include?
是首选方法 . 它在内部使用C语言for
循环,当元素与内部rb_equal_opt/rb_equal
函数匹配时会中断 . 除非您为重复的成员资格检查创建一个集合,否则它无法获得更高的效率 .member?
未在Array
类中重新定义,并使用Enumerable
模块中未经优化的实现,该实现实际上枚举了所有元素 .转换为Ruby代码,这涉及以下内容
include?
和member?
都具有O(n)
时间复杂度,因为它们都搜索数组以获得第一次出现的期望值 .我们可以使用一个集来获得
O(1)
访问时间,代价是必须首先创建数组的哈希表示 . 如果您反复检查同一阵列上的成员资格,则此初始投资可以快速获得回报 .Set
没有在C中实现,但作为普通的Ruby类,底层@hash
的O(1)
访问时间仍然值得 .这是
Set
类的实现,正如您所看到的,
Set
类只创建一个内部@hash
实例,将所有对象映射到true
,然后使用Hash#include?
检查成员资格,这是在Hash
类中使用O(1)
访问时间实现的 .我不会讨论其他7种方法,因为它们都效率较低 .
除了上面列出的11之外,实际上还有更多的方法具有
O(n)
复杂性,但我决定不扫描它们,因为扫描整个数组而不是在第一次匹配时中断 .不要使用这些,
你在找include?: