Ruby - Set key-value pairs inside array of hashes - arrays

The problem is:
I have a method
def comparison_reporter(list_of_scenarios_results1, list_of_scenarios_results2)
actual_failed_tests = list_of_scenarios_results2.select {|k,v| v == 'Failed'}
actual_passed_tests = list_of_scenarios_results2.select {|k,v| v == 'Passed'}
failed_tests = Array.new(actual_failed_tests.length) { Hash.new }
failed_tests.each do |hash|
actual_failed_tests.keys.map {|name| hash["test_name"] = name}
actual_failed_tests.values.map {|new_status| hash["actual_status"] = new_status}
list_of_scenarios_results1.values_at(*actual_failed_tests.keys).map {|old_status| hash["previous_status"] = old_status}
end
final_result = {
"passed_tests_count" => list_of_scenarios_results2.select {|k,v| v == 'Passed'}.length,
"failed_tests_count" => list_of_scenarios_results2.select {|k,v| v == 'Failed'}.length,
"failed_tests" => failed_tests
}
return final_result
end
This method takes 2 hashes as arguments and returns the result of their comparison and some other things. Currently, it always returns failed_tests with two (or more) identical hashes (same key-value pairs).
I think, that problem is somewhere in failed_tests.each do |hash| block, but I can't find the reason of this bug, please advice. Example of the method result (in .json format)
{
"passed_tests_count": 3,
"failed_tests_count": 2,
"failed_tests": [
{
"test_name": "As a user I want to use Recent searches tab",
"actual_status": "Failed",
"previous_status": "Failed"
},
{
"test_name": "As a user I want to use Recent searches tab",
"actual_status": "Failed",
"previous_status": "Failed"
}
]
}
UPD:
hash1 (first argument) -
{""=>"Passed",
"As a new user I want to use no fee rentals tab"=>"Passed",
"As a new user I want to use Luxury rentals tab"=>"Passed",
"As a user I want to use Recent searches tab"=>"Failed",
"As a user I want to use new listings for you tab"=>"Passed"}
hash2 (second argument)-
{""=>"Passed",
"As a new user I want to use no fee rentals tab"=>"Failed",
"As a new user I want to use Luxury rentals tab"=>"Passed",
"As a user I want to use Recent searches tab"=>"Failed",
"As a user I want to use new listings for you tab"=>"Passed"}
Example of desired desired output:
{
"passed":"count",
"failed":"count",
"failed_tests": [
{"test_name":"As a user I want to use Recent searches tab",
"actual_status":"failed",
"previous_status":"failed"},
{"test_name":"As a new user I want to use no fee rentals tab",
"actual_status":"failed",
"previous_status":"passed"}]
}

Solution:
def comparison_reporter(before, after)
failed_tests = after.select { |k,v| v == "Failed" }.map do |k,v|
{
test_name: k,
actual_status: v,
previous_status: before[k]
}
end
{
passed: after.size - failed_tests.size,
failed: failed_tests.size,
failed_tests: failed_tests
}
end
Simplified failed_tests quite a bit. Since we calculate number of failed tests, we can use it for the final counts, instead of iterating over the hash again.

The problem is on line 8: You're overwriting hash["previous_status"] with the last value in list_of_scenarios_results1.values_at(*actual_failed_tests.keys) when you map over it.
Usually you use map to assign an iterable to something, not modify something else.
e.g.
x = ['1','2','3'].map(&:to_i)
rather than
x = []; ['1','2','3'].map {|v| x << v.to_i}
I'd suggest re-thinking your approach. Will you always have the same keys in both hashes? If so you could simplify this. I'd also suggest looking into byebug. It's an interactive debugger that'll let you step through your function and see where things aren't doing what you expect.

Related

Merge three arrays of hashes in different way

