Understanding Ruby Code - ruby

I need some help understanding the below ruby code.
counted = Hash.new(0)
parsed_reponse["result"]["data"].each { |h| counted[h["version"]] += 1 }
counted = Hash[counted.map {|k,v| [k,v.to_s] }]
I understand line 1 creats a hash which I believe is similar to a
dictionary in python.
Line 2 loops through my json data set a adds a
new key and value pair if none exits and increments the count if 1
does exist.
What does the 3rd line do?

Your last line just converts all values to String:
Hash[{a: 2, b: 3}.map {|k, v| [k, v.to_s]}]
#=> {:a=>"2", :b=>"3"}
I would refactor it to:
counted.transform_values!(&:to_s) # for ruby >= 2.4
#=> {:a=>"1", :b=>"2"}
Or for older versions:
counted.each { |k, v| counted[k] = v.to_s }
Because:
counted.map {|k,v| [k,v.to_s] } - creates new array of arrays
Hash[result] - creates new Hash object from result array.
Both steps are redundant, you can just modify existing hash.

Related

Parse nested indented list with Ruby by using "select_before"

I want to parse the formal list from https://www.loc.gov/marc/bibliographic/ecbdlist.html into a nested structure of hashes and arrays.
At first, I used a recursive approach - but ran into the problem that Ruby (and BTW also Python) can handle only less than 1000 recursive calls (stack level too deep).
I found "select_before" and it seemed great:
require 'pp'
# read list into array and get rid of unnecessary lines
marc = File.readlines('marc21.txt', 'r:utf-8')[0].lines.map(&:chomp).select { |line| line if !line.match(/^\s*$/) && !line.match(/^--.+/) }
# magic starts here
marc = marc.slice_before { |line| line[/^ */].size == 0 }.to_a
marc = marc.inject({}) { |hash, arr| hash = hash.merge( arr[0] => arr[1..-1] ) }
I now want to iterate these steps throughout the array. As the indentation levels in the list vary ([0, 2, 3, 4, 5, 6, 8, 9, 10, 12] not all of them always present), I use a helper method get_indentation_map to use only the smallest amount of indentation in each iteration.
But adding only one level (far from the goal of turning the whole array into the new structure), I get the error "no implicit conversion of Regex into Integer" the reason of which I fail to see:
def get_indentation_map( arr )
arr.map { |line| line[/^ */].size }
end
# starting again after slice_before of the unindented lines (== 0)
marc = marc.inject({}) do |hash, arr|
hash = hash.merge( arr[0] => arr[1..-1] ) # so far like above
# now trying to do the same on the next level
hash = hash.inject({}) do |h, a|
indentation_map = get_indentation_map( a ).uniq.sort
# only slice before smallest indentation
a = a.slice_before { |line| line[/^ */].size == indentation_map[0] }.to_a
h = h.merge( a[0] => a[1..-1] )
end
hash
end
I would be very grateful for hints how to best parse this list. I aim at a json-like structure in which every entry is the key for the further indented lines (if there are). Thanks in advance.

How to compare ruby hash with same key?

I have two hashes like this:
hash1 = Hash.new
hash1["part1"] = "test1"
hash1["part2"] = "test2"
hash1["part3"] = "test3"
hash2 = Hash.new
hash2["part1"] = "test1"
hash2["part2"] = "test2"
hash2["part3"] = "test4"
Expected output: part3
Basically, I want to iterate both of the hashes and print out "part3" because the value for "part3" is different in the hash. I can guarantee that the keys for both hashes will be the same, the values might be different. I want to print out the keys when their values are different?
I have tried iterating both hashes at once and comparing values but does not seem to give the right solution.
The cool thing about Ruby is that it is so high level that it is often basically English:
Print keys from the first hash if the values in the two hashes are different:
hash1.keys.each { |key| puts key if hash1[key] != hash2[key] }
Select the first hash keys that have different values in the two hashes and print each of them:
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key| puts key }
Edit: I'll leave this should it be of interest, but #ndn's solution is certainly better.
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part3"]
hash1["part1"] = "test99"
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part1", "part3"]
This uses the form of Hash#merge that employs a block (here { |_,v1,v2| v1==v2 }) to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the three block variables, _, v1 and v2. The first block variable equals the common key. I've used the local variable _ for that, as is customary when the variable is not used in the block calculation.
The steps (for the original hash1):
g = hash1.merge(hash2) { |_,v1,v2| v1==v2 }
#=> {"part1"=>true, "part2"=>true, "part3"=>false}
h = g.reject { |_,v| v }
#=> {"part3"=>false}
h.keys
#=> ["part3"]
The obvious way is that of ndn, here a solution without blocks by converting to arrays, joining them and subtracting the elements that are the same, followed by converting back to hash and asking for the keys.
Next time it would be better to include what you tried so far.
((hash1.to_a + hash2.to_a) - (hash1.to_a & hash2.to_a)).to_h.keys
# ["part3"]

Ruby array of hashes values to string

