Best way to iterate a list of hash in ruby - ruby

I have data structure like List< HashMap< String, List>>:
records = [{"1"=>[{"account_id"=>"1", "v"=>"1"}, {"account_id"=>"1",
"v"=>"2"}, {"account_id"=>"1", "v"=>"3"}, {"account_id"=>"1",
"v"=>"4"]}, {"2"=>[{"account_id"=>"2", "v"=>"4"}, {"account_id"=>"2",
"v"=>"4"}, {"account_id"=>"2", "v"=>"4"}]}]
I don't care about the keys in hashmap ("1" and "2" in this case), and want to iterate values of map by group:
records.each do |account_map|
account_record = account_map.values[0] # This line
for i in (0 ... account_record.size - 1)
#do something and update account_record[i]
end
end
end
How can I merge account_record = account_map.values[0] into each loop or make it look better. Thanks

Your example is quite confusing, but the regular way to iterate a hash is the following
hash.each do |key, value|
end
So in your example it looks like you should do
records.each do |account_map|
account_map.each do |index, array|
array.each do |hash|
hash['account_id'] # This is how you access your data
hash['v'] # This is how you access your data
end
end
end
Of course you should use better variables names than index, array and hash.

Related

Convert dot notation keys to tree-structured YAML in Ruby

I've sent my I18n files to be translated by a third party. Since my translator is not computer savvy we made a spreadsheet with the keys, they where sent in dot notation and the values translated.
For example:
es.models.parent: "Pariente"
es.models.teacher: "Profesor"
es.models.school: "Colegio"
How can I move that into a YAML file?
UPDATE: Just like #tadman said, this already is YAML. So if you are with the, you are just fine.
So we will focus this question if you would like to have the tree structure for YAML.
The first thing to do is transform this into a Hash.
So the previous info moved into this:
tr = {}
tr["es.models.parent"] = "Pariente"
tr["es.models.teacher"] = "Profesor"
tr["es.models.school"] = "Colegio"
Then we just advanced creating a deeper hash.
result = {} #The resulting hash
tr.each do |k, value|
h = result
keys = k.split(".") # This key is a concatenation of keys
keys.each_with_index do |key, index|
h[key] = {} unless h.has_key? key
if index == keys.length - 1 # If its the last element
h[key] = value # then we only need to set the value
else
h = h[key]
end
end
end;
require 'yaml'
puts result.to_yaml #Here it is for your YAMLing pleasure

How to "split and group" an array of objects based on one of their properties

Context and Code Examples
I have an Array with instances of a class called TimesheetEntry.
Here is the constructor for TimesheetEntry:
def initialize(parameters = {})
#date = parameters.fetch(:date)
#project_id = parameters.fetch(:project_id)
#article_id = parameters.fetch(:article_id)
#hours = parameters.fetch(:hours)
#comment = parameters.fetch(:comment)
end
I create an array of TimesheetEntry objects with data from a .csv file:
timesheet_entries = []
CSV.parse(source_file, csv_parse_options).each do |row|
timesheet_entries.push(TimesheetEntry.new(
:date => Date.parse(row['Date']),
:project_id => row['Project'].to_i,
:article_id => row['Article'].to_i,
:hours => row['Hours'].gsub(',', '.').to_f,
:comment => row['Comment'].to_s.empty? ? "N/A" : row['Comment']
))
end
I also have a Set of Hash containing two elements, created like this:
all_timesheets = Set.new []
timesheet_entries.each do |entry|
all_timesheets << { 'date' => entry.date, 'entries' => [] }
end
Now, I want to populate the Array inside of that Hash with TimesheetEntries.
Each Hash array must contain only TimesheetEntries of one specific date.
I have done that like this:
timesheet_entries.each do |entry|
all_timesheets.each do |timesheet|
if entry.date == timesheet['date']
timesheet['entries'].push entry
end
end
end
While this approach gets the job done, it's not very efficient (I'm fairly new to this).
Question
What would be a more efficient way of achieving the same end result? In essence, I want to "split" the Array of TimesheetEntry objects, "grouping" objects with the same date.
You can fix the performance problem by replacing the Set with a Hash, which is a dictionary-like data structure.
This means that your inner loop all_timesheets.each do |timesheet| ... if entry.date ... will simply be replaced by a more efficient hash lookup: all_timesheets[entry.date].
Also, there's no need to create the keys in advance and then populate the date groups. These can both be done in one go:
all_timesheets = {}
timesheet_entries.each do |entry|
all_timesheets[entry.date] ||= [] # create the key if it's not already there
all_timesheets[entry.date] << entry
end
A nice thing about hashes is that you can customize their behavior when a non-existing key is encountered. You can use the constructor that takes a block to specify what happens in this case. Let's tell our hash to automatically add new keys and initialize them with an empty array. This allows us to drop the all_timesheets[entry.date] ||= [] line from the above code:
all_timesheets = Hash.new { |hash, key| hash[key] = [] }
timesheet_entries.each do |entry|
all_timesheets[entry.date] << entry
end
There is, however, an even more concise way of achieving this grouping, using the Enumerable#group_by method:
all_timesheets = timesheet_entries.group_by { |e| e.date }
And, of course, there's a way to make this even more concise, using yet another trick:
all_timesheets = timesheet_entries.group_by(&:date)

