我有一个数组,我想制作一个哈希,所以我可以快速地问“数组中是X吗?”。
在perl中,有一种简单(快速)的方法可以做到这一点:
my @array = qw( 1 2 3 );
my %hash;
@hash{@array} = undef;
这会生成一个类似于:
的哈希{
1 => undef,
2 => undef,
3 => undef,
}
我在Ruby中提出的最好的是:
array = [1, 2, 3]
hash = Hash[array.map {|x| [x, nil]}]
给出:
{1=>nil, 2=>nil, 3=>nil}
有更好的Ruby方式吗?
不,Array.include?不是个好主意。它的慢。它在O(n)中进行查询而不是O(1)。为简洁起见,我的示例数组有三个元素;假设实际的有一百万个元素。让我们做一点基准测试:
#!/usr/bin/ruby -w
require 'benchmark'
array = (1..1_000_000).to_a
hash = Hash[array.map {|x| [x, nil]}]
Benchmark.bm(15) do |x|
x.report("Array.include?") { 1000.times { array.include?(500_000) } }
x.report("Hash.include?") { 1000.times { hash.include?(500_000) } }
end
产地:
user system total real
Array.include? 46.190000 0.160000 46.350000 ( 46.593477)
Hash.include? 0.000000 0.000000 0.000000 ( 0.000523)
答案 0 :(得分:43)
如果您只需要哈希是会员资格,请考虑使用Set
:
集
Set实现了无序值的集合 重复。这是Array直观互操作的混合体 设施和哈希的快速查找。
Set易于与Enumerable个对象一起使用(实现
each
)。大多数初始化方法和二元运算符都接受 除了集合和数组之外的通用Enumerable对象。一个 可以使用Enumerable对象转换为Setto_set
方法。Set使用Hash作为存储空间,因此您必须注意以下几点:
- 元素的平等根据
Object#eql?
和Object#hash
确定。- Set假定每个元素的标识在存储时不会更改。修改集合的元素将使集合呈现为 不可靠的状态。
- 当要存储字符串时,将存储字符串的冻结副本,除非原始字符串已被冻结。
比较
比较运算符
<
,>
,<=
和>=
实现为 {proper _,} {subset?,superset?}方法的简写。然而 有意遗漏<=>
运营商,因为不是每一对运营商 套装具有可比性。 (例如{x,y}与{x,z})实施例
require 'set' s1 = Set.new [1, 2] # -> #<Set: {1, 2}> s2 = [1, 2].to_set # -> #<Set: {1, 2}> s1 == s2 # -> true s1.add("foo") # -> #<Set: {1, 2, "foo"}> s1.merge([2, 6]) # -> #<Set: {1, 2, "foo", 6}> s1.subset? s2 # -> false s2.subset? s1 # -> true
[...]
公共类方法
new(enum = nil)
创建一个包含给定枚举元素的新集合 对象
如果给出了一个块,则enum的元素由the进行预处理 给定块。
答案 1 :(得分:22)
试试这个:
a=[1,2,3]
Hash[a.zip]
答案 2 :(得分:9)
如果你想快速询问“数组中的X是什么?”你应该使用Array#include?
。
编辑(响应OP中的添加):
如果您想要快速查找时间,请使用Set。拥有指向所有nil
的哈希是愚蠢的。使用Array#to_set
进行转换也很简单。
require 'benchmark'
require 'set'
array = (1..1_000_000).to_a
set = array.to_set
Benchmark.bm(15) do |x|
x.report("Array.include?") { 1000.times { array.include?(500_000) } }
x.report("Set.include?") { 1000.times { set.include?(500_000) } }
end
我的机器上的结果:
user system total real
Array.include? 36.200000 0.140000 36.340000 ( 36.740605)
Set.include? 0.000000 0.000000 0.000000 ( 0.000515)
你应该考虑使用一个开头而不是一个数组,这样就不需要转换了。
答案 3 :(得分:6)
我相当确定没有一次巧妙的方法来构造这个哈希。我倾向于明确并陈述我正在做的事情:
hash = {}
array.each{|x| hash[x] = nil}
它看起来并不是特别优雅,但它很清楚,并且完成了工作。
FWIW,您的原始建议(至少在Ruby 1.8.6下)似乎不起作用。我得到一个“ArgumentError:Hash的奇数个参数”错误。 Hash。[]需要一个文字的,偶数加长的值列表:
Hash[a, 1, b, 2] # => {a => 1, b => 2}
所以我尝试将您的代码更改为:
hash = Hash[*array.map {|x| [x, nil]}.flatten]
但表现很糟糕:
#!/usr/bin/ruby -w
require 'benchmark'
array = (1..100_000).to_a
Benchmark.bm(15) do |x|
x.report("assignment loop") {hash = {}; array.each{|e| hash[e] = nil}}
x.report("hash constructor") {hash = Hash[*array.map {|e| [e, nil]}.flatten]}
end
给出
user system total real
assignment loop 0.440000 0.200000 0.640000 ( 0.657287)
hash constructor 4.440000 0.250000 4.690000 ( 4.758663)
除非我在这里遗漏了一些东西,否则一个简单的赋值循环似乎是构造这个哈希的最清晰,最有效的方法。
答案 4 :(得分:5)
你可以这样做:
require 'set'
set = array.to_set
set.include?(x)
答案 5 :(得分:4)
您创建哈希的方式看起来不错。我在irb身边有一个淤泥,这是另一种方式
>> [1,2,3,4].inject(Hash.new) { |h,i| {i => nil}.merge(h) }
=> {1=>nil, 2=>nil, 3=>nil, 4=>nil}
答案 6 :(得分:2)
我认为chrismear关于使用赋值而不是创造的观点非常重要。但是,为了使整个事情更像Ruby,我可能会建议为每个元素分配其他而不是nil
:
hash = {}
array.each { |x| hash[x] = 1 } # or true or something else "truthy"
...
if hash[376] # instead of if hash.has_key?(376)
...
end
分配给nil
的问题是您必须使用has_key?
而不是[]
,因为[]
会为您提供nil
(您的标记值)如果Hash
没有指定的密钥。你可以通过使用不同的默认值来解决这个问题,但为什么还要进行额外的工作呢?
# much less elegant than above:
hash = Hash.new(42)
array.each { |x| hash[x] = nil }
...
unless hash[376]
...
end
答案 7 :(得分:1)
也许我误解了这里的目标;如果你想知道X是否在数组中,为什么不进行array.include?(“X”)?
答案 8 :(得分:1)
对目前的建议进行一些基准测试,结果表明,chrismear和Gaius的基于任务的哈希创建比我的map方法略快(并且分配nil比分配true更快)。 mtyaka和rampion的Set建议创建速度要慢35%。
就查询而言,hash.include?(x)
的速度比hash[x]
快很多;两者都是set.include?(x)
的两倍。
user system total real
chrismear 6.050000 0.850000 6.900000 ( 6.959355)
derobert 6.010000 1.060000 7.070000 ( 7.113237)
Gaius 6.210000 0.810000 7.020000 ( 7.049815)
mtyaka 8.750000 1.190000 9.940000 ( 9.967548)
rampion 8.700000 1.210000 9.910000 ( 9.962281)
user system total real
times 10.880000 0.000000 10.880000 ( 10.921315)
set 93.030000 17.490000 110.520000 (110.817044)
hash-i 45.820000 8.040000 53.860000 ( 53.981141)
hash-e 47.070000 8.280000 55.350000 ( 55.487760)
基准代码是:
#!/usr/bin/ruby -w
require 'benchmark'
require 'set'
array = (1..5_000_000).to_a
Benchmark.bmbm(10) do |bm|
bm.report('chrismear') { hash = {}; array.each{|x| hash[x] = nil} }
bm.report('derobert') { hash = Hash[array.map {|x| [x, nil]}] }
bm.report('Gaius') { hash = {}; array.each{|x| hash[x] = true} }
bm.report('mtyaka') { set = array.to_set }
bm.report('rampion') { set = Set.new(array) }
end
hash = Hash[array.map {|x| [x, true]}]
set = array.to_set
array = nil
GC.start
GC.disable
Benchmark.bmbm(10) do |bm|
bm.report('times') { 100_000_000.times { } }
bm.report('set') { 100_000_000.times { set.include?(500_000) } }
bm.report('hash-i') { 100_000_000.times { hash.include?(500_000) } }
bm.report('hash-e') { 100_000_000.times { hash[500_000] } }
end
GC.enable
答案 9 :(得分:1)
如果你没有打扰哈希值是什么
irb(main):031:0> a=(1..1_000_000).to_a ; a.length
=> 1000000
irb(main):032:0> h=Hash[a.zip a] ; h.keys.length
=> 1000000
我的桌面需要一秒左右。
答案 10 :(得分:0)
如果您正在寻找等效的Perl代码:
grep {$_ eq $element} @array
您可以使用简单的Ruby代码:
array.include?(element)
答案 11 :(得分:0)
这是使用哈希缓存查找的简洁方法:
a = (1..1000000).to_a
h = Hash.new{|hash,key| hash[key] = true if a.include? key}
它的作用是为新的哈希值创建默认构造函数,然后在缓存中存储“true”(如果它在数组中)(否则为nil)。这允许延迟加载到缓存中,以防您不使用每个元素。
答案 12 :(得分:0)
如果您的哈希为[0,0,0,1,0]
hash = {}
arr.each_with_index{|el, idx| hash.merge!({(idx + 1 )=> el }) }
返回:
# {1=>0, 2=>0, 3=>0, 4=>1, 5=>0}