I'm new to Ruby and trying to build a meeting app. I have three arrays containing hashes :
one containing my scheduled meetings with dates and therefore an empty array
of people
one containing the people invited per meeting
and a last one containing the people who refused
This materializes as:
meetings = [
{:id=>"1", :peoples=>[]}
{:id=>"2", :peoples=>[]}
{:id=>"3", :peoples=>[]}
]
invited_peoples = [
{:id=>"1", :peoples=>['Tom', 'Henry', 'Georges', 'Nicolas']}
{:id=>"2", :peoples=>['Arthur', 'Carl']}
]
absent_peoples = [
{:id=>"1", :peoples=>['Henry', 'Georges']}
]
And I would like to have : meetings + invited_peoples - absent_peoples like
meetings_with_participants = [
{:id=>"1", :peoples=>['Tom', 'Nicolas']}
{:id=>"2", :peoples=>['Arthur', 'Carl']}
{:id=>"3", :peoples=>[]}
]
I'm looking for a readable solution but I don't find anyone...
Sorry for my english and thank you in advance,
Nicolas
Define a method to find object by id
def find_by_id array_of_hash, id
array_of_hash.find {|x| x[:id] == id} || {peoples: []}
end
Use map to turn a new array, inside map block just use your logic meetings + invited_peoples - absent_peoples like
result = meetings.map do |item|
id = item[:id]
{id: id, peoples: item[:peoples] + find_by_id(invited_peoples, id)[:peoples] - find_by_id(absent_peoples, id)[:peoples]}
end
Result:
=> [{:id=>"1", :peoples=>["Tom", "Nicolas"]}, {:id=>"2", :peoples=>["Arthur", "Carl"]}, {:id=>"3", :peoples=>[]}]
Create a simple hash
h = meetings.each_with_object({}) { |g,h| h[g[:id]] = g[:peoples] }
#=> {"1"=>[], "2"=>[], "3"=>[]}
Add invitees
invited_peoples.each { |g| h[g[:id]] += g[:peoples] }
Now
h #=> {"1"=>["Tom", "Henry", "Georges", "Nicolas"],
# "2"=>["Arthur", "Carl"], "3"=>[]}
Remove declinees
absent_peoples.each { |g| h[g[:id]] -= g[:peoples] }
Now
h #=> {"1"=>["Tom", "Nicolas"], "2"=>["Arthur", "Carl"],
# "3"=>[]}
Convert the hash to an array of hashes
h.map { |k,v| { :id=> k, :peoples=> v } }
#=> [{:id=>"1", :peoples=>["Tom", "Nicolas"]},
# {:id=>"2", :peoples=>["Arthur", "Carl"]},
# {:id=>"3", :peoples=>[]}]
I initially created a hash and only after processing the invitees and decliners did I convert it to an array of hashes. Doing it that way sped :id lookups for adding and removing people. As a consequence, if n = meetings.size, these calculations have a computational complexity close to O(n), "close to" because hash key lookups have a computational complexity that is nearly O(1) (that is, the time needed to locate a key and its value is nearly constant, regardless of the size of the hash). By contrast, methods that search for a value of :id in invited_peoples and absent_peoples, for each element of meetings, have computational complexity of O(n2).

Ruby Hash Key with Multiple Values: returning the minimum value in a timely manner

UPDATE: I was initially overwriting the hash keys, but have since resolved that. Thank you for everyone's input so far.
The issue now is how long the iterations are taking hours to produce data:
customers csv has 22,000 rows.
fiber csv has 170,000 rows.
fiber = CSV.read("fiber.csv", {headers: true})
customers = CSV.read("customers.csv", {headers: true})
hh = Hash.new { |hsh,key| hsh[key] = [] }
#for each customer, loop through all the fiber coords
customers.each do |c|
fiber.each do |f|
hh[customer["cid"]].push Haversine.distance(c["lat"], c["lng"], f["lat"], f["lng"])
end
end
vals = hh.map { |k, v| v.min } #returns the minimum value per row (which I want)
Since I'd like to use these values outside of the program/command line, I thought writing to a CSV would be an okay approach (other suggestions welcome).
However, since the above nested loop takes hours on hours to run without ever finishing, this is not an ideal approach.
CSV.open("hash_output.csv", "wb") {|csv| vals.each {|elem| csv << [elem]} }
Any ideas on how to speed this process up?
I think the problem is that you are overriding your name space with each loop. I would do something like this:
hh = Hash.new { |hsh,key| hsh[key] = [] }
#for each customer, loop through all the fiber coords
customers.each do |c|
fiber.each do |f|
hh[c["last Name"]].push Haversine.distance(c["lat"], c["lng"], f["lat"], f["lng"])
end
end
That way the keys will be the customer's last name and the values will be an array of distances.
So the resulting data structure will look like this:
{
"DOE" => [922224.16, 920129.46, 919214.42],
...
}

Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays

