Convert an array to hash, where keys are the indices - ruby

I am transforming an array into a hash, where the keys are the indices and values are the elements at that index.
Here is how I have done it
# initial stuff
arr = ["one", "two", "three", "four", "five"]
x = {}
# iterate and build hash as needed
arr.each_with_index {|v, i| x[i] = v}
# result
>>> {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Is there a better (in any sense of the word "better") way to do it?

arr = ["one", "two", "three", "four", "five"]
x = Hash[(0...arr.size).zip arr]
# => {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}

Ruby < 2.1:
Hash[arr.map.with_index { |x, i| [i, x] }]
#=> {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Ruby >= 2.1:
arr.map.with_index { |x, i| [i, x] }.to_h

x = Hash.new{|h, k| h[k] = arr[k]}

%w[one two three four five].map.with_index(1){ |*x| x.reverse }.to_h
Remove (1) if you want to start the index from 0.

Here is a solution making use of Object#tap, to add values to a newly-created hash:
arr = ["one", "two", "three", "four", "five"]
{}.tap do |hsh|
arr.each_with_index { |item, idx| hsh[idx] = item }
end
#=> {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}

Many good solutions already, just adding a variant (provided you do not have duplicated values):
["one", "two", "three", "four", "five"].map.with_index.to_h.invert
# => {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}

You could monkey patch Array to provide a new method:
class Array
def to_assoc offset = 0
# needs recent enough ruby version
map.with_index(offset).to_h.invert
end
end
Now you can do:
%w(one two three four).to_assoc(1)
# => {1=>"one", 2=>"two", 3=>"three", 4=>"four"}
This is a common operation I'm doing in Rails apps so I keep this monkey patch around in an initializer.

You should go with the map method, it's better and uses less memory. Another solution is with while loop. Good to know this way too, because often you may need to use the while loop.
At example 2, if you want to take each odd index in the an array as a key in the hash, and each even index as a value in the hash
Example 1:
some_array = ["one", "two", "three", "four", "five"]
def to_hash(arr)
counter = 0
new_hash = Hash.new(0)
while counter < arr.length
new_hash[counter] = arr[counter]
counter += 1
end
return new_hash
end
puts to_hash(arr)
# Output
{0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Example 2 -
Perhaps after getting data as a string, and you split this string to an array, and now you want to convert to key, value.
some_array = ['KBD', 'King Bedroom', 'QBD', 'Queen Bedroom', 'DBD', 'Double Bedroom', 'SGLB', 'Single Bedroom']
def to_hash(arr)
new_hash = Hash.new(0)
counter = 0
while counter < arr.length
new_hash[arr[counter]] = arr[counter+1]
counter += 2
end
return "#{new_hash}"
end
puts to_key_value_hash(some_array)
# Output
{"KBD"=>"King Bedroom", "QBD"=>"Queen Bedroom", "DBD"=>"Double Bedroom", "SGLB"=>"Single Bedroom"}
Many ways to do this, but just saying not to forget the while loop.

Related

convert array to hash and initialize to a value

what is the best way to convert an array of
arr = ["one", "two", "three", "four", "five"]
to hash of
{"one"=>0, "two"=>0, "three"=>0, "four"=>0, "five"=>0}
Iam planning to fillup the '0' with my own values later, I just need the technique now.
Thanks.
arr.product([0]).to_h
or for versions < 2.0
Hash[arr.product([0])]
I don't know if it's the best way:
Hash[arr.map{ |el| [el, 0] }]
I would do this:
arr.inject({}) { |hash, key| hash.update(key => 0) }
Hash[ *arr.collect { |v| [ v, 0 ] }.flatten ]
You can also do
arr.each_with_object({}) {|v, h| h[v] = 1}
where:
v is the value of each element in the array,
h represents the hash object

Counting unique occurrences of values in a hash

I'm trying to count occurrences of unique values matching a regex pattern in a hash.
If there's three different values, multiple times, I want to know how much each value occurs.
This is the code I've developed to achieve that so far:
def trim(results)
open = []
results.map { |k, v| v }.each { |n| open << n.to_s.scan(/^closed/) }
puts open.size
end
For some reason, it returns the length of all the values, not just the ones I tried a match on. I've also tried using results.each_value, to no avail.
Another way:
hash = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
hash.each_with_object(Hash.new(0)) {|(k,v),h| h[v]+=1 if v.start_with?('foo')}
#=> {"foo"=>2}
or
hash.each_with_object(Hash.new(0)) {|(k,v),h| h[v]+=1 if v =~ /^foo|bar/}
#=> {"foo"=>2, "bar"=>1}
Something like this?
hash = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
groups = hash.group_by{ |k, v| v[/(?:foo|bar)/] }
# => {"foo"=>[[:a, "foo"], [:d, "foo"]],
# "bar"=>[[:b, "bar"]],
# nil=>[[:c, "baz"]]}
Notice that there is a nil key, which means the regex didn't match anything. We can get rid of it because we (probably) don't care. Or maybe you do care, in which case, don't get rid of it.
groups.delete(nil)
This counts the number of matching "hits":
groups.map{ |k, v| [k, v.size] }
# => [["foo", 2], ["bar", 1]]
group_by is a magical method and well worthy of learning.
def count(hash, pattern)
hash.each_with_object({}) do |(k, v), counts|
counts[k] = v.count{|s| s.to_s =~ pattern}
end
end
h = { a: ['open', 'closed'], b: ['closed'] }
count(h, /^closed/)
=> {:a=>1, :b=>1}
Does that work for you?
I think it worths to update for RUBY_VERSION #=> "2.7.0" which introduces Enumerable#tally:
h = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
h.values.tally #=> {"foo"=>2, "bar"=>1, "baz"=>1}
h.values.tally.select{ |k, _| k=~ /^foo|bar/ } #=> {"foo"=>2, "bar"=>1}

simple hash merge by array of keys and values in ruby (with perl example)

In Perl to perform a hash update based on arrays of keys and values I can do something like:
#hash{'key1','key2','key3'} = ('val1','val2','val3');
In Ruby I could do something similar in a more complicated way:
hash.merge!(Hash[ *[['key1','key2','key3'],['val1','val2','val3']].transpose ])
OK but I doubt the effectivity of such procedure.
Now I would like to do a more complex assignment in a single line.
Perl example:
(#hash{'key1','key2','key3'}, $key4) = &some_function();
I have no idea if such a thing is possible in some simple Ruby way. Any hints?
For the Perl impaired, #hash{'key1','key2','key3'} = ('a', 'b', 'c') is a hash slice and is a shorthand for something like this:
$hash{'key1'} = 'a';
$hash{'key2'} = 'b';
$hash{'key3'} = 'c';
In Ruby 1.9 Hash.[] can take as its argument an array of two-valued arrays (in addition to the old behavior of a flat list of alternative key/value arguments). So it's relatively simple to do:
mash.merge!( Hash[ keys.zip(values) ] )
I do not know perl, so I'm not sure what your final "more complex assignment" is trying to do. Can you explain in words—or with the sample input and output—what you are trying to achieve?
Edit: based on the discussion in #fl00r's answer, you can do this:
def f(n)
# return n arguments
(1..n).to_a
end
h = {}
keys = [:a,:b,:c]
*vals, last = f(4)
h.merge!( Hash[ keys.zip(vals) ] )
p vals, last, h
#=> [1, 2, 3]
#=> 4
#=> {:a=>1, :b=>2, :c=>3}
The code *a, b = some_array will assign the last element to b and create a as an array of the other values. This syntax requires Ruby 1.9. If you require 1.8 compatibility, you can do:
vals = f(4)
last = vals.pop
h.merge!( Hash[ *keys.zip(vals).flatten ] )
You could redefine []= to support this:
class Hash
def []=(*args)
*keys, vals = args # if this doesn't work in your version of ruby, use "keys, vals = args[0...-1], args.last"
merge! Hash[keys.zip(vals.respond_to?(:each) ? vals : [vals])]
end
end
Now use
myhash[:key1, :key2, :key3] = :val1, :val2, :val3
# or
myhash[:key1, :key2, :key3] = some_method_returning_three_values
# or even
*myhash[:key1, :key2, :key3], local_var = some_method_returning_four_values
you can do this
def some_method
# some code that return this:
[{:key1 => 1, :key2 => 2, :key3 => 3}, 145]
end
hash, key = some_method
puts hash
#=> {:key1 => 1, :key2 => 2, :key3 => 3}
puts key
#=> 145
UPD
In Ruby you can do "parallel assignment", but you can't use hashes like you do in Perl (hash{:a, :b, :c)). But you can try this:
hash[:key1], hash[:key2], hash[:key3], key4 = some_method
where some_method returns an Array with 4 elements.

How to sort an array in Ruby to a particular order?

I want to sort an array in particular order given in another array.
EX: consider an array
a=["one", "two", "three"]
b=["two", "one", "three"]
Now I want to sort array 'a' in the order of 'b', i.e
a.each do |t|
# It should be in the order of 'b'
puts t
end
So the output should be
two
one
three
Any suggestions?
Array#sort_by is what you're after.
a.sort_by do |element|
b.index(element)
end
More scalable version in response to comment:
a=["one", "two", "three"]
b=["two", "one", "three"]
lookup = {}
b.each_with_index do |item, index|
lookup[item] = index
end
a.sort_by do |item|
lookup.fetch(item)
end
If b includes all elements of a and if elements are unique, then:
puts b & a
Assuming a is to be sorted with respect to order of elements in b
sorted_a =
a.sort do |e1, e2|
b.index(e1) <=> b.index(e2)
end
I normally use this to sort error messages in ActiveRecord in the order of appearance of fields on the form.

Populate ruby Array1 with Array2 String element if and only if Array2 element matches Hash value(not key)

I have a ruby hash:
VALS = { :one => "One", :two => "Two" }
and an Array:
array2 = ["hello", "world", "One"]
Question: How can I populate a new array1 so that it only pulls in any values in array2 that match exactly the values in VALS?
For example, I have tried:
array2.each_with_index do |e,i|
array1 << e if VALS[i] ~= e
end
Along with other thing, and none work. Noob.
Thanks
brilliant! but whent I tried:
p array.select { |i| hash.has_value? i ? array[i+1] : "foo"}
I got an can't convert fixnum error. I must be missing something.
Using nested loops would be very slow if both collections are large. It's better to treat the contents as sets:
array1 = VALS.values & array2
print array1
Output:
One
Here's an option:
hash = { :one => "One", :two => "Two" }
array = ["hello", "world", "One"]
p array.select { |i| hash.has_value? i }
# >> ["One"]
got it!
array.select do |i|
if VALS.has_value? i
result << array[array.index(i)+1]
end
end

Resources