Is there a more efficient way to turn an array into a hash? - ruby

I think that my method is a little clumsy, and that there is likely to be a one-liner that I'm missing. Ideas?
def _to_hash
hsh = {}
self.each_slice(2){|v| hsh[v[0]] = v[1]}
hsh
end
1.9.3-p0 :003 > ["a", 1, "b", 2]._to_hash
{
"a" => 1,
"b" => 2
}

#phiggy's method is correct, but also remember that you can use a splat operator:
a = ["a", 1, "b", 2]
Hash[*a] #=> {"a"=>1, "b"=>2}

You want Hash's .[] operator:
> Hash["a", 1, "b", 2]
=> {"a"=>1, "b"=>2}

Related

Inverting a hash value (that's an array) into new individual keys

I have the following:
lumpy_hash = { 1 => ["A", "B"] }
then if I invoke Hash#invert on this hash, I'd like to get:
lumpy_hash = {"A" => 1, "B" => 1}
I don't get that from using Hash#invert. Any ideas on doing this? I'm not sure if I should try Hash#map or Hash#invert.
There are many ways to do this. Here is one:
Hash[lumpy_hash.map { |k,v| v.product([k]) }.first]
#=> {"A"=>1, "B"=>1}
I don't think the method Hash#invert is useful here.
The steps:
enum = lumpy_hash.map
#=> #<Enumerator: {1=>["A", "B"]}:map>
k,v = enum.next
#=> [1, ["A", "B"]]
k #=> 1
v #=> ["A", "B"]
a = v.product([k])
#=> ["A", "B"].product([1])
#=> [["A", 1], ["B", 1]]
Hash[a]
#=> {"A"=>1, "B"=>1}
Here's another way that makes use of a hash's default value. This one is rather interesting:
key,value = lumpy_hash.to_a.first
#=> [1, ["A","B"]]
Hash.new { |h,k| h[k]=key }.tap { |h| h.values_at(*value) }
#=> {"A"=>1,"B"=>1}
Object#tap passes an empty hash to its block, assigning it to the block variable h. The block returns h after adding three key-value pairs, each having a value equal to the hash's default value. It adds the pairs merely by computing the values of keys the hash doesn't have!
Here's another, more pedestrian, method:
lumpy_hash.flat_map{|k,vs| vs.map{|v| {v => k}}}.reduce(&:merge)
=> {"A"=>1, "B"=>1}

How can I push keys to an unsorted array?

I'm trying to create an array of the keys of an ordered hash. I want them to be listed in the same order in both the array and the hash. I have this hash.
h = { "a" => 3, "b" => 1, "c" = 4, "d" = 2 }
What I want is this array.
arr = ["b", "d", "a", "c"]
I have
h.sort_by { |k, v| v}
h.keys
but that returns the keys in alphabetical order. What can I do to keep them in the order of the sorted hash?
h.sort_by{|k,v| v} will give you [["b", 1], ["d", 2], ["a", 3], ["c", 4]], then use .map to get the key.
h.sort_by{|k,v| v}.map &:first
h = { "a" => 3, "b" => 1, "c" => 4, "d" => 2 }
p h.sort_by(&:last).map(&:first) #=> ["b", "d", "a", "c"]
You may try this also,
h = { "a" => 3, "b" => 1, "c" => 4, "d" => 2 }
Hash[h.sort_by{|k,v| v}].keys
#=> ["b", "d", "a", "c"]
This code
h.sort_by { |k,v| v}
h.keys
doesn't work because the sort_by method doesn't sort the original array, it returns a new sorted array, where each value is a (key, value) pair from the original hash:
[["b", 1], ["d", 2], ["a", 3], ["c", 4]]
If you're using Ruby 2.1.1, you can then just call to_h on the array, which will re-map the key/value pairs back into a hash:
h.sort_by { |k, v| v}.to_h.keys

Create an array from a hash with each_with_index

I have an array:
arr = ["a", "b", "c"]
What I want to do is to create a Hash so that it looks like:
{1 => "a", 2 => "b", 3 => c}
I tried to do that:
Hash[arr.each_with_index.map { |item, i| [i => item] }]
but didn't get what I was looking for.
each_with_index returns the original receiver. In order to get something different from the original receiver, map is necessary anyway. So there is no need of an extra step using each or each_with_index. Also, with_index optionally takes the initial index.
Hash[arr.map.with_index(1){|item, i| [i, item]}]
# => {1 => "a", 2 => "b", 3 => c}
Hash[] takes an array of arrays as argument. So you need to use [i, item] instead of [i => item]
arr = ["a", "b", "c"]
Hash[arr.each_with_index.map{|item, i| [i+1, item] }]
#=> {1=>"a", 2=>"b", 3=>"c"}
Just for clarification: [i => item] is the same as writing [{i => item}] so you really produced an array of arrays that in turn contained a single hash each.
I also added a +1 to the index so the hash keys start at 1 as you requested. If you don't care or if you want to start at 0, just leave that off.
arr = ["a", "b", "c"]
p Hash[arr.map.with_index(1){|i,j| [j,i]}]
# >> {1=>"a", 2=>"b", 3=>"c"}

Create a hash from an array of keys

I have looked at other questions in SO and did not find an answer for my specific problem.
I have an array:
a = ["a", "b", "c", "d"]
I want to convert this array to a hash where the array elements become the keys in the hash and all they the same value say 1. i.e hash should be:
{"a" => 1, "b" => 1, "c" => 1, "d" => 1}
My solution, one among the others :-)
a = ["a", "b", "c", "d"]
h = Hash[a.map {|x| [x, 1]}]
There are several options:
to_h with block:
a.to_h { |a_i| [a_i, 1] }
#=> {"a"=>1, "b"=>1, "c"=>1, "d"=>1}
product + to_h:
a.product([1]).to_h
#=> {"a"=>1, "b"=>1, "c"=>1, "d"=>1}
transpose + to_h:
[a,[1] * a.size].transpose.to_h
#=> {"a"=>1, "b"=>1, "c"=>1, "d"=>1}
a = ["a", "b", "c", "d"]
4 more options, achieving the desired output:
h = a.map{|e|[e,1]}.to_h
h = a.zip([1]*a.size).to_h
h = a.product([1]).to_h
h = a.zip(Array.new(a.size, 1)).to_h
All these options rely on Array#to_h, available in Ruby v2.1 or higher
a = %w{ a b c d e }
Hash[a.zip([1] * a.size)] #=> {"a"=>1, "b"=>1, "c"=>1, "d"=>1, "e"=>1}
["a", "b", "c", "d"].inject({}) do |hash, elem|
hash[elem] = 1
hash
end
Here:
hash = Hash[a.map { |k| [k, value] }]
This assumes that, per your example above, that a = ['a', 'b', 'c', 'd'] and that value = 1.
a = ["a", "b", "c", "d"]
h = a.inject({}){|h,k| h[k] = 1; h}
#=> {"a"=>1, "b"=>1, "c"=>1, "d"=>1}
a = ['1','2','33','20']
Hash[a.flatten.map{|v| [v,0]}.reverse]
{}.tap{|h| %w(a b c d).each{|x| h[x] = 1}}

