If I have an array like this
["Member", "Friends", "Hello", "Components", "Family", "Lastname"]
and I need to split it at "Components" and get 2 arrays, which are
["Member", "Friends", "Hello"]
and
["Family", "Lastname"]
Can I do that and how?
You can use Array#slice:
class Array
def msplit(m)
idx = self.index(m)
idx ? [self[0..idx-1], self[idx+1..-1]] : nil
end
end
arr = ["Member", "Friends", "Hello", "Components", "Family", "Lastname"]
a, b = arr.msplit("Components")
a # => ["Member", "Friends", "Hello"]
b # => ["Family", "Lastname"]
a.msplit("Foo") # => nil
In ruby 1.9 (because of chunk method):
array = ["Member", "Friends", "Hello", "Components", "Family", "Lastname"]
a, b = array.chunk {|e| e != "Components"}.map {|a, b| b if a}.compact
p a, b #=> ["Member", "Friends", "Hello"] ["Family", "Lastname"]
May be by
i = ary.index["Components"] - 1
ary[0..i] #=>["Member", "Friends", "Hello"]
i = ary.index["Components"] + 1
ary[i..ary.length] #=>["Family", "Lastname"]
or
ary[0..ary.index["Components"]-1] #=>["Member", "Friends", "Hello"]
ary[ary.index["Components"]+1..ary.length] #=>["Family", "Lastname"]
Also can use:
i = ary.index["Components"] - 1
ary.take(i) #=>["Member", "Friends", "Hello"]
newArray = oldArray.slice(5,2)
use the index and length methods to work out the 5 and 2 values
should leave you with:
newArray ["Family", "Lastname"]
oldArray ["Member", "Friends", "Hello"]
see the doc here: http://www.ruby-doc.org/core-1.8.3/classes/Array.html#M000398
As an method:
class Array
def split_by(val)
[0, a.index(val)], a[a.index(val)+1..-1]
end
end
array = ["Member", "Friends", "Hello", "Components", "Family", "Lastname"]
first, second = array.split_by("Complonents")
first
#=> ["Member", "Friends", "Hello"]
second
#=> ["Family", "Lastname"]
or inliner
array = ["Member", "Friends", "Hello", "Components", "Family", "Lastname"]
first, second = array[0, a.index("Components")], a[a.index("Components")+1..-1]
Related
I have two arrays of hashes. Here's the structure of the arrays:
array_1 = [{:address=>"123 Main St", :locality=>"New York",
:neighbourhood=>"Main", :lot_front=>18.0, :lot_depth=>37.0,
:property_type=>"Detached", :asking_price=>156000}]
array_2 = [{:address=>"121 Orchard Heights Dr", :locality=>"Clarington",
:neighbourhood=>"Newcastle", :lot_front=>19.0, :lot_depth=>46.0,
:property_type=>"Detached", :closed_price=>270000,
:sold_date=>"2013-04-02"}]
My goal is to do the following:
For every item in array_1, find all items in array_2 that have the same values for :locality and the :neighbourhood. Then....find the average of all the values of :closed_price of the items from the search.
I'd like the code to loop through all the items in array_1 and execute the above logic.
I'm a beginner and have tried everything I know. Nothing has worked.
Thanks for your help.
Try this :)
I tried to be as simple and readable as possible on this solution.
def average(array)
array.sum.to_f / array.size.to_f
end
array_1.map do |a|
closed_prices = array_2.select do |b|
a[:locality] == b[:locality] and a[:neighbourhood] == b[:neighbourhood]
end.map do |matched|
matched[:closed_price]
end
average(closed_prices)
end
I used the following hashes to test this code:
I removed the extra keys from the hashes to simplify, removing noises.
array_1 = [
{
:locality => "New York",
:neighbourhood => "Main"
},
{
:locality => "Clarington",
:neighbourhood => "Newcastle"
},
]
array_2 = [
{
:locality => "Clarington",
:neighbourhood => "Newcastle",
:closed_price => 270000
},
{
:locality => "New York",
:neighbourhood => "Main",
:closed_price => 100000
},
{
:locality => "New York",
:neighbourhood => "Main",
:closed_price => 200000
}
]
This would return the array
[150000.0, 270000.0]
Which is the average of the :closed_price values for all the items of the array_2 that have the same :locality and :neighbourhood for each item of the array_1. This resulting array has two elements on this case because the array_1 has two elements.
You can also monkey patch the Array class to implement the avg() method right there if you want on this solution!
This will make the code much more simple.
Like this:
array_1.map do |a|
array_2.select do |b|
a[:locality] == b[:locality] and a[:neighbourhood] == b[:neighbourhood]
end.map do |matched|
matched[:closed_price]
end.avg
end
I explained more about monkey patching right here if you desire: How do I create an average from a Ruby array?
Try the following:
array_1.each do |el|
close_addresses = array_2.filter { |sub_el| el[:locality] == sub_el[:locality] && el[:neighbourhood] == sub_el[:neighbourhood] }
if close_addresses.any?
el[:expected_price] = close_addresses.instance_eval { reduce(0) { |sum, sub_el| sum + sub_el[:closed_price] } / size }
end
end
Added the :expected_price key to the array_1 elements to save the average :closed_price values.
array_1 = [{:address=>"123 Main St", :locality=>"New York",
:neighbourhood=>"Main", :lot_front=>18.0, :lot_depth=>37.0,
:property_type=>"Detached", :asking_price=>156000},
{:address=>"321 Niam St", :locality=>"New York",
:neighbourhood=>"Niam", :lot_front=>18.0, :lot_depth=>37.0,
:property_type=>"Unattached", :asking_price=>100000}]
array_2 = [{:address=>"121 Orchard Heights Dr", :locality=>"New York",
:neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
:property_type=>"Detached", :closed_price=>270000,
:sold_date=>"2013-04-02"},
{:address=>"121 Orchard Heights Dr", :locality=>"New York",
:neighbourhood=>"Niam", :lot_front=>19.0, :lot_depth=>46.0,
:property_type=>"Detached", :closed_price=>600000,
:sold_date=>"2013-04-02"},
{:address=>"121 Orchard Heights Dr", :locality=>"New York",
:neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
:property_type=>"Detached", :closed_price=>400000,
:sold_date=>"2013-04-02"}]
First construct a hash h2 whose keys are distinct pairs of the values of :locality and neighborhourhood in array_2 and whose values are the elements of h2 (hashes) whose values for those keys correspond to the elements of the key:
h2 = array_2.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[[g[:locality], g[:neighbourhood]]] << g
end
#=> {["New York", "Main"]=>[
# {:address=>"121 Orchard Heights Dr", :locality=>"New York",
# :neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
# :property_type=>"Detached", :closed_price=>270000,
# :sold_date=>"2013-04-02"},
# {:address=>"121 Orchard Heights Dr", :locality=>"New York",
# :neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
# :property_type=>"Detached", :closed_price=>400000,
# :sold_date=>"2013-04-02"}
# ],
# ["New York", "Niam"]=>[
# {:address=>"121 Orchard Heights Dr", :locality=>"New York",
# :neighbourhood=>"Niam", :lot_front=>19.0, :lot_depth=>46.0,
# :property_type=>"Detached", :closed_price=>600000,
# :sold_date=>"2013-04-02"}
# ]
# }
See the form of Hash::new that takes a block.
Now merge each element (hash) of array_1 with a hash having the single key :closed_price whose value is the average of the values of :close_price taken over all elements in h2 for which the values of :locality and neighborhourhood are the same as those for the element of array_1 being matched:
array_1.map do |g|
a2s = h2[ [g[:locality], g[:neighbourhood]] ]
g.merge( closed_avg: a2s.sum { |g| g[:closed_price] }.fdiv(a2s.size) )
end
[{:address=>"123 Main St", :locality=>"New York",
:neighbourhood=>"Main", :lot_front=>18.0, :lot_depth=>37.0,
:property_type=>"Detached", :asking_price=>156000, :closed_avg=>335000.0},
{:address=>"321 Niam St", :locality=>"New York",
:neighbourhood=>"Niam", :lot_front=>18.0, :lot_depth=>37.0,
:property_type=>"Unattached", :asking_price=>100000, :closed_avg=>600000.0}]
See Hash#merge and Numeric#fdiv. merge does not mutate (modify) any of the elements (hashes) of array_1.
The construction of h2 is effectively the same as the following:
h2 = {}
array_2.each do |g|
arr = [g[:locality], g[:neighbourhood]]
h2[arr] = [] unless h2.key?(arr)
h2[arr] << g
end
h2
array_of_hash1 =
[{"Date" => "2019-07-01", "Country" => "US", "Email" => "sample1#gmail.com", "Price" => "11.224323", "Tax" => "8.55443"},
{"Date" => "2019-07-01", "Country" => "US", "Email" => "sample2#gmail.com", "Price" => "16.664323", "Tax" => "6.55443"},
{"Date" => "2019-06-30", "Country" => "US", "Email" => "sample3#gmail.com", "Price" => "17.854323", "Tax" => "7.12343"},
{"Date" => "2019-07-02", "Country" => "UK", "Email" => "sample4#gmail.com", "Price" => "14.224323", "Tax" => "4.32443"}]
array_of_hash2 =
[{"Date" => "2019-07-01", "Name" => "John", "Price" => "11.3442223", "Tax" => "3.44343"},
{"Date" => "2019-07-01", "Name" => "Jack", "Price" => "14.332323", "Tax" => "5.41143"},
{"Date" => "2019-07-02", "Name" => "Sam", "Price" => "10.2223443", "Tax" => "2.344552"}]
Above are my inputs in array of hashes
Add Price and Tax in array_of_hash1 by Date
Add Price and Tax in array_of_hash2 by Date
Subtract as (i) - (ii)
If there are no values in array_of_hash2 by Date compare with array_of_hash1. Then take values from array_of_hash1 only.
Here is my expected output.
Expected Output:
[{"Date" => "2019-07-01", "Country" => "US", "Email" => "sample1#gmail.com", "Price" => "2.2121007000000006", "Tax" => "6.254"},
{"Date" => "2019-07-02", "Country" => "UK", "Email" => "sample4#gmail.com", "Price" => "4.0019787", "Tax" => "1.9798780000000002"},
{"Date" => "2019-06-30", "Country" => "US", "Email" => "sample3#gmail.com", "Price" => "17.854323", "Tax" => "7.12343"}]
Using two helper methods for a DRY code and using Enumerable#sum, Enumerable#group_by, Hash#merge, Hash#transform_values and also other methods you can find in the documentation.
I'm using also Object#then here.
def sum_price_tax(ary)
ary.first.merge ary.then { |ary| { "Price" => ary.sum { |h| h["Price"].to_f }, "Tax" => ary.sum { |h| h["Tax"].to_f} } }
end
def group_and_sum(array_of_hash)
array_of_hash.group_by { |h| h["Date"] }.transform_values { |ary| sum_price_tax(ary) }
end
After the methods are defined, you can do:
a1 = group_and_sum(array_of_hash1)
a2 = group_and_sum(array_of_hash2)
a1.map { |k, v| v.merge(a2[k] || {}) { |h, old_val, new_val| old_val.is_a?(Float) ? old_val - new_val : old_val } }
#=> [{"Date"=>"2019-07-01", "Country"=>"US", "Email"=>"sample1#gmail.com", "Price"=>2.2121007000000006, "Tax"=>6.254, "Name"=>"John"}, {"Date"=>"2019-06-30", "Country"=>"US", "Email"=>"sample3#gmail.com", "Price"=>17.854323, "Tax"=>7.12343}, {"Date"=>"2019-07-02", "Country"=>"UK", "Email"=>"sample4#gmail.com", "Price"=>4.0019787, "Tax"=>1.9798780000000002, "Name"=>"Sam"}]
In this way also the "Name" is present.
One way you could get rid of "Name" is using Object#tap and Hash#delete:
a1.map { |k, v| v.merge(a2[k] || {}) { |h, old_val, new_val| old_val.is_a?(Float) ? old_val - new_val : old_val }.tap { |h| h.delete("Name") } }
#=> [{"Date"=>"2019-07-01", "Country"=>"US", "Email"=>"sample1#gmail.com", "Price"=>2.2121007000000006, "Tax"=>6.254}, {"Date"=>"2019-06-30", "Country"=>"US", "Email"=>"sample3#gmail.com", "Price"=>17.854323, "Tax"=>7.12343}, {"Date"=>"2019-07-02", "Country"=>"UK", "Email"=>"sample4#gmail.com", "Price"=>4.0019787, "Tax"=>1.9798780000000002}]
We are given the following (simplified from the arrays given in the question and with one hash added to arr2):
arr1 = [
{"Date"=>"2019-07-01", "Country"=>"US", "Price"=>"11.22", "Tax"=>"8.55"},
{"Date"=>"2019-07-01", "Country"=>"US", "Price"=>"16.66", "Tax"=>"6.55"},
{"Date"=>"2019-06-30", "Country"=>"US", "Price"=>"17.85", "Tax"=>"7.12"},
{"Date"=>"2019-07-02", "Country"=>"UK", "Price"=>"14.22", "Tax"=>"4.32"}
]
arr2 = [
{"Date"=>"2019-07-01", "Price"=>"11.34", "Tax"=>"3.44"},
{"Date"=>"2019-07-01", "Price"=>"14.33", "Tax"=>"5.41"},
{"Date"=>"2019-07-02", "Price"=>"10.22", "Tax"=>"2.34"},
{"Date"=>"2019-07-03", "Price"=>"14.67", "Tax"=>"3.14"}
]
We will need a list of dates that are values of "Date" in the hashes in arr1.
dates1 = arr1.map { |g| g["Date"] }.uniq
#=> ["2019-07-01", "2019-06-30", "2019-07-02"]
Now convert arr2 to an array of those elements h in arr2 for which h["Date"] is in dates1, with all keys other than "Price" and "Tax" removed from each hash retained, and with the values of those two keys converted to string representations of their values negated:
a2 = arr2.each_with_object([]) do |g,arr| arr <<
{ "Date"=>g["Date"], "Price"=>"-" << g["Price"], "Tax" =>"-" << g["Tax"] } if
dates1.include?(g["Date"])
end
#=> [{"Date"=>"2019-07-01", "Price"=>"-11.34", "Tax"=>"-3.44"},
# {"Date"=>"2019-07-01", "Price"=>"-14.33", "Tax"=>"-5.41"},
# {"Date"=>"2019-07-02", "Price"=>"-10.22", "Tax"=>"-2.34"}]
We now loop over all elements of arr1 and a2 to create hash with keys the values of "Date", with values of "Price" and "Tax" aggregated. Once that is done we extract the value of the hash that has been constructed.
(arr1 + a2).each_with_object({}) do |g,h|
h.update(g["Date"]=>g) do |_,merged_hash,hash_to_merge|
merged_hash.merge(hash_to_merge) do |k,merged_str,str_to_merge|
["Price", "Tax"].include?(k) ? "%.2f" %
(merged_str.to_f + str_to_merge.to_f) : merged_str
end
end
end.values
#=> [{"Date"=>"2019-07-01", "Country"=>"US", "Price"=>"2.21", "Tax"=>"6.25"},
# {"Date"=>"2019-06-30", "Country"=>"US", "Price"=>"17.85", "Tax"=>"7.12"},
# {"Date"=>"2019-07-02", "Country"=>"UK", "Price"=>"4.00", "Tax"=>"1.98"}]
In this last step the receiver of values is found to be the hash:
{"2019-07-01"=>{"Date"=>"2019-07-01", "Country"=>"US",
"Price"=>"2.21", "Tax"=>"6.25"},
"2019-06-30"=>{"Date"=>"2019-06-30", "Country"=>"US",
"Price"=>"17.85", "Tax"=>"7.12"},
"2019-07-02"=>{"Date"=>"2019-07-02", "Country"=>"UK",
"Price"=>"4.00", "Tax"=>"1.98"}}
Notice that the result would be the same if arr[1]["Country"]=>"Canada". I've assumed that would not be a problem or could not occur.
The last step uses versions of the methods Hash#update (a.k.a. merge!) and Hash#merge that employ a hash to determine the values of keys that are present in both hashes being merged.
The values of the block variables (|_,merged_hash,hash_to_merge| and |k,merged_str,str_to_merge|) are explained in the docs. The first block variable is the common key (_ and k). I've represented the first of these with an underscore to signal to the reader that it is not used in the block calculation (a common convention). The second block variable is the value of the key in the hash being built (merged_hash and merged_str). The third block variable is the value of the key in the hash being merged (merged_hash and str_to_merge).
I want to loop through a hash that contains a mix of nested object types. E.g.
{
"a" => "1",
"b" => ["1", "2"],
"c" => {"d" => "1", "e" => {"f" => ["1", "2", {"g" => ["x", "y", "1"]}]}}
}
and transform all string values to integers, given that they consist of numbers only. So they should match /^\d+$/.
But the important part is the iteration part.
How do I find all the values of key/value pairs as well as all values inside arrays, so I can manipulate them?
It's a bit similar to the deep_symbolize_keys method, only here I'm interested in everything else that hash keys.
My own implementation goes like this, but it's long and I may not have catched all edge cases:
def all_to_i(x)
if x.is_a?(Hash)
x.each do |k, v|
if v.is_a?(Hash)
all_to_i(v)
elsif v.is_a?(Array)
x[k] = v.map { |v2| all_to_i(v2) }
elsif v.is_a?(String)
if v.scan(/^\d+$/) != []
x[k] = v.to_i
else
x[k] = v
end
end
end
elsif x.is_a?(Array)
x.map { |x2| all_to_i(x2) }
elsif x.is_a?(String)
if x.scan(/^\d+$/) != []
x.to_i
else
x
end
end
end
# => {"a"=>1, "b"=>[1, 2], "c"=>{"d"=>1, "e"=>{"f"=>[1, 2, {"g"=>["x", "y", 1]}]}}}
I have the following hash
{ "1" => "a", "2" => "a", "3" => "2"}
and the following sqlite3 table named ITEMS:
POSITION CODE NAME VALUE
"1" "a" "Cat" "Small"
"1" "b" "Cat" "Big"
"2" "a" "Fish" "Red"
"2" "b" "Fish" "Blue"
"3" "1" "Car" "Fast"
"3" "2" "Car" "Slow"
I would like to query the table and return a hash of format {"NAME" => "VALUE"}.
The input hash is in the format {"POSITION" => "CODE"}, so the first value in the hash above has position "1" with code "a", which would return Name "Cat" and Value "Small". The real version of this will have an initial hash of 20 values, and a table with closer to 200 records.
The example hash above should return:
{ "Cat" => "Small", "Fish" => "Red", "Car" => "Slow" }
Here is some code :
Item.where("(POSITION = ? AND CODE = ?) OR
(POSITION = ? AND CODE = ?) OR
(POSITION = ? AND CODE = ?)", *hash.flatten
)
.select("name, value")
.map { |rec| [rec.name, rec.value] }.to_h
update
As #mudasobwa said
Item.where((["(POSITION = ? AND CODE = ?)"] * hash.size).join(' OR '), hash.flatten)
.select("name, value")
.map { |rec| [rec.name, rec.value] }.to_h
This is a wonderful tip. I liked it too much.
Hello I wanted to create a hash that would be append the email with name if that name exist in someother hash. I believe there would be small piece of code that will do the ruby magic.
Input
a = [
{"email"=>"foobar#gmail.com", "name"=>"Adam"},
{"email"=>"test#gmail.com", "name"=>"John"},
{"email"=>"hello#gmail.com", "name"=>"Adam"}
]
Output
a = [
{"email"=>"foobar#gmail.com", "name"=>"Adam | foobar#gmail.com"},
{"email"=>"test#gmail.com", "name"=>"John"},
{"email"=>"hello#gmail.com", "name"=>"Adam | hello#gmail.com "}
]
Here's another option:
# get names
names = a.map {|e| e['name'] }
# find duplicates
duplicates = names.detect {|e| names.count(e) > 1 }
# append email to duplicates
a.each { |e| e['name'] = "#{e['name']} | #{e['email']}" if duplicate.include?(e['name'])}
Given your input
a = [
{"email"=>"foobar#gmail.com", "name"=>"Adam"},
{"email"=>"test#gmail.com", "name"=>"John"},
{"email"=>"hello#gmail.com", "name"=>"Adam"}
]
And I'll provide a cleverly named comparison hash of my own
b = [
{"name"=>"Adam"},
{"name"=>"Adam"}
]
Here's your ruby magic
a.map do |hash_a|
# This is the 'magic' line that compares the values of
# the two hashes name values
if b.any? { |hash_b| hash_b['name'] == hash_a['name'] }
hash_a['name'] << " | #{hash_a['email']}"
hash_a
else
hash_a
end
end
Output:
[
{
"email" => "foobar#gmail.com",
"name" => "Adam | foobar#gmail.com"
},
{
"email" => "test#gmail.com",
"name" => "John"
},
{
"email" => "hello#gmail.com",
"name" => "Adam | hello#gmail.com"
}
]