Convert string values to Hash - ruby

I have a string that I need to convert into a hash. The string's key will always be a symbol and the value will always be an integer:
"a=1, b=2, c=3, d=4"
This string should return a hash that looks like:
{ :a => 1, :b => 2, :c => 3, :d => 4 }
I've tried several different things, but the closest I've been able to come so far is to split the string twice, first for the comma and space, second for the equal sign, and create symbols:
def str_to_hash(str)
array = str.split(', ').map{|str| str.split('=').map{|k, v| [k.to_sym, v] }}
end
I'd expected the following output:
{:a=>1, :b=>2, :c=>3, :d=>4}
Instead I got:
[[[:a, nil], [:"1", nil]], [[:b, nil], [:"2", nil]], [[:c, nil], [:"3", nil]], [[:d, nil], [:"4", nil]]]
As you can see, it is creating 8 separate strings with 4 symbols. I can't figure out how to make Ruby recognize the numbers and set them as the values in the key/value pair. I've looked online and even asked my coworkers for help, but haven't found an answer so far. Can anybody help?

Try this I think it looks a little cleaner
s= "a=1, b=2, c=3, d=4"
Hash[s.scan(/(\w)=(\d)/).map{|a,b| [a.to_sym,b.to_i]}]
Here is the inner workings
#utilize scan with capture groups to produce a multidimensional Array
s.scan(/(\w)=(\d)/)
#=> [["a", "1"], ["b", "2"], ["c", "3"], ["d", "4"]]
#pass the inner Arrays to #map an replace the first item with a sym and the second to Integer
.map{|a,b| [a.to_sym,b.to_i]}
#=> [[:a, 1], [:b, 2], [:c, 3], [:d, 4]]
#Wrap the whole thing in Hash::[] syntax to convert
Hash[s.scan(/(\w)=(\d)/).map{|a,b| [a.to_sym,b.to_i]}]
#=> {:a=>1, :b=>2, :c=>3, :d=>4}
If you want to avoid the Hash::[] method which I have always though was ugly you can do the following
#Ruby >= 2.1 you can use Array#to_h
s.scan(/(\w)=(\d)/).map{|a,b| [a.to_sym,b.to_i]}.to_h
#=> {:a=>1, :b=>2, :c=>3, :d=>4}
#Ruby < 2.1 you can use Enumerable#each_with_object
s.scan(/(\w)=(\d)/).each_with_object({}){|(k,v),obj| obj[k.to_sym] = v.to_i}
#=> {:a=>1, :b=>2, :c=>3, :d=>4}
Although there are a ton of other ways to handle this issue as is evident by the many other answers here is one more just for fun.
Hash[*s.scan(/(\w)=(\d)/).flatten.each_with_index.map{|k,i| i.even? ? k.to_sym : k.to_i}]

> s = "a=1, b=2, c=3, d=4"
=> "a=1, b=2, c=3, d=4"
> Hash[s.split(",").map(&:strip).map { |p| p.split("=") }.map { |k, v| [ k.to_sym, v.to_i ] }]
=> {:a=>1, :b=>2, :c=>3, :d=>4}
Part of the problem is that you're trying to do it in a single line and losing track of what the intermediate values are. Break it down into each component, make sure you're using what Ruby gives you, etc.
Your naming assumes you get an array back (not a hash). Hash[...], however, will create a hash based on an array of [key, value] pairs. This makes manual hash stuffing go away. Also, that method should return a hash, not set something–keep methods small, and pure.
Note I strip the first set of split values. This avoids symbols like :" a", which you get if you don't trim leading/trailing spaces. My code does not take strings like "a = 1" into account–yours should.
First, make things readable. Then, if (and only if) it makes sense, and remains legible, play code golf.
> s = "a=1, b=2, c=3, d=4"
=> "a=1, b=2, c=3, d=4"
> a1 = s.split(",")
=> ["a=1", " b=2", " c=3", " d=4"]
> a2 = a1.map(&:strip)
=> ["a=1", "b=2", "c=3", "d=4"]
> a3 = a2.map { |s| s.split("=") }
=> [["a", "1"], ["b", "2"], ["c", "3"], ["d", "4"]]
> a4 = a3.map { |k, v| [ k.to_sym, v.to_i ] }
=> [[:a, 1], [:b, 2], [:c, 3], [:d, 4]]
> Hash[a4]
=> {:a=>1, :b=>2, :c=>3, :d=>4}
Unrelated, but if you're doing a lot of ETL with Ruby, especially on plain text, using mixins can make code much cleaner, closer to a DSL. You can play horrible games, too, like:
> def splitter(sym, s)
String.send(:define_method, sym) do
split(s).map(&:strip)
end
end
> s = "a=1, b=2, c=3, d=4"
> splitter :split_comma, ","
> splitter :split_eq, "-"
> Hash[s.split_comma.map(&:split_eq).map { |k, v| [ k.to_sym, v.to_i ]}]
=> {:a=>1, :b=>2, :c=>3, :d=>4}
It can get significantly worse than this and become a full-fledged ETL DSL. It's great if you need it, though.