How do I subgroup this hash that has already been grouped?

I have a set of word strings which I am turning into a hash, grouped by the size of the string. I am doing this by:
hash = set.group_by(&:size)
resulting in
hash = {5=>[apple, andys, throw, balls], 7=>[bananas, oranges]}
I want to further group the hash values by first letter, so the the end results looks like:
hash = {5=>{a=>[apple, andys],b=>[balls],t=>[throw]}, 7=>{b=>[bananas], o=>[oranges]}}
I tried putting
hash.each_value do | value |
value = value.group_by(&:chr)
end
after the first group_by but that only seems to return the original hash. I am admittedly a ruby beginner so I'm not sure if I could do this in one fell swoop, or exactly how (&:size) notation works, if I were asked to write it out. Thoughts?
To update your hash you need to do like this
hash.each do |key, value|
hash[key] = value.group_by(&:chr)
end
I'd keep the whole computation functional:
>> Hash[set.group_by(&:size).map { |k, vs| [k, vs.group_by(&:chr)] }]
=> {5=>{"a"=>["apple", "andys"], "t"=>["throw"], "b"=>["balls"]},
7=>{"b"=>["bananas"], "o"=>["oranges"]}}

Remove the '-' dividers in JSON keys in Ruby

I'm trying to read some JSON data from the Tumblr API.
I'm using the Hashie gem to read the values as object properties. This should make reading easier/cleaner.
it turns something like this:
data['post']['title']
into this:
data.post.title
Unfortunately there are some keys showing up with a '-' as divider between like this:
regular-title: Mijn eerste post
format: html
regular-body: <p>post</p>
therefore i cannot use post.regular-title. Is there a way to replace all the minus(-) symbols into underscores(_)?
This will do it:
def convert_object(data)
case data
when Hash
data.inject({}) do |h,(k,v)|
h[(k.respond_to?(:tr) ? k.tr('-', '_') : k)] = convert_object(v)
h
end
when Array
data.map { |i| convert_object(i) }
else
data
end
end
You can use it like this:
convert_object(JSON.parse('{"something-here":"value","otherkey":{"other-key":"value-value"}}'))
Karaszi Istvan helped me a lot with the solution. I added the check for an array in the hash. This way hashes in arrays in the hash will get underscored too.
def convert_hash(hash)
case hash
when Hash
hash.inject({}) do |h,(k,v)|
h[k.tr('-', '_')] = convert_hash(v)
h
end
when Array
array = hash
number = 0
array.each do
array[number] = convert_hash(array[number])
number += 1
end
array
else
hash
end
end
I don't know why i added the 'number' as iterator. Somehow hash.each didn't work.

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

Resources