A web service is returning a hash that contains an unknown number of nested hashes, some of which contain an array, which in turn contains an unknown number of nested hashes.
Some of the keys are not unique -- i.e. are present in more than one of the nested hashes.
However, all the keys that I actually care about are all unique.
Is there someway I can give a key to the top-level hash, and get back it's value even if the key-value pair is buried deep in this morass?
(The web service is Amazon Product Advertising API, which slightly varies the structure of the results that it gives depending on the number of results and the search types permitted in each product category.)
Here's a simple recursive solution:
def nested_hash_value(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42
No need for monkey patching, just use Hashie gem: https://github.com/intridea/hashie#deepfind
user = {
name: { first: 'Bob', last: 'Boberts' },
groups: [
{ name: 'Rubyists' },
{ name: 'Open source enthusiasts' }
]
}
user.extend Hashie::Extensions::DeepFind
user.deep_find(:name) #=> { first: 'Bob', last: 'Boberts' }
For arbitrary Enumerable objects, there is another extension available, DeepLocate: https://github.com/intridea/hashie#deeplocate
Combining a few of the answers and comments above:
class Hash
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.is_a? Enumerable
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
Ruby 2.3 introduces Hash#dig, which allows you to do:
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
A variation of barelyknown's solution: This will find all the values for a key in a hash rather than the first match.
class Hash
def deep_find(key, object=self, found=[])
if object.respond_to?(:key?) && object.key?(key)
found << object[key]
end
if object.is_a? Enumerable
found << object.collect { |*a| deep_find(key, a.last) }
end
found.flatten.compact
end
end
{a: [{b: 1}, {b: 2}]}.deep_find(:b) will return [1, 2]
Despite this appearing to be a common problem, I've just spent a while trying to find/come up with exactly what I need, which I think is the same as your requirement. Neither of the links in the first response are spot-on.
class Hash
def deep_find(key)
key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
end
end
So given:
hash = {:get_transaction_list_response => { :get_transaction_list_return => { :transaction => [ { ...
The following:
hash.deep_find(:transaction)
will find the array associated with the :transaction key.
This is not optimal as the inject will continue to iterate even if memo is populated.
I use the following code
def search_hash(hash, key)
return hash[key] if hash.assoc(key)
hash.delete_if{|key, value| value.class != Hash}
new_hash = Hash.new
hash.each_value {|values| new_hash.merge!(values)}
unless new_hash.empty?
search_hash(new_hash, key)
end
end
I ended up using this for a small trie search I wrote:
def trie_search(str, obj=self)
if str.length <= 1
obj[str]
else
str_array = str.chars
next_trie = obj[str_array.shift]
next_trie ? trie_search(str_array.join, next_trie) : nil
end
end
Note: this is just for nested hashes at the moment. Currently no array support.
Because Rails 5 ActionController::Parameters no longer inherits from Hash, I've had to modify the method and make it specific to parameters.
module ActionController
class Parameters
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.respond_to?(:each)
object = object.to_unsafe_h if object.is_a?(ActionController::Parameters)
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
end
If the key is found, it returns the value of that key, but it doesn't return an ActionController::Parameter object so Strong Parameters are not preserved.

How do I consolidate a hash in Perl?

I have an array of hash references. The hashes contain 2 keys, USER and PAGES. The goal here is to go through the array of hash references and keep a running total of the pages that the user printed on a printer (this comes from the event logs). I pulled the data from an Excel spreadsheet and used regexes to pull the username and pages. There are 182 rows in the spreadsheet and each row contains a username and the number of pages they printed on that job. Currently the script can print each print job (all 182) with the username and the pages they printed but I want to consolidate this down so it will show: username 266 (i.e. just show the username once, and the total number of pages they printed for the whole spreadsheet.
Here is my attempt at going through the array of hash references, seeing if the user already exists and if so, += the number of pages for that user into a new array of hash references (a smaller one). If not, then add the user to the new hash ref array:
my $criteria = "USER";
my #sorted_users = sort { $a->{$criteria} cmp $b->{$criteria} } #user_array_of_hash_refs;
my #hash_ref_arr;
my $hash_ref = \#hash_ref_arr;
foreach my $index (#sorted_users)
{
my %hash = (USER=>"",PAGES=>"");
if(exists $index{$index->{USER}})
{
$hash{PAGES}+=$index->{PAGES};
}
else
{
$hash{USER}=$index->{USER};
$hash{PAGES}=$index->{PAGES};
}
push(#hash_ref_arr,{%hash});
}
But it gives me an error:
Global symbol "%index" requires explicit package name at ...
Maybe my logic isn't the best on this. Should I use arrays instead? It seems as though a hash is the best thing here, given the nature of my data. I just don't know how to go about slimming the array of hash refs down to just get a username and the total pages they printed (I know I seem redundant but I'm just trying to be clear). Thank you.
my %totals;
$totals{$_->{USER}} += $_->{PAGES} for #user_array_of_hash_refs;
And then, to get the data out:
print "$_ : $totals{$_}\n" for keys %totals;
You could sort by usage too:
print "$_ : $totals{$_}\n" for sort { $totals{$a} <=> $totals{$b} } keys %totals;
As mkb mentioned, the error is in the following line:
if(exists $index{$index->{USER}})
However, after reading your code, your logic is faulty. Simply correcting the syntax error will not provide your desired results.
I would recommend skipping the use of temporary hash within the loop. Just work with the a results hash directly.
For example:
#!/usr/bin/perl
use strict;
use warnings;
my #test_data = (
{ USER => "tom", PAGES => "5" },
{ USER => "mary", PAGES => "2" },
{ USER => "jane", PAGES => "3" },
{ USER => "tom", PAGES => "3" }
);
my $criteria = "USER";
my #sorted_users = sort { $a->{$criteria} cmp $b->{$criteria} } #test_data;
my %totals;
for my $index (#sorted_users) {
if (not exists $totals{$index->{USER}}) {
# initialize total for this user
$totals{$index->{USER}} = 0;
}
# add to user's running total
$totals{$index->{USER}} += $index->{PAGES}
}
print "$_: $totals{$_}\n" for keys %totals;
This produces the following output:
$ ./test.pl
jane: 3
tom: 8
mary: 2
The error comes from this line:
if(exists $index{$index->{USER}})
The $ sigil in Perl 5 with {} after the name means that you are getting a scalar value out of a hash. There is no hash declared by the name %index. I think that you probably just need to add a -> operator so the problem line becomes:
if(exists $index->{$index->{USER}})
but not having the data makes me unsure.
Also, good on you for using use strict or you would be instantiating the %index hash silently and wondering why your results didn't make any sense.
my %total;
for my $name_pages_pair (#sorted_users) {
$total{$name_pages_pair->{USER}} += $name_pages_pair->{PAGES};
}
for my $username (sort keys %total) {
printf "%20s %6u\n", $username, $total{$username};
}

Groovy read most recent file in directory

I just have a question about writing a function that will search a directory for the most recent log in a directory. I currently came up with one, but I'm wondering if there is a better (perhaps more proper) way of doing this.
I'm currently using hdsentinel to create logs on computer and placing the log in a directory. The logs are saved like so:
/directory/hdsentinel-computername-date
ie. C:/hdsentinel-owner-2010-11-11.txt
So I wrote a quick script that loops through certain variables to check for the most recent (within the past week) but after looking at it, I'm question how efficient and proper it is to do things this way.
Here is the script:
String directoryPath = "D:"
def computerName = InetAddress.getLocalHost().hostName
def dateToday = new Date()
def dateToString = String.format('%tm-%<td-%<tY', dateToday)
def fileExtension = ".txt"
def theFile
for(int i = 0; i < 7; i++) {
dateToString = String.format('%tY-%<tm-%<td', dateToday.minus(i))
fileName = "$directoryPath\\hdsentinel-$computerName-$dateToString$fileExtension"
theFile = new File(fileName)
if(theFile.exists()) {
println fileName
break;
} else {
println "Couldn't find the file: " + fileName
}
}
theFile.eachLine { print it }
The script works fine, perhaps it has some flaws. I felt I should go ahead and ask what the typical route is for this type of thing before I continue with it.
All input is appreciated.
Though a bit messy, you could implement a multi-column sort via the 'groupBy' method (Expounding on Aaron's code)..
def today = new Date()
def recent = {file -> today - new Date(file.lastModified()) < 7}
new File('/yourDirectory/').listFiles().toList()
.findAll(recent)
.groupBy{it.name.split('-')[1]}
.collect{owner, logs -> logs.sort{a,b -> a.lastModified() <=> b.lastModified()} }
.flatten()
.each{ println "${new Date(it.lastModified())} ${it.name}" }
This finds all logs created within the last week, groups them by owner name, and then sorts according to date modified.
If you have files other than logs in the directory, you may first need to grep for files containing 'hdsentinel.'
I hope this helps.
EDIT:
From the example you provided, I cannot determine if the least significant digit in the format:
C:/hdsentinel-owner-2010-11-11.txt
represents the month or the day. If the latter, sorting by file name would automatically prioritize by owner, and then by date created (without all of the chicanery of the above code).
For Instance:
new File('/directory').listFiles().toList().findAll(recent).sort{it.name}
Hopefully this helps some..This sorts a given path by date modified in a groovier way. The lists them out.
you can limit the list, and add other conditions in the closure to get the desired results
new File('/').listFiles().sort() {
a,b -> a.lastModified().compareTo b.lastModified()
}.each {
println it.lastModified() + " " + it.name
}
As I was trying to solve a similar problem, learnt a much cleaner approach.
Define a closure for sorting
def fileSortCondition = { it.lastModified() }
And File.listFiles() has other variation which accepts FileFilter and FilenameFilter in Java, and these interfaces has a single method called accept, Implement the interface as a closure.
def fileNameFilter = { dir, filename ->
if(filename.matches(regrx))
return true
else
return false
} as FilenameFilter
And lastly
new File("C:\\Log_Dir").listFiles(fileNameFilter).sort(fileSortCondition).reverse()
Implement FileFilter interface if filtering is to be done by File attributes.
def fileFilter = { file ->
if(file.isDirectory())
return false
else
return true } as FileFilter

Resources