Converting an array of keys and an array of values into a hash in Ruby

I have two arrays like this:
keys = ['a', 'b', 'c']
values = [1, 2, 3]
Is there a simple way in Ruby to convert those arrays into the following hash?
{ 'a' => 1, 'b' => 2, 'c' => 3 }
Here is my way of doing it, but I feel like there should be a built-in method to easily do this.
def arrays2hash(keys, values)
hash = {}
0.upto(keys.length - 1) do |i|
hash[keys[i]] = values[i]
end
hash
end
The following works in 1.8.7:
keys = ["a", "b", "c"]
values = [1, 2, 3]
zipped = keys.zip(values)
=> [["a", 1], ["b", 2], ["c", 3]]
Hash[zipped]
=> {"a"=>1, "b"=>2, "c"=>3}
This appears not to work in older versions of Ruby (1.8.6). The following should be backwards compatible:
Hash[*keys.zip(values).flatten]
Another way is to use each_with_index:
hash = {}
keys.each_with_index { |key, index| hash[key] = values[index] }
hash # => {"a"=>1, "b"=>2, "c"=>3}
The same can be done using Array#transpose method. If you are using Ruby version >= 2.1, you can take the advantage of the method Array#to_h, otherwise use your old friend, Hash::[]
keys = ['a', 'b', 'c']
values = [1, 2, 3]
[keys, values].transpose.to_h
# => {"a"=>1, "b"=>2, "c"=>3}
Hash[[keys, values].transpose]
# => {"a"=>1, "b"=>2, "c"=>3}
Try this, this way the latter one d will overwrite the former one c
irb(main):001:0> hash = Hash[[[1,2,3,3], ['a','b','c','d']].transpose]
=> {1=>"a", 2=>"b", 3=>"d"}
irb(main):002:0>

Resources