Related
I'm trying to use the map function to combine an array of arrays into an array of arrays that eliminate a unique value in arr[0][0] but pull arr[0][1] and group it with the corresponding unique value.
arr = [[a, 1], [a, 2], [b,3], [b, 4]]
=> [[a, [1, 2]], [b, [3,4]]]
I'm sure this is pretty basic but I'm rather new to coding in general. Thank you for your help.
Try this:
arr = [[:a, 1], [:a, 2], [:b, 3], [:b, 4]]
arr.group_by(&:first).map { |k, v| [k, v.map(&:last)] }
#=> [[:a, [1, 2]], [:b, [3, 4]]]
Depending on what your goal is, you might want to turn the result into a hash:
Hash[arr.group_by(&:first).map { |k, v| [k, v.map(&:last)] }]
#=> {:a=>[1, 2], :b=>[3, 4]}
I have a an array as follows:
[[172, 3],
[173, 1],
[174, 2],
[174, 3],
[174, 1]]
That I'd like to convert into an array, but while summing the values for matching keys. So I'd get the following:
{172 => 3, 173 => 1, 174 => 6}
How would I go about doing this?
How would I go about doing this?
Solve one problem at a time.
Given your array:
a = [[172, 3], [173, 1], [174, 2], [174, 3], [174, 1]]
We need an additional hash:
h = {}
Then we have to traverse the pairs in the array:
a.each do |k, v|
if h.key?(k) # If the hash already contains the key
h[k] += v # we add v to the existing value
else # otherwise
h[k] = v # we use v as the initial value
end
end
h #=> {172=>3, 173=>1, 174=>6}
Now let's refactor it. The conditional looks a bit cumbersome, what if we would just add everything?
h = {}
a.each { |k, v| h[k] += v }
#=> NoMethodError: undefined method `+' for nil:NilClass
Bummer, that doesn't work because the hash's values are initially nil. Let's fix that:
h = Hash.new(0) # <- hash with default values of 0
a.each { |k, v| h[k] += v }
h #=> {172=>3, 173=>1, 174=>6}
That looks good. We can even get rid of the temporary variable by using each_with_object:
a.each_with_object(Hash.new(0)) { |(k, v), h| h[k] += v }
#=> {172=>3, 173=>1, 174=>6}
You can try something about:
> array
#=> [[172, 3], [173, 1], [174, 2], [174, 3], [174, 1]]
array.group_by(&:first).map { |k, v| [k, v.map(&:last).inject(:+)] }.to_h
#=> => {172=>3, 173=>1, 174=>6}
Ruby 2.4.0 version:
a.group_by(&:first).transform_values { |e| e.sum(&:last) }
#=> => {172=>3, 173=>1, 174=>6}
For:
array = [[172, 3], [173, 1], [174, 2], [174, 3], [174, 1]]
You could use a hash with a default value 0 like this
hash = Hash.new{|h,k| h[k] = 0 }
Then use it, and sum up values:
array.each { |a, b| hash[a] += b }
#=> {172=>3, 173=>1, 174=>6}
Another possible solution:
arr = [[172, 3], [173, 1], [174, 2], [174, 3], [174, 1]]
hash = arr.each_with_object({}) {|a,h| h[a[0]] = h[a[0]].to_i + a[1]}
p hash
# {172=>3, 173=>1, 174=>6}
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}
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.
I have two arrays,
a = [1, 2]
b = [:a]
I want to get the result as
[[1, :a], [2, :a]]
Is there any methods for this?
Use the Array#product:
a = [1, 2]
b = [:a]
a.product(b)
=> [[1, :a], [2, :a]]
Also you can do it this way
[a,b*a.size].transpose
#=> [[1, :a], [2, :a]]