Have you tried Hash['a',1,'b',2,'c',3] ??
On your irb terminal it should give this => {"a"=>1, "b"=>2, "c"=>3}
So all that you can do is split the string and give it to Hash which will do the job for you.
Hash[s.split(",").map(&:strip).map{|p| x = p.split("="); [x[0].to_sym, x[1]]}]
Hope that helps

a little hackhish but it works, you can take it from here :-)
h = {}
"a=1, b=2, c=3, d=4".split(',').each do |fo|
k = fo.split('=').first.to_sym
v = fo.split('=').last
h[k] = v
end
puts h.class.name
puts h

My solution:
string = "a=1, b=2, c=3, d=4"
hash = {}
string.split(',').each do |pair|
key,value = pair.split(/=/)
hash[key] = value
end
puts hash.inspect
Despite from not being a one-linner it's a readable solution.

Related

Easily map over Hash like Array#map

Sometimes I want to map over a collection.
If it's an array it's easy:
foo = [1,2,3]
foo.map {|v| v + 1}
#=> [2, 3, 4]
But a hash doesn't work the same way:
bar = {a: 1, b: 2, c: 3}
bar.map{|k,v| v+1}
#=> [2, 3, 4]
What I'd really like is something like:
bar = {a: 1, b: 2, c: 3}
bar.baz{|k,v| v+1}
#=> {:a=>2, :b=>3, :c=>4}
where Hash#baz is some method. Is there an easy way to get a "map-like" experience for a hash?
In Ruby 2.4 you can use the built-in Hash#transform_values:
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.transform_values {|v| v+1 }
# => {:a=>2, :b=>3, :c=>4}
Just to point out the obvious and most common solution to address this need:
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.map { |k, v| [k, v + 1] }.to_h
# => {:a=>2, :b=>3, :c=>4}
From the ruby-forum, you can use Hash#merge to merge the hash with itself:
bar = {a: 1, b: 2, c: 3}
#=> {:a=>1, :b=>2, :c=>3}
bar.merge(bar){|k,v| v+1}
#=> {:a=>2, :b=>3, :c=>4}
One of the great things about Ruby is if you don't like what's in the Ruby core you can always go and extend it to fill in the missing pieces:
class Hash
def map_values
map do |k,v|
[ k, yield(k, v) ]
end.to_h
end
end
Which gives you the thing you wanted:
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.map_values{ |k,v| v+1 }
# => {:a=>2, :b=>3, :c=>4}
I'm surprised this hasn't been introduced into core Ruby, but it might be in the future.
Update: As Eric points out, transform_values is now in Ruby 2.4.0. This is also in ActiveSupport if you're using Rails 4.2 or later.
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.transform_values{ |v| v+1 }
# => {:a=>2, :b=>3, :c=>4}
bar = {a: 1, b: 2, c: 3}
bar.merge(bar) { |*,v| v+1 }
#=> {:a=>2, :b=>3, :c=>4}
This uses the form of Hash.merge that employs a block to return the values of keys that are present in both hashes being merged, which here is all keys.
Another way:
bar.keys.each { |k| bar[k] += 1 }
bar
#=> {:a=>2, :b=>3, :c=>4}
which can be written in one line using Object#tap:
bar.tap { |h| h.keys.each { |k| h[k] += 1 } }
#=> {:a=>2, :b=>3, :c=>4}

print value of key in nested hash in ruby without loop

irb(main):024:0> outer_hash={:x=>in_hash, :y=>another_in_hash}
=> {:x=>{:a=>1, :b=>2, :c=>3}, :y=>{:l=>4, :m=>5, :n=>6}}
I wish to print inner hash key and value without loop.
Answering the exact question that was stated:
▶ hash.inspect.gsub(/:\w+=>{/, '').gsub(/[{}:>]/, '')
#⇒ "a=1, b=2, c=3, l=4, m=5, n=6"
I think you are asking for something like this:
>> outer_hash.map { |_,v| v }
=> [{:a=>1, :b=>2, :c=>3}, {:l=>4, :m=>5, :n=>6}]
or this:
>> outer_hash.map { |_,v| v.to_a }
=> [[[:a, 1], [:b, 2], [:c, 3]], [[:l, 4], [:m, 5], [:n, 6]]]

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}

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

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}

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