I am new to Ruby and trying to find if I can create a hash which has keys as keys_Array and values as val_array.
Currently I have the following but it gives empty array.
key_hash = Hash.new { |hash, key|
hash.key = ["#{csv['values']} #{csv['keys']}"]
}
p key_hash.keys #empty array here
If you're trying to create a hash from two corresponding arrays for keys and values, this is quick way:
keys = ["a", "b", "c"]
values = [1, 2, 3]
hash = Hash[keys.zip(values)]
# => {"a"=>1, "b"=>2, "c"=>3}
# for symbols
hash = Hash[keys.map(&:to_sym).zip(values)]
# => {:a=>1, :b=>2, :c=>3}
If you want to make a new hash from two hashes (one contains keys for new hash and other contains values with respect to keys in first hash then only)
you can use something like below :
keys = ['k1', 'k2', 'k3']
values = ['b1', 'b2']
h = {}
keys.zip(values) { |a,b| h[a.to_sym] = b }
# => nil
p h
# => {:k1=>"b1", :k2=>"b2", :k3=>nil}
Keep in mind that if the keys are more and values are less in number then key will have nil value as mentioned in the e.g. but if the keys are less as compare to values then it will now consider the remaining values for e.g.
keys =['b1', 'b2']
=> ["b1", "b2"]
values = ['k1', 'k2', 'k3']
=> ["k1", "k2", "k3"]
h = {}
=> {}
keys.zip(values) { |a,b| h[a.to_sym] = b }
=> nil
p h
{:b1=>"k1", :b2=>"k2"}
I want to dynamically create a Hash without overwriting keys from an array of arrays. Each array has a string that contains the nested key that should be created. However, I am running into the issue where I am overwriting keys and thus only the last key is there
data = {}
values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]
What it should look like:
{
"income" => {
"concessions" => 0,
"gross-income" => "900000"
},
"expenses" => {
"admin" => "7500",
"other" => "0"
}
"noi" => "722300",
"purpose" => "refinancing",
"fees" => {
"fee-one" => 0,
"fee-two" => 0
},
"address" => {
"zip" => "10019"
}
}
This is the code that I currently, have how can I avoid overwriting keys when I merge?
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = data.merge!(hh)
end
end
end
The code you've provided can be modified to merge hashes on conflict instead of overwriting:
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
data.merge!(hh) { |_, old, new| old.merge(new) }
end
end
end
But this code only works for the two levels of nesting.
By the way, I noted ruby-on-rails tag on the question. There's deep_merge method that can fix the problem:
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
data.deep_merge!(hh)
end
end
end
values.flatten.each_slice(2).with_object({}) do |(f,v),h|
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
end
#=> {"income"=>{"concessions"=>0, "gross-income"=>"900000"},
# "noi"=>"722300",
# "purpose"=>"refinancing",
# "fees"=>{"fee-one"=>"0", "fee-two"=>"0"},
# "expenses"=>{"admin"=>"7500", "other"=>"0"},
# "address"=>{"zip"=>"10019"}}
The steps are as follows.
values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]
a = values.flatten
#=> ["income:concessions", 0, "noi", "722300", "purpose", "refinancing",
# "fees:fee-one", "0", "income:gross-income", "900000", "expenses:admin", "7500",
# "fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
enum1 = a.each_slice(2)
#=> #<Enumerator: ["income:concessions", 0, "noi", "722300",
# "purpose", "refinancing", "fees:fee-one", "0", "income:gross-income", "900000",
# "expenses:admin", "7500", "fees:fee-two", "0", "address:zip", "10019",
# "expenses:other","0"]:each_slice(2)>
We can see what values this enumerator will generate by converting it to an array.
enum1.to_a
#=> [["income:concessions", 0], ["noi", "722300"], ["purpose", "refinancing"],
# ["fees:fee-one", "0"], ["income:gross-income", "900000"],
# ["expenses:admin", "7500"], ["fees:fee-two", "0"],
# ["address:zip", "10019"], ["expenses:other", "0"]]
Continuing,
enum2 = enum1.with_object({})
#=> #<Enumerator: #<Enumerator:
# ["income:concessions", 0, "noi", "722300", "purpose", "refinancing",
# "fees:fee-one", "0", "income:gross-income", "900000", "expenses:admin", "7500",
# "fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
# :each_slice(2)>:with_object({})>
enum2.to_a
#=> [[["income:concessions", 0], {}], [["noi", "722300"], {}],
# [["purpose", "refinancing"], {}], [["fees:fee-one", "0"], {}],
# [["income:gross-income", "900000"], {}], [["expenses:admin", "7500"], {}],
# [["fees:fee-two", "0"], {}], [["address:zip", "10019"], {}],
# [["expenses:other", "0"], {}]]
enum2 can be thought of as a compound enumerator (though Ruby has no such concept). The hash being generated is initially empty, as shown, but will be filled in as additional elements are generated by enum2
The first value is generated by enum2 and passed to the block, and the block values are assigned values by a process called array decomposition.
(f,v),h = enum2.next
#=> [["income:concessions", 0], {}]
f #=> "income:concessions"
v #=> 0
h #=> {}
We now perform the block calculation.
f.is_a?(String)
#=> true
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
#=> ["income", "concessions"]
e.nil?
#=> false
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
#=> {"concessions"=>0}
h[k] equals nil if h does not have a key k. In that case (h[k] || {}) #=> {}. If h does have a key k (and h[k] in not nil).(h[k] || {}) #=> h[k].
A second value is now generated by enum2 and passed to the block.
(f,v),h = enum2.next
#=> [["noi", "722300"], {"income"=>{"concessions"=>0}}]
f #=> "noi"
v #=> "722300"
h #=> {"income"=>{"concessions"=>0}}
Notice that the hash, h, has been updated. Recall it will be returned by the block after all elements of enum2 have been generated. We now perform the block calculation.
f.is_a?(String)
#=> true
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
#=> ["noi"]
e #=> nil
e.nil?
#=> true
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
#=> "722300"
h #=> {"income"=>{"concessions"=>0}, "noi"=>"722300"}
The remaining calculations are similar.
merge overwrites a duplicate key by default.
{ "income"=> { "concessions" => 0 } }.merge({ "income"=> { "gross-income" => "900000" } } completely overwrites the original value of "income". What you want is a recursive merge, where instead of just merging the top level hash you're merging the nested values when there's duplication.
merge takes a block where you can specify what to do in the event of duplication. From the documentation:
merge!(other_hash){|key, oldval, newval| block} → hsh
Adds the contents of other_hash to hsh. If no block is specified, entries with duplicate keys are overwritten with the values from other_hash, otherwise the value of each duplicate key is determined by calling the block with the key, its value in hsh and its value in other_hash
Using this you can define a simple recursive_merge in one line
def recursive_merge!(hash, other)
hash.merge!(other) { |_key, old_val, new_val| recursive_merge!(old_val, new_val) }
end
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = recursive_merge!(data, hh)
end
end
end
A few more lines will give you a more robust solution, that will overwrite duplicate keys that are not hashes and even take a block just like merge
def recursive_merge!(hash, other, &block)
hash.merge!(other) do |_key, old_val, new_val|
if [old_val, new_val].all? { |v| v.is_a?(Hash) }
recursive_merge!(old_val, new_val, &block)
elsif block_given?
block.call(_key, old_val, new_val)
else
new_val
end
end
end
h1 = { a: true, b: { c: [1, 2, 3] } }
h2 = { a: false, b: { x: [3, 4, 5] } }
recursive_merge!(h1, h2) { |_k, o, _n| o } # => { a: true, b: { c: [1, 2, 3], x: [3, 4, 5] } }
Note: This method reproduces the results you would get from ActiveSupport's Hash#deep_merge if you're using Rails.
This is how I would handle this:
def new_h
Hash.new{|h,k| h[k] = new_h}
end
values.flatten.each_slice(2).each_with_object(new_h) do |(k,v),obj|
keys = k.is_a?(String) ? k.split(':') : [k]
if keys.count > 1
set_key = keys.pop
obj.merge!(keys.inject(new_h) {|memo,k1| memo[k1] = new_h})
.dig(*keys)
.merge!({set_key => v})
else
obj[k] = v
end
end
#=> {"income"=>{
"concessions"=>0,
"gross-income"=>"900000"},
"noi"=>"722300",
"purpose"=>"refinancing",
"fees"=>{
"fee-one"=>"0",
"fee-two"=>"0"},
"expenses"=>{
"admin"=>"7500",
"other"=>"0"},
"address"=>{
"zip"=>"10019"}
}
Explanation:
Define a method (new_h) for setting up a new Hash with default new_h at any level (Hash.new{|h,k| h[k] = new_h})
First flatten the Array (values.flatten)
then group each 2 elements together as sudo key value pairs (.each_slice(2))
then iterate over the pairs using an accumulator where each new element added is defaulted to a Hash (.each_with_object(new_h.call) do |(k,v),obj|)
split the sudo key on a colon (keys = k.is_a?(String) ? k.split(':') : [k])
if there is a split then create the parent key(s) (obj.merge!(keys.inject(new_h.call) {|memo,k1| memo[k1] = new_h.call}))
merge the last child key equal to the value (obj.dig(*keys.merge!({set_key => v}))
other wise set the single key equal to the value (obj[k] = v)
This has infinite depth as long as the depth chain is not broken say [["income:concessions:other",12],["income:concessions", 0]] in this case the latter value will take precedence (Note: this applies to all the answers in one way or anther e.g. the accepted answer the former wins but a value is still lost dues to inaccurate data structure)
repl.it Example
["one", "two", "three", "three"]
I want to open a file and write
{"one" => 1, "two" => 1, "three" => 2}
["one", "two"]
and in the next time open the same file and search for the each word if exsit append + 1 else create new word
{"one" => 2, "two" => 2, "three" => 2}
This should do :
hash = ["one", "two", "three", "three"]
frequency_file = 'frequency.dat'
if File.exists?(frequency_file)
old_frequency = File.open(frequency_file) {|f| Marshal.load(f.read)}
else
old_frequency = {}
end
old_frequency.default = 0
frequency = hash.group_by{|name| name}.map{|name, list| [name,list.count+old_frequency[name]]}.to_h
File.open(frequency_file,'w'){|f| f.write(Marshal.dump(frequency))}
puts frequency.inspect
# => {"one"=>1, "two"=>1, "three"=>2}
# => {"one"=>2, "two"=>2, "three"=>4}
If you prefer a human-readable file :
require 'yaml'
hash = ["one", "two", "three", "three"]
frequency_file = 'frequency.yml'
if File.exists?(frequency_file)
old_frequency = YAML.load_file(frequency_file)
else
old_frequency = {}
end
old_frequency.default = 0
frequency = hash.group_by{|name| name}.map{|name, list| [name,list.count+old_frequency[name]]}.to_h
File.open(frequency_file,'w'){|f| f.write frequency.to_yaml}
puts frequency.inspect
# => {"one"=>1, "two"=>1, "three"=>2}
# => {"one"=>2, "two"=>2, "three"=>4}
Here are some variations that'd do it:
ary = %w[a b a c a b]
ary.group_by { |v| v }.map{ |k, v| [k, v.size] }.to_h # => {"a"=>3, "b"=>2, "c"=>1}
ary.each_with_object(Hash.new(0)) { |v, h| h[v] += 1} # => {"a"=>3, "b"=>2, "c"=>1}
ary.uniq.map { |v| [v, ary.count(v)] }.to_h # => {"a"=>3, "b"=>2, "c"=>1}
Since they're all about the same length it becomes important to know which is the fastest.
require 'fruity'
ary = %w[a b a c a b] * 1000
compare do
group_by { ary.group_by { |v| v }.map{ |k, v| [k, v.size] }.to_h }
each_with_object { ary.each_with_object(Hash.new(0)) { |v, h| h[v] += 1} }
uniq_map { ary.uniq.map { |v| [v, ary.count(v)] }.to_h }
end
# >> Running each test 4 times. Test will take about 1 second.
# >> group_by is faster than uniq_map by 30.000000000000004% ± 10.0%
# >> uniq_map is faster than each_with_object by 19.999999999999996% ± 10.0%
How to persist the data and append to it is a separate question and how to do it depends on the size of the data you're checking, and how fast you need the code to run. Databases are very capable of doing these sort of checks extremely fast as they have code optimized to search and count unique occurrences of records. Even SQLite should have no problem doing this. Using an ORM like Sequel or ActiveRecord would make it painless to talk to the DB and to scale or port to a more capable database manager if needed.
Writing to a local file is OK if you occasionally need to update, or you don't have a big list of words, and you don't need to share the information with other pieces of code or with another machine.
Reading a file to recover the hash then incrementing it assumes a word will never be deleted, they'll only be added. I've written a lot of document analysis code and that case hasn't occurred, so I'd recommend thinking about long-term use before settling on your particular path.
Could you put the string representation of a hash (the first line of the file) in a separate (e.g., JSON) file? If so, consider something like the following.
First let's create a JSON file for the hash and a second file, the words of which are to be counted.
require 'json'
FName = "lucy"
JSON_Fname = "hash_counter.json"
File.write(JSON_Fname, JSON.generate({"one" => 1, "two" => 1, "three" => 2}))
#=> 27
File.write(FName, '["one", "two", "three", "three"]')
#=>32
First read the JSON file, parse the hash and give h a default value of zero.1.
h = JSON.parse(File.read(JSON_Fname))
#=> {"one"=>1, "two"=>1, "three"=>2}
h.default = 0
(See Hash#default=). Then read the other file and update the hash.
File.read(FName).downcase.scan(/[[:alpha:]]+/).each { |w| h[w] += 1 }
h #=> {"one"=>2, "two"=>2, "three"=>4}
Lastly, write the hash h to the JSON file (as I did above).2
1 Ruby expands h[w] += 1 to h[w] = h[w] + 1 before parsing the expression. If h does not have a key w, Hash#[] returns the hash's default value, if it has one. Here h["cat"] #=> 0 since h has no key "cat" and the default has been set to zero. The expression therefore becomes h[w] = 0 + 1 #=> 1. Note that the method on the left of the equality is Hash#[]=, which is why the default value does not apply there.
2 To be safe, write the new JSON string to a temporary file, delete the JSON file, then rename the temporary file to the former JSON file name.
I have a Ruby array whose elements alternate between Strings and Hashes. For example-
["1234", Hash#1, "5678", Hash#2]
I would like to create a nested hash structure from this. So,
hash["1234"]["key in hash#1"] = value
hash["5678"]["key in hash#2"] = value
Does anyone have/now a nice way of doing this? Thank you.
Simply use
hsh = Hash[*arr] #suppose arr is the array you have
It will slice 2 at a time and convert into hash.
I don't think there is a method on array to do this directly. The following code works and is quite easy to read.
hsh = {}
ary.each_slice(2) do |a, b|
hsh[a] = b
end
# Now `hsh` is as you want it to be
Guessing at what you want, since "key in hash#1" is not clear at all, nor have you defined what hash or value should be:
value = 42
h1 = {a:1}
h2 = {b:2}
a = ["1234",h1,"5678",h2]
a.each_slice(2).each{ |str,h| h[str] = value }
p h1, #=> {:a=>1, "1234"=>42}
h2 #=> {:b=>2, "5678"=>42}
Alternatively, perhaps you mean this:
h1 = {a:1}
h2 = {b:2}
a = ["1234",h1,"5678",h2]
hash = Hash[ a.each_slice(2).to_a ]
p hash #=> {"1234"=>{:a=>1}, "5678"=>{:b=>2}}
p hash["1234"][:a] #=> 1
let's guess, using facets just for fun:
require 'facets'
xs = ["1234", {:a => 1, :b => 2}, "5678", {:c => 3}]
xs.each_slice(2).mash.to_h
#=> {"1234"=>{:a=>1, :b=>2}, "5678"=>{:c=>3}}