在Ruby中将数组转换为索引散列

我有一个数组,我想做一个散列,所以我可以很快问“数组中的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方法吗?

编辑1

不,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) 

如果所有你需要的哈希是成员资格,考虑使用一个Set

Set实现一个没有重复值的无序值的集合。 这是Array的直观互操作设施和Hash快速查找的混合体。

设置很容易与Enumerable对象一起使用(实现each )。 大多数初始化方法和二元运算符接受除了集合和数组之外的genericsEnumerable对象。 Enumerable对象可以使用to_set方法转换为Set 。

设置使用哈希作为存储,所以你必须注意以下几点:

  • 元素的平等是根据Object#eql?来确定的Object#eql?Object#hash
  • Set假设每个元素的标识在存储时不会改变。 修改集合中的元素将会使集合处于不可靠的状态。
  • 当要存储string时,将存储string的冻结副本,除非原始string已冻结。

对照

比较运算符<><=>=被实现为{proper _,} {subset?,superset?}方法的简写forms。 然而, <=>操作符被故意排除,因为不是每一组都是可比的。 ({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 

[…]

公共类方法

新(枚举=零)

创build一个包含给定枚举对象的元素的新集合。

如果给出了一个块,枚举的元素将由给定的块进行预处理。

试试这个:

 a=[1,2,3] Hash[a.zip []] 

你可以做这个非常方便的技巧:

 Hash[*[1, 2, 3, 4].map {|k| [k, nil]}.flatten] => {1=>nil, 2=>nil, 3=>nil, 4=>nil} 

如果你想快速问“arrays中是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) 

你应该考虑只使用一组来开始,而不是一个数组,所以一个转换是不必要的。

我相当肯定,没有一个聪明的方法来构造这个散列。 我的倾向将是明确的,并说明我在做什么:

 hash = {} array.each{|x| hash[x] = nil} 

它看起来不是特别优雅,但很明显,做这项工作。

FWIW,你原来的build议(至less在Ruby 1.8.6下)似乎不起作用。 我得到一个“ArgumentError:散列的奇数个参数”错误。 Hash。[]需要一个字面值,甚至长的值列表:

 Hash[a, 1, b, 2] # => {a => 1, b => 2} 

所以我试着改变你的代码:

 hash = Hash[*array.map {|x| [x, nil]}.flatten] 

但performance是可怕的:

 #!/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) 

除非我在这里丢失了一些东西,否则一个简单的赋值循环似乎是构造这个哈希的最清晰和最有效的方法。

Rampion击败了我。 设置可能是答案。

你可以做:

 require 'set' set = array.to_set set.include?(x) 

你创build哈希的方式看起来不错。 我在irb周围有一块土地,这是另一种方式

 >> [1,2,3,4].inject(Hash.new) { |h,i| {i => nil}.merge(h) } => {1=>nil, 2=>nil, 3=>nil, 4=>nil} 

我认为chrismear在创作上的使用是非常重要的。 为了让整个事情变得更加Ruby化,虽然我可能会build议为每个元素分配一些不是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 

也许我误解了这里的目标。 如果你想知道X是否在数组中,为什么不做array.include?(“X”)?

对目前为止的build议做了一些基准testing,结果是chrismear和Gaius的基于赋值的哈希创build比我的映射方法稍微快一些(并且赋值nil比赋值true要快一些)。 mtyaka和rampion的设置build议比创build慢35%左右。

就查找而言, hash.include?(x)hash[x]快很less; 两者都是一样快的两倍。包括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 

如果你不打扰散列值是什么

 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 

需要一秒钟左右我的桌面上。

如果你正在寻找这个Perl代码的等价物:

 grep {$_ eq $element} @array 

你可以使用简单的Ruby代码:

 array.include?(element) 

这里有一个很好的方法来caching哈希查找:

 a = (1..1000000).to_a h = Hash.new{|hash,key| hash[key] = true if a.include? key} 

它所做的几乎是为新的散列值创build一个默认的构造函数,然后如果在数组中,则将“true”存储在caching中(否则为零)。 这允许延迟加载到caching中,以防万一您不使用每个元素。

如果你的散列是[0,0,0,1,0]这将保留0

  hash = {} arr.each_with_index{|el, idx| hash.merge!({(idx + 1 )=> el }) } 

返回:

  # {1=>0, 2=>0, 3=>0, 4=>1, 5=>0}