I have an array, and I want to make a hash so I can quickly ask "is X in the array?".
In perl, there is an easy (and fast) way to do this:
my @array = qw( 1 2 3 );
my %hash;
@hash{@array} = undef;
This generates a hash that looks like:
{
1 => undef,
2 => undef,
3 => undef,
}
The best I've come up with in Ruby is:
array = [1, 2, 3]
hash = Hash[array.map {|x| [x, nil]}]
which gives:
{1=>nil, 2=>nil, 3=>nil}
Is there a better Ruby way?
EDIT 1
No, Array.include? is not a good idea. Its slow. It does a query in O(n) instead of O(1). My example array had three elements for brevity; assume the actual one has a million elements. Let's do a little benchmarking:
#!/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
Produces:
user system total real
Array.include? 46.190000 0.160000 46.350000 ( 46.593477)
Hash.include? 0.000000 0.000000 0.000000 ( 0.000523)
If you want to quickly ask "is X in the array?" you should use
Array#include?
.Edit (in response to addition in OP):
If you want speedy look up times, use a Set. Having a Hash that points to all
nil
s is silly. Conversion is an easy process too withArray#to_set
.Results on my machine:
You should consider just using a set to begin with, instead of an array so that a conversion is never necessary.
You can do this very handy trick:
Doing some benchmarking on the suggestions so far gives that chrismear and Gaius's assignment-based hash creation is slightly faster than my map method (and assigning nil is slightly faster than assigning true). mtyaka and rampion's Set suggestion is about 35% slower to create.
As far as lookups,
hash.include?(x)
is a very tiny amount faster thanhash[x]
; both are twice as a fast asset.include?(x)
.Benchmarking code is:
This preserves 0's if your hash was
[0,0,0,1,0]
Returns :
I think chrismear's point on using assignment over creation is great. To make the whole thing a little more Ruby-esque, though, I might suggest assigning something other than
nil
to each element:The problem with assigning to
nil
is that you have to usehas_key?
instead of[]
, since[]
give younil
(your marker value) if theHash
doesn't have the specified key. You could get around this by using a different default value, but why go through the extra work?I'm fairly certain that there isn't a one-shot clever way to construct this hash. My inclination would be to just be explicit and state what I'm doing:
It doesn't look particularly elegant, but it's clear, and does the job.
FWIW, your original suggestion (under Ruby 1.8.6 at least) doesn't seem to work. I get an "ArgumentError: odd number of arguments for Hash" error. Hash.[] expects a literal, even-lengthed list of values:
so I tried changing your code to:
but the performance is dire:
gives
Unless I'm missing something here, a simple assignment loop seems the clearest and most efficient way to construct this hash.