i need to create a hash/array where 2 elements are stored: the country code, and the number of times the country occurred.
I want to vet some conceptual logic: i want to create a helper method that passes in a list of countries. Then, I loop through each country and will merge the country code into the hash through a series of if statements:
#map_country = Hash.new
if country == "United States"
#map_country.merge(:us => ??)
I'm not quite sure how I can add a counter to push into the hash. Can anyone help? Basically, I want to achieve how many times "United States" shows up.
Also, once I have this Hash completed - I want to do something different to each country based on the count. How do I go about picking out the value from the key? Moreover, how do I get just the key?
<% if #map_country[:country] > 5 %>
... do this with #map_country...
Thanks! Apologies if this is confusing, but really could use some help here. Thanks!
To me it sounds like you're trying to count occurrences which you can do with the #inject method:
[1] pry(main)> countries = ["United States", "Canada", "United States", "Mexico"]
=> ["United States", "Canada", "United States", "Mexico"]
[2] pry(main)> countries.inject({}) { |hash, ctr| hash[ctr] = hash[ctr].to_i + 1; hash }
=> {"United States"=>2, "Canada"=>1, "Mexico"=>1}
Then say you want to do something with that hash, you could loop through it like this:
[3] pry(main)> occ = countries.inject({}) { |hash, ctr| hash[ctr] = hash[ctr].to_i + 1; hash }
=> {"United States"=>2, "Canada"=>1, "Mexico"=>1}
[4] pry(main)> occ.each do |country, val|
[4] pry(main)* if val == 2
[4] pry(main)* puts "There are two occurences of #{country}"
[4] pry(main)* end
[4] pry(main)* end
There are two occurences of United States
If you're set on using a Hash (rather than a custom class) for this then just use a default_proc to auto-vivify entries with zeros and you a simple increment is all you need:
#map_country = Hash.new { |h, k| h[k] = 0 }
if country == 'United States'
#map_country[:us] += 1
Related
I have an array of hashes. Here is a small sample of the typical values:
[{"id"=>1,
"context"=>"r178",
"asset"=>"Art Schools Hub",
"campaign"=>"Fashion Careers",
"contact_email"=>"evert_nolan#hammechaefer.net",
"notes"=>"",
"user_first_name"=>"Agustin",
"user_last_name"=>"Welch",
"status"=>"Completed",
"date_collected"=>"01/22/16"},
{"id"=>4,
"context"=>"r178",
"asset"=>"Art Schools Hub",
"campaign"=>"Graphic Design Careers",
"contact_email"=>"jamil_brakus#effertz.biz",
"notes"=>"",
"user_first_name"=>"Agustin",
"user_last_name"=>"Welch",
"status"=>"In Progress",
"date_collected"=>"01/22/16"},
{"id"=>15,
"context"=>"r178",
"asset"=>"Art Schools Hub",
"campaign"=>"Art Education",
"contact_email"=>"miss_kyle_mccullough#hicklezboncak.net",
"notes"=>"",
"user_first_name"=>"Jermaine",
"user_last_name"=>"Wilkinson",
"status"=>"Open",
"date_collected"=>"01/22/16"}]
I know that doing a select like this:
results = #network.select { |x| x["campaign"] == "Art Education" && x["status"] == "Open" }
filters the array returning an array of hashes where the selected keys have the searched values.
However, the user must be able to filter this array based on any or all of the keys having values the user submits.
While I can substitute the values from a form's params into the block like this:
results = #network.select { |x| x[params[1]["column"]] == params[1]["search"] && x[params[2]["column"]] == params[2]["search"] }
The logic of each select could be different. There could be as many as 10 different conditions with a column value and a search value in the form params.
I need a way to dynamically create the expression in the block portion of the select based on the conditions the user submits.
Unfortunately, every way I've tried to construct an expression for the block results in a string value that can not be evaluated by the select.
I've working on this for days, so I'd be very grateful if someone could give me a solution.
EDIT:
Thanks to Wand Maker's elegant solution, I made the following modifications, based on his code, to allow the user to filter the array of hashes based on keys whose search value starts with a value the user submits, instead of being equal to the value:
pm = params.map { |h| {h["column"] => h["search"].downcase} }.reduce(&:merge)
result = #network.select do |h|
temp = h.slice(*pm.keys)
new_temp = Hash.new
temp.each do |k,v|
new_temp[k]=v.downcase.slice(0..pm[k].length - 1)
end
new_temp == pm
end
This now works great.
Here is one possible way.
Let's define params to be:
params = [{"column" => "context", "search" => "r178"},
{"column" => "campaign", "search" => "Art Education"}]
We will process it to the structurally resemble the elements of #network.
pm = params.map { |h| {h["column"] => h["search"]} }.reduce(&:merge)
#=> {"context"=>"r178", "campaign"=>"Art Education"}
Now, we will pick the keys present in this processed params hash pm, and use it to get slice of each element from #network array, and if both the processed params hash and sliced hash are equal, then, we have a match and we can select the item.
result = #network.select {|h| h.slice(*pm.keys) == pm}
Complete code sample, I have added require "active_support/core_ext/hash" so that below program can be run as standalone ruby program for illustration purpose. It will not be needed in Rails code.
require "pp"
require "active_support/core_ext/hash"
#network = [{"id"=>1, "context"=>"r178", "asset"=>"Art Schools Hub", "campaign"=>"Fashion Careers", "contact_email"=>"evert_nolan#hammechaefer.net", "notes"=>"", "user_first_name"=>"Agustin", "user_last_name"=>"Welch", "status"=>"Completed", "date_collected"=>"01/22/16"},
{"id"=>4, "context"=>"r178", "asset"=>"Art Schools Hub", "campaign"=>"Graphic Design Careers", "contact_email"=>"jamil_brakus#effertz.biz", "notes"=>"", "user_first_name"=>"Agustin", "user_last_name"=>"Welch", "status"=>"In Progress", "date_collected"=>"01/22/16"},
{"id"=>15, "context"=>"r178", "asset"=>"Art Schools Hub", "campaign"=>"Art Education", "contact_email"=>"miss_kyle_mccullough#hicklezboncak.net", "notes"=>"", "user_first_name"=>"Jermaine", "user_last_name"=>"Wilkinson", "status"=>"Open", "date_collected"=>"01/22/16"}]
params = [{"column" => "context", "search" => "r178"},
{"column" => "campaign", "search" => "Art Education"}]
pm = params.map { |h| {h["column"] => h["search"]} }.reduce(&:merge)
pp result = #network.select {|h| h.slice(*pm.keys) == pm}
#=> [{"id"=>15,
# "context"=>"r178",
# "asset"=>"Art Schools Hub",
# ...
# "status"=>"Open",
# "date_collected"=>"01/22/16"}]
With respect to clarification sought in the comments, the solution can be adapted for starts_with type of condition as well. One can use:
pp result = #network.select {|h| pm.keys.all?{|k| h[k].starts_with? pm[k]}}
Here's an example hash and an example array to search in the hash:
nicknames = { "Black Mamba" => "Kobe Bryant",
"Half Man Half Amazing" => "Vince Carter",
"The Big Fundamental" => "Tim Duncan",
"Big Ticket" => "Kevin Garnett",
"Obi-Wan Ginobili" => "Manu Ginobili",
"The Answer" => "Allen Iverson" }
names = [ "Vince Carter", "Manu Ginobili", "Allen Iverson" ]
I want to return:
selected = { "Half Man Half Amazing" => "Vince Carter", "Obi-Wan Ginobili" => "Manu Ginobili", "The Answer" = "Allen Iverson" }
What's a good way to do this? Thanks!
You can simply do the following:
nicknames.select { |key, value| names.include?(value) }
(copy-paste the code you provided and mine in your IRB console and you'll see it working).
If the values in the hash are unique, then you can reverse the keys and the values. MrYoshiji's code works, but here is a more efficient way.
hash = nicknames.invert
names.to_enum.with_object({}){|k, h| h[hash[k]] = k}
I'm doing data processing, one task is to get stats of people distribution. Say for the people of name "john doe", there fall in different states, ca, ar, and ny, and of different age groups, twenties, thirties, etc. {1,2} or {3} is the people's id.
"john doe" => "ca:tw#2{1,2}:th#1{3};ar:tw#1{4}:fi#1{5};ny:tw#1{6};"
Now if I want to get the id of john doe in ca with age tw, how should I get them? Maybe using Regex? And if I want to add a new id to it, say 100, now it becomes
"john doe" => "ca:tw#3{1,2,100}:th#1{3};ar:tw#1{4}:fi#1{5};ny:tw#1{6};"
how should I do that?
Thanks!
If you want to stick with string manipulation, you can use regex and gsub.
Here is one way to do it. It could use some clean up (eg error handling, re-factoring, etc.), but I think it would get you started.
def count(details, location, age_group)
location_details = /#{location}(.+?);/.match(details)[1]
age_count = /#{age_group}#(\d+)\{/.match(details)[1]
return age_count.to_i
end
def ids(details, location, age_group)
location_details = /#{location}(.+?);/.match(details)[1]
age_ids = /#{age_group}#\d+\{(.+?)\}/.match(details)[1]
return age_ids
end
def add(details, location, age_group, new_id)
location_details = /#{location}(.+?);/.match(details)[1]
new_count = count(details, location, age_group) + 1
new_ids = ids(details, location, age_group) + ',' + new_id
location_details.gsub!(/#{age_group}#\d+\{(.+?)\}/, "#{age_group}##{new_count}{#{new_ids}}")
details.gsub!(/#{location}(.+?);/, "#{location}#{location_details};")
end
You can see it produces the results you wanted (at least functionally, not sure about performance):
names = {"john doe" => "ca:tw#2{1,2}:th#1{3};ar:tw#1{4}:fi#1{5};ny:tw#1{6};"}
puts count(names["john doe"], 'ca', 'tw')
#=> 2
puts ids(names["john doe"], 'ca', 'tw')
#=> 1,2
names["john doe"] = add(names["john doe"], 'ca', 'tw', '100')
puts names["john doe"]
#=> ca:tw#3{1,2,100}:th#1{3};ar:tw#1{4}:fi#1{5};ny:tw#1{6};
It doesn't make sense to use a string for this inside the program. You may read the data from a string as it is stored, or write it back out that way, but you should store it in a manner that's easy to manipulate. For instance:
data = {
"john doe" => {
"ca" => {
"tw" => [1,2],
"th" => [3]
},
"ar" => {
"tw" => [4],
"fi" => [5]
},
"ny" => {
"tw" => [6]
}
}
}
Given that, the ids of the California John Doe's in their 20's are data['john doe']['ca']['tw']. The number of such John Doe's is data['john doe']['ca']['tw'].length; the first id is data['john doe']['ca']['tw'][0], and the second is data['john doe']['ca']['tw'][1]. You could add id 100 to it with data['john doe']['ca']['tw'] << 100; 100 would then be the value of data['john doe']['ca']['tw'][2].
If I were writing this, though, I would probably use actual numbers for the age-range keys (20, 30, 50) instead of those obscure letter prefixes.
I have an array that contains dates and values. An example of how it might look:
[
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
Notice that some of the dates repeat. I'm trying to output this in a table format and I only want to show unique dates. So I loop through it with the following code to get my desired output.
prev_date = nil
#reading_schedule.reading_plans.each do |plan|
use_date = nil
if plan.assigned_date != prev_date
use_date = plan.assigned_date
end
prev_date = plan.assigned_date
plan.assigned_date = use_date
end
The resulting table will then look something like this
1/1/2010 aa
bb
1/2/2010 cc
dd
1/3/2010 ee
This work fine but I am new to ruby and was wondering if there was a better way to do this.
Enumerable.group_by is a good starting point:
require 'pp'
asdf = [
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
pp asdf.group_by { |n| n.keys.first }.map{ |a,b| { a => b.map { |c| c.to_a.last.last } } }
# >> [{"1/1/2010"=>["aa", "bb"]}, {"1/2/2010"=>["cc", "dd"]}, {"1/3/2010"=>["ee"]}]
Which should be a data structure you can bend to your will.
I don't know as though it's better, but you could group the values by date using (e.g.) Enumerable#reduce (requires Ruby >= 1.8.7; before that, you have Enumerable#inject).
arr.reduce({}) { |memo, obj|
obj.each_pair { |key, value|
memo[key] = [] if ! memo.has_key?(key);
memo[key] << value
}
memo
}.sort
=> [["1/1/2010", ["aa", "bb"]], ["1/2/2010", ["cc", "dd"]], ["1/3/2010", ["ee"]]]
You could also use Array#each to similar effect.
This is totally a job for a hash.
Create a hash and use the date as the hashkey and an empty array as the hashvalue.
Then accumulate the values from the original array in the hashvalue array
I have this code here and it works but there has to be a better way.....i need two arrays that look like this
[
{
"Vector Arena - Auckland Central, New Zealand" => {
"2010-10-10" => [
"Enter Sandman",
"Unforgiven",
"And justice for all"
]
}
},
{
"Brisbane Entertainment Centre - Brisbane Qld, Austr..." => {
"2010-10-11" => [
"Enter Sandman"
]
}
}
]
one for the past and one for the upcoming...the problem i have is i am repeating myself and though it works i want to clean it up ...here is my data
..
Try this:
h = Hash.new {|h1, k1| h1[k1] = Hash.new{|h2, k2| h2[k2] = []}}
result, today = [ h, h.dup], Date.today
Request.find_all_by_artist("Metallica",
:select => "DISTINCT venue, showdate, LOWER(song) AS song"
).each do |req|
idx = req.showdate < today ? 0 : 1
result[idx][req.venue][req.showdate] << req.song.titlecase
end
Note 1
In the first line I am initializing an hash of hashes. The outer hash creates the inner hash when a non existent key is accessed. An excerpt from Ruby Hash documentation:
If this hash is subsequently accessed by a key that doesn‘t correspond to a hash
entry, the block will be called with the hash object and the key, and should
return the default value. It is the block‘s responsibility to store the value in
the hash if required.
The inner hash creates and empty array when the non existent date is accessed.
E.g: Construct an hash containing of content as values and date as keys:
Without a default block:
h = {}
list.each do |data|
h[data.date] = [] unless h[data.date]
h[data.date] << data.content
end
With a default block
h = Hash.new{|h, k| h[k] = []}
list.each do |data|
h[data.date] << data.content
end
Second line simply creates an array with two items to hold the past and future data. Since both past and the present stores the data as Hash of Hash of Array, I simply duplicate the value.
Second line can also be written as
result = [ h, h.dup]
today = Date.today