Array in value of hash - ruby

How to push inputs into a value of a hash? My problem is that I got multiple keys and all of them reference arrays.
{"A"=>["C"], "B"=>["E"], "C"=>["D"], "D"=>["B"]}
How can I push another String onto one of these? For example I want to add a "Z" to the array of key "A"?
Currently I either overwrite the former array or all data is in one.
Its about converting a Array ["AB3", "DC2", "FG4", "AC1", "AF4"] into a hash with {"A"=>["B", "C", "F"]}.

Any command <<, push, unshift will do a job
if h["A"]
h["A"] << "Z"
else
h["A"] = ["Z"]
end

You said your original problem is converting the array ["AB3", "DC2", "FG4", "AC1", "AF4"] into the hash {"A"=>["B", "C", "F"]}, which can be done like this:
Hash[a.group_by { |s| s[0] }.map { |k, v| [k, v.map { |s| s[1] }] }]
Or like this:
a.inject(Hash.new{|h, k| h[k]=[]}) { |h, s| h[s[0]] << s[1] ; h }
Note that Hash.new{|h, k| h[k]=[]} creates an array with a default value of [] (an empty array), so you'll always be able to use << to add elements to it.

Better approach:
Add a new class method in Hash as below:
class Hash
def add (k,v)
unless self.key?k
self[k] = [v]
else
self[k] = self[k] << v
end
self
end
end
h={}
h.add('A','B') #=> {"A"=>["B"]}
h.add('A','C') #=> {"A"=>["B", "C"]}
h.add('B','X') #=> {"A"=>["B", "C"], "B"=>["X"]}
Done.
This can be even more idiomatic according to your precise problem; say, you want to send multiple values at once, then code can be DRY-ed to handle multiple arguments.
Hope this helps.
All the best.

Related

How to generate the expected output by using split method used in my code?

Question:
Create a method for Array that returns a hash having 'key' as length of the element and value as an array of all the elements of that length. Make use of Array#each.
Returned Hash should be sorted by key.
I have tried to do it through Hash sorting over length. I have almost resolved it using another method but I want to use split and hash to achieve expected output.
Can anyone suggest any amendments in my code below?
Input argument:
array-hash.rb "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
Expected output:
{1=>["x", "5"], 3=>["abc", "def", "234"], 4=>["1234", "abcd", "mnop", "zZzZ"]}
class String
def key_length(v2)
hash = {}
v2.each do |item|
item_length = item.to_s.length
hash[item_length] ||= []
hash[item_length].push(item)
end
Hash[hash.sort]
end
end
reader = ''
if ARGV.empty?
puts 'Please provide an input'
else
v1 = ARGV[0]
v2 = v1.tr("'[]''",'').split
p reader.key_length(v2)
end
Actual output:
{35=>["abc,def,1234,234,abcd,x,mnop,5,zZzZ"]}
Given the array (converted from string, note integers as string between ""):
ary = str[1..-2].delete('\'').split(',')
ary #=> ["abc", "def", "1234", "234", "abcd", "x", "mnop", "5", "zZzZ"]
The most "idiomatic" way should be using group_by:
ary.group_by(&:size)
If you want to use each, then you could use Enumerable#each_with_object, where the object is an Hash#new with an empty array as default:
ary.each_with_object(Hash.new{ |h,k| h[k] = []}) { |e, h| h[e.size] << e }
Which is the same as
res = Hash.new{ |h,k| h[k] = []}
ary.each { |e| res[e.size] << e }
Not sure why you need to monkeypatch* array here, is this a school exercise or something?
I think your bug is you need to pass in the comma delimiter arg to split.
I would solve the underlying problem as a reduce/inject/fold thing, myself.
s = "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
splits = s.tr("'[]''",'').split(',') # need to pass in the comma for the split
Hash[splits.inject({}) { |memo,s| memo[s.length] ||= []; memo[s.length] << s; memo }.sort] # doesn't use Array.each but?
{1=>["x", "5"], 3=>["def", "234"], 4=>["1234", "abcd", "mnop"],
5=>["['abc"], 6=>["zZzZ']"]}

Inverting a hash: Making multiple hash keys from an array