I have an array of hashes (#1) that looks like this:
data = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>0}]
that I am trying to compare with following array of hashes (#2):
test = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>"0"}]
where #1 I obtained from database by mysql2 (what actually is in the database)
and #2 from my cucumber scenario (what I minimally expect ot be there).
By definition #2 must be a subset of #1 so I follow with this code:
data = data.to_set
test = test.to_set
assert test.subset?(data)
The problem is in data array the value of active is NOT a string. In case of data it is Fixnum, and in case of test, it is String.
I need a solution that will work even for more than one hash in the array. (As the database can return more than one row of results) That is why I convert to sets and use subset?
From other questions I got:
data.each do |obj|
obj.map do |k, v|
{k => v.to_s}
end
end
However it does not work for me. Any ideas?
Assumptions you can make:
All the keys in data will always be Strings.
All the keys in test will always be Strings. And always be the identical to data.
All the values in test will always be Strings.
Here are a couple of approaches that should do it, assuming I understand the question correctly.
#1: convert the hash values to strings
def stringify_hash_values(h)
h.each_with_object({}) { |(k,v),h| h[k] = v.to_s }
end
def sorta_subset?(data,test)
(test.map { |h| stringify_hash_values(data) } -
data.map { |h| stringify_hash_values(data) }).empty?
end
data = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>0}]
test = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>"0"}]
sorta_subset?(data,test) #=> true
#2 see if keys are the same and values converted to strings are equal
require 'set'
def hashes_sorta_equal?(h,g)
hk = h.keys
(hk.to_set == g.keys.to_set) &&
(h.values_at(*hk).map(&:to_s) == g.values_at(*hk).map(&:to_s))
end
def sorta_subset?(data,test)
test.all? { |h| data.any? { |g| hashes_sorta_equal?(g,h) } }
end
sorta_subset?(data,test) #=> true
Don't ask me why it works, but I found A solution:
data.map! do |obj|
obj.each do |k, v|
obj[k] = "#{v}"
end
end
I think it has something to do with what functions on arrays and hashes change the object itself and not create a changed copy of the object.

Create a hash map from an array that returns the largest value

I have an array of questions where each question has a category_id and a value.
I'd like to map these so that when the key (category_id) already exists in the hash, then the values are added together.
And, finally, I'd like to find the largest value in the hash:
h = Hash.new {|k, v| k[v] = 0}
#test_session.answered_questions.each do |q|
if h.key?(q.category_id)
#add q.value to the value stored in the hash
else
h = { q.category_id => q.value } #insert the "q.category_id" as key and with value "q.value"
end
end
key_with_max_value = h.max_by { |k, v| v }[0] #find the highest value
#result.category = key_with_max_value
#result.score = h[key_with_max_value].value
There is probably a lot better way to achieve this but I'm quite new to Ruby.
h = Hash.new(0)
#test_session.answered_questions.each {|q| h[q.category_id] += q.value}
#result.category, #result.score = h.max_by { |k, v| v }
Each value in the hash will be initialized to zero with Hash.new(0) and since h.max_by returns the key value pair, you can directly assign them to your #result variable.
You can simply do:
#test_session.answered_questions.each { |q| h[q.category_id] += q.value }
When a key is not present, it is assumed to have a value of 0 because of the way you initialized the hash, so it gets inserted with 0 + q.value.
See the documentation, or try it out.
Also, you can assign two variables separated by commas to h.max_by { |k, v| v }. This is called Multiple Assignment, and it works for arrays too:
a,b,c = [1,2,3]

unable to combine 2 arrays into a hash - ruby

I’m having a few problems creating a hash out of 2 arrays in ruby (1.9.2)
My issue is some of the hash keys are the same and it seems to cause an issue
So my first array (called listkey) contains these 5 items
puts listkey
service_monitor_errorlog
service_monitor_errorlog
wmt_errorlog
wmt_errorlog
syslog
the second ( called listvalue) contains these 5 items
puts listvalue
service_monitor_errorlog.0
service_monitor_errorlog.current
wmt_errorlog.0
wmt_errorlog.current
syslog.txt
what I want is a hash which contains all 5 items e.g.
{
"service_monitor_errorlog"=>"service_monitor_errorlog.0",
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.0",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
However using the hash zip command
MyHash = Hash[listkey.zip(listvalue)]
I get this hash produced
puts MyHash
{
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
Can anyone help? I’ve tried all sorts of commands to merge the 2 arrays into a hash but none of them seem to work
Cheers
Mike
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EDIT
I've just found out hashs have to have unique keys so could anyone help me work out a way to combine the arrays to form a hash with the values as arrays e.g.
{
"service_monitor_errorlog"=>["service_monitor_errorlog.0", "service_monitor_errorlog.current"]
"wmt_errorlog"=> ["wmt_errorlog.0", "wmt_errorlog.current"]
"syslog"=> ["syslog.txt"]
}
In 1.9 I'd probably do this:
listkey.zip(listvalue).each_with_object(Hash.new{|h,k| h[k] = []}) do |(k,v), h|
h[k] << v
end
Example:
a=['a','b','c','a']
b=[1,2,3,4]
a.zip(b).each_with_object(Hash.new{|h,k| h[k]=[]}) { |(k,v), h| h[k] << v }
#=> {"a"=>[1, 4], "b"=>[2], "c"=>[3]}
For your updated question, an (ugly) solution is
the_hash = listkey.zip(listvalue).inject({}) do | a, (k, v) |
a[k] ||= []
a[k] << v
a
end
or (without the inject)
the_hash = {}
listkey.zip(listvalue).each do | k, v |
the_hash[k] ||= []
the_hash[k] << v
end
Answering the answer after the edit. group_by is a bit inconvenient in this case, so let's use facets' map_by, which is a group_by that allows you to decide what you want to accumulate:
require 'facets'
Hash[xs.zip(ys).map_by { |k, v| [k, v] }]
#=> {"syslog"=>["syslog.txt"],
# "service_monitor_errorlog"=>
# ["service_monitor_errorlog.0", "service_monitor_errorlog.current"],
# "wmt_errorlog"=>["wmt_errorlog.0", "wmt_errorlog.current"]}
Please check this code
a=['a','b','c','a']
b=[1,2,3,4]
c=Hash.new
a.each_with_index do |value,key|
#puts key
#puts value
c[value]=b[key]
end
puts c
Output is
{"a"=>4, "b"=>2, "c"=>3}
This means key should be unique

Resources