I'm trying to get a hash that has an array of values inverted such that the keys are now values. From an expression like this:
StackOverflow.transform({ 1 => ['A', 'E'] , 2 => ["B"]})
I'm trying to get this result:
{"A"=>1, "E"=>1, "B"=>2}
I have this:
class StackOverflow
def self.transform(old)
a = Hash[old.map { |k,v| v.product([k]) }.first]
end
end
but the keys are all separated as individual keys (not grouped). It returns:
{"A"=>1, "E"=>1}
I'm also trying to downcase the keys, but I feel like after I figure out this inversion issue properly, I'll be able to (hopefully?) figure out the downcasing logic as well.
You were very close. You want to use flat_map instead of first.
class StackOverflow
def self.transform(old)
Hash[old.flat_map { |k,v| v.product([k]) }]
end
end
You were using first to flatten the array.
Another way:
class StackOverflow
def self.transform(old)
val = nil
old.each_with_object(Hash.new { |h,k| h[k]=val }) do |(k,v),h|
val = k
h.values_at(*v)
end
end
end
old = { 1=>['A', 'E'], 2=>['B'] }
StackOverflow.transform(old)
#=> {"A"=>1, "E"=>1, "B"=>2}

How to merge array index values and create a hash

I'm trying to convert an array into a hash by using some matching. Before converting the array into a hash, I want to merge the values like this
"Desc,X1XXSC,C,CCCC4524,xxxs,xswd"
and create a hash from it. The rule is that, first value of the array is the key in Hash, in array there are repeating keys, for those keys I need to merge values and place it under one key. "Desc:" are keys. My program looks like this.
p 'test sample application'
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
arr = Array.new
arr = str.split(":")
p arr
test_hash = Hash[*arr]
p test_hash
I could not find a way to figure it out. If any one can guide me, It will be thankful.
Functional approach with Facets:
require 'facets'
str.split(":").each_slice(2).map_by { |k, v| [k, v] }.mash { |k, vs| [k, vs.join] }
#=> {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
Not that you cannot do it without Facets, but it's longer because of some basic abstractions missing in the core:
Hash[str.split(":").each_slice(2).group_by(&:first).map { |k, gs| [k, gs.map(&:last).join] }]
#=> {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
A small variation on #Sergio Tulentsev's solution:
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
str.split(':').each_slice(2).each_with_object(Hash.new{""}){|(k,v),h| h[k] += v}
# => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
str.split(':') results in an array; there is no need for initializing with arr = Array.new
each_slice(2) feeds the elements of this array two by two to a block or to the method following it, like in this case.
each_with_object takes those two elements (as an array) and passes them on to a block, together with an object, specified by:
(Hash.new{""}) This object is an empty Hash with special behaviour: when a key is not found then it will respond with a value of "" (instead of the usual nil).
{|(k,v),h| h[k] += v} This is the block of code which does all the work. It takes the array with the two elements and deconstructs it into two strings, assigned to k and v; the special hash is assigned to h. h[k] asks the hash for the value of key "Desc". It responds with "", to which "X1" is added. This is repeated until all elements are processed.
I believe you're looking for each_slice and each_with_object here
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
hash = str.split(':').each_slice(2).each_with_object({}) do |(key, value), memo|
memo[key] ||= ''
memo[key] += value
end
hash # => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
Enumerable#slice_before is a good way to go.
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
a = ["Desc","C","xxxs"] # collect the keys in a separate collection.
str.split(":").slice_before(""){|i| a.include? i}
# => [["Desc", "X1"], ["C", "CCCC"], ["Desc", "XXSC"], ["xxxs", "xswd"], ["C", "4524"]]
hsh = str.split(":").slice_before(""){|i| a.include? i}.each_with_object(Hash.new("")) do |i,h|
h[i[0]] += i[1]
end
hsh
# => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}

How to convert the values of a hash from String to Array in Ruby?

I'm looking to perform a conversion of the values in a Ruby hash from String to Integer.
I thought this would be fairly similar to the way you perform a conversion in a Ruby array (using the map method), but I have not been able to find an elegant solution that doesn't involve converting the hash to an array, flattening it, etc.
Is there a clean solution to do this?
Eg. From
x = { "a" => "1", "b" => "2", "c"=> "3" }
To
x = { "a" => 1, "b" => 2, "c" => 3 }
To avoid modifying the original Hash (unlike the existing answers), I'd use
newhash = x.reduce({}) do |h, (k, v)|
h[k] = v.to_i and h
end
If you're using Ruby 1.9, you can also use Enumerable#each_with_object to achieve the same effect a bit more cleanly.
newhash = x.each_with_object({}) do |(k, v), h|
h[k] = v.to_i
end
If you want to, you can also extract the logic into a module and extend the Hash class with it.
module HMap
def hmap
self.each_with_object({}) do |(k, v), h|
h[k] = yield(k, v)
end
end
end
class Hash
include HMap
end
Now you can use
newhash = x.hmap { |k, v| v.to_i } # => {"a"=>1, "b"=>2, "c"=>3}
My preferred solution:
Hash[x.map { |k, v| [k, v.to_i]}] #=> {"a"=>1, "b"=>2, "c"=>3}
A somewhat wasteful one (has to iterate over the values twice):
Hash[x.keys.zip(x.values.map(&:to_i))] #=> {"a"=>1, "b"=>2, "c"=>3}
Try this:
x.each{|k,v| x[k]=v.to_i}
p.keys.map { |key| p[key] = p[key].to_i }

How to uniq an array case insensitive

As far as i know, the result of
["a", "A"].uniq
is
["a", "A"]
My question is:
How do I make ["a", "A"].uniq give me either ["a"] or ["A"]
There is another way you can do this. You can actually pass a block to uniq or uniq! that can be used to evaluate each element.
["A", "a"].uniq { |elem| elem.downcase } #=> ["A"]
or
["A", "a"].uniq { |elem| elem.upcase } #=> ["A"]
In this case though, everything will be case insensitive so it will always return the array ["A"]
Just make the case consistent first.
e.g:
["a","A"].map{|i| i.downcase}.uniq
Edit: If as mikej suggests, the elements returned must be exactly the same as in the original array, then this will do that for you:
a.inject([]) { |result,h| result << h unless result.map{|i| i.downcase}.include?(h.downcase); result }
Edit2 Solution which should satisfy mikej :-)
downcased = []
a.inject([]) { |result,h|
unless downcased.include?(h.downcase);
result << h
downcased << h.downcase
end;
result}
you may build a mapping (Hash) between the case-normalized (e.g. downcased) values and the actual value and then take just the values from the hash:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element ; h }\
.values
selects the last occurrence of a given word (case insensitive):
["A", "b", "C"]
if you want the first occurrence:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element unless h[element.downcase] ; h }\
.values
["a", "A"].map{|x| x.downcase}.uniq
=> ["a"]
or
["a", "A"].map{|x| x.upcase}.uniq
=> ["A"]
If you are using ActiveSupport, you can use uniq_by.
It doesn't affect the case of the final output.
['A','a'].uniq_by(&:downcase) # => ['A']
A bit more efficient and way is to make use of uniq keys in hashes, so check this:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] = j; hash}.values
will return the last element, in this case
["A"]
whereas using ||= as assign operator:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] ||= j; hash}.values
will return first element, in this case
["a"]
especially for big Arrays this should be faster as we don't search the array each time using include?
cheers...
A more general solution (though not the most efficient):
class EqualityWrapper
attr_reader :obj
def initialize(obj, eq, hash)
#obj = obj
#eq = eq
#hash = hash
end
def ==(other)
#eq[#obj, other.obj]
end
alias :eql? :==
def hash
#hash[#obj]
end
end
class Array
def uniq_by(eq, hash = lambda{|x| 0 })
map {|x| EqualityWrapper.new(x, eq, hash) }.
uniq.
map {|x| x.obj }
end
def uniq_ci
eq = lambda{|x, y| x.casecmp(y) == 0 }
hash = lambda{|x| x.downcase.hash }
uniq_by(eq, hash)
end
end
The uniq_by method takes a lambda that checks the equality, and a lambda that returns a hash, and removes duplicate objects as defined by those data.
Implemented on top of that, the uniq_ci method removes string duplicates using case insensitive comparisons.

Resources