I have an array of paths:
paths = ["home", "usr/lib/folder1/", "usr/lib/folder2/"]
I tried to make a tree with full path on the ends (leaves):
{"home" => "home", "usr" => {"lib" => {"folder1" => "usr/lib/folder1/", "folder2" => "usr/lib/folder2/"}}}
This is my code:
paths.each do |path|
current = tree
path.split('/').inject('') do |_sub_path, dir|
sub_path = File.join(dir)
current[sub_path] ||= {path => {}}
current = current[sub_path]
sub_path
end
end
Can you show me the right way for my question?
This is my first question on SO. Sorry if my English bad.
This will do it but its not tested nor is it very Rubyish...
paths = ["home", "usr/lib/folder1/", "usr/lib/folder2/"]
ans =
paths.reduce({}){
|acc, e|
last = e.split('/').last
e.split('/').reduce(acc){
|acc, n|
acc[n] = {} if acc[n].nil?
acc[n] = e if n == last
acc[n]
}
acc
}
p ans
What's your strategy for adding more paths?I.E. If you have to add another path like "home/user/data" and "/home/user/docs" or "usr/lib/folder1/data" and "usr/lib/folder2/data", what's your strategy?
This could be solved recursively.
def recurse(arr, arr_build = [])
return arr_build.join('/') if arr == [[]]
arr.group_by { |a| a.first }.
transform_values { |v| recurse(v.map { |a| a.drop(1) },
arr_build + [v.first.first]) }
end
recurse(paths.map { |s| s.split('/') })
#=> {"home"=>"home",
# "usr"=>{"lib"=>{"folder1"=>"usr/lib/folder1",
# "folder2"=>"usr/lib/folder2"}}}
See Enumerable#group_by and Hash#transform_values.
Note that when
arr = paths.map { |s| s.split('/') }
#=> [["home"], ["usr", "lib", "folder1"], ["usr", "lib", "folder2"]]
we find that
arr.group_by { |a| a.first }
#=> {"home"=>[["home"]],
# "usr"=>[["usr", "lib", "folder1"], ["usr", "lib", "folder2"]]}
Related
I have an array with hashes in it. If they have the same key I just want to add its value.
#receivers << result
#receivers
=> [{:email=>"user_02#yorlook.com", :amount=>10.00}]
result
=> {:email=>"user_02#yorlook.com", :amount=>7.00}
I want the result of above to look like this
[{:email=>"user_02#yorlook.com", :amount=>17.00}]
Does anyone know how to do this?
Here is the the entire method
def receivers
#receivers = []
orders.each do |order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result = { email: product_email, amount: amount(order.total_price) }
else
result = { email: product_email, amount: amount(order.total_price, 0.9),
email: outfit_email, amount: amount(order.total_price, 0.1) }
end
#receivers << result
end
end
Using Enumerable#group_by
#receivers.group_by {|h| h[:email]}.map do |k, v|
{email: k, amount: v.inject(0){|s,h| s + h[:amount] } }
end
# => [{:email=>"user_02#yorlook.com", :amount=>17.0}]
Using Enumerable#each_with_object
#receivers.each_with_object(Hash.new(0)) {|h, nh| nh[h[:email]]+= h[:amount] }.map do |k, v|
{email: k, amount: v}
end
# Output: [{ "em#il.one" => 29.0 }, { "em#il.two" => 39.0 }]
def receivers
return #receivers if #receivers
# Produces: { "em#il.one" => 29.0, "em#il.two" => 39.0 }
partial_result = orders.reduce Hash.new(0.00) do |result, order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result[product_email] += amount(order.total_price)
else
result[product_email] += amount(order.total_price, .9)
result[outfit_email] += amount(order.total_price, .1)
end
result
end
#receivers = partial_result.reduce [] do |result, (email, amount)|
result << { email => amount }
end
end
I would just write the code this way:
def add(destination, source)
if destination.nil?
return nil
end
if source.class == Hash
source = [source]
end
for item in source
target = destination.find {|d| d[:email] == item[:email]}
if target.nil?
destination << item
else
target[:amount] += item[:amount]
end
end
destination
end
usage:
#receivers = []
add(#receivers, {:email=>"user_02#yorlook.com", :amount=>10.00})
=> [{:email=>"user_02#yorlook.com", :amount=>10.0}]
add(#receivers, #receivers)
=> [{:email=>"user_02#yorlook.com", :amount=>20.0}]
a = [
{:email=>"user_02#yorlook.com", :amount=>10.0},
{:email=>"user_02#yorlook.com", :amount=>7.0}
]
a.group_by { |v| v.delete :email } # group by emails
.map { |k, v| [k, v.inject(0) { |memo, a| memo + a[:amount] } ] } # sum amounts
.map { |e| %i|email amount|.zip e } # zip to keys
.map &:to_h # convert nested arrays to hashes
From what I understand, you could get away with just .inject:
a = [{:email=>"user_02#yorlook.com", :amount=>10.00}]
b = {:email=>"user_02#yorlook.com", :amount=>7.00}
c = {email: 'user_03#yorlook.com', amount: 10}
[a, b, c].flatten.inject({}) do |a, e|
a[e[:email]] ||= 0
a[e[:email]] += e[:amount]
a
end
=> {
"user_02#yorlook.com" => 17.0,
"user_03#yorlook.com" => 10
}
I'm new to Ruby and trying to solve a problem. I have an array of hashes:
list = [{"amount"=>2.25,"rel_id"=>1103, "date"=>"2012-12-21"},
{"amount"=>2.75,"rel_id"=>1103, "date"=>"2012-12-24"},
{"amount"=>2.85,"rel_id"=>666, "date"=>"2012-12-27"},
{"amount"=>3.15,"rel_id"=>666, "date"=>"2012-12-28"}
#and many many more..
]
I need to group them by rel_id, that i could see total amount and dates they were given, in this kind of format:
{1103=>{:total_amount=>5.0, :dates=>["2012-12-21", "2012-12-24"]}, 666=>{:total_amount=>6.0, :dates=>["2012-12-27", "2012-12-28"]}}
I solved this in this way, but i'm pretty sure it's one of the worst approach to do that and i think it's not a ruby way..
results = {}
list.each do |line|
if !(results.has_key?(line["rel_id"]))
results[line["rel_id"]]={:total_amount=>line["amount"],:dates=>[line["date"]]}
else
results[line["rel_id"]][:total_amount] = results[line["rel_id"]][:total_amount]+line["amount"]
results[line["rel_id"]][:dates]<<line["date"]
end
end
Maybe you could give me or explain how to implement a nicer, more beautiful approach in a ruby way?
You can do something like this:
list.each_with_object({}) do |details, rollup|
rollup[details["rel_id"]] ||= { total_amount: 0, dates: [] }
rollup[details["rel_id"]][:total_amount] += details["amount"]
rollup[details["rel_id"]][:dates] << details["date"]
end
Edited for readability/names.
Functional approach (I'll use mash, use Hash[...] if no Facets):
purchases_grouped = list.group_by { |p| p["rel_id"] }
result = purchases_grouped.mash do |rel_id, purchases|
total_amount = purchases.map { |p| p["amount"] }.reduce(:+)
dates = purchases.map { |p| p["date"] }
accumulated = {total_amount: total_amount, dates: dates}
[rel_id, accumulated]
end
#=> {1103=>{:total_amount=>5.0, :dates=>["2012-12-21", "2012-12-24"]},
# 666 =>{:total_amount=>6.0, :dates=>["2012-12-27", "2012-12-28"]}}
h = list.group_by{|h| h["rel_id"]}
h.each{|k, v| h[k] = {
total_amount: v.inject(0){|x, h| x + h["amount"]},
dates: v.map{|h| h["date"]},
}}
h # => ...
Or
h = list.group_by{|h| h["rel_id"]}
h.each{|k, v| h[k] = {
total_amount: v.map{|h| h["amount"]}.inject(:+),
dates: v.map{|h| h["date"]},
}}
h # => ...
list = [
{amount: 2.25, rel_id: 1103, date: "2012-12-21"},
{amount: 2.75, rel_id: 1103, date: "2012-12-24"},
{amount: 2.85, rel_id: 666, date: "2012-12-27"},
{amount: 3.15, rel_id: 666, date: "2012-12-28"},
]
results = Hash.new do |hash, key|
hash[key] = {}
end
list.each do |hash|
totals = results[hash[:rel_id]]
totals[:amount] ||= 0
totals[:amount] += hash[:amount]
totals[:dates] ||= []
totals[:dates] << hash[:date]
end
p results
--output:--
{1103=>{:amount=>5.0, :dates=>["2012-12-21", "2012-12-24"]},
666=>{:amount=>6.0, :dates=>["2012-12-27", "2012-12-28"]}}
Alex Peachey's each_with_object solution modified:
results = list.each_with_object({}) do |h, acc|
record = acc[h["rel_id"]]
record ||= { total_amount: 0, dates: [] }
record[:total_amount] += h["amount"]
record[:dates] << h["date"]
end
I'm trying to write my code more compact. I have three hashes. The first hash (#hash) is a collection of sub-hashes (value_1, value_2)
#hash = {
"Key" => ["value_1", "value_2"]
}
#value_1 = {
"Foo" => ["bar_1", "bar_2"]
}
#value_2 = {
"Foo2" => ["bar2_1", "bar2_2"]
}
Now, in my haml-view i'm trying to make something like this:
- i = 0
- #hash.each_value do |value|
- #value_[i].each_pair do |k, v|
= k
= v[0]
- i = i +1
I don't want to write one hash after the other. It's a bit similar to making a symbol out of a string, where you can write somthing like "value_#{i}".to_sym. I hope, somebody can follow and help me.
#hashes = [
{
"Foo" => ["bar_1", "bar_2"]
},
{
"Foo2" => ["bar2_1", "bar2_2"]
}
]
Then
#hashes.each do |v|
v.each_pair do |k,v|
= k
= v[0]
And if you need the index use each_with_index.
EDIT
Try this:
- #hash.each_value do |value|
- value.each do |v|
- instance_variable_get(:"##{v}").each_pair do |k, val|
= k
= val[0]
API
I have a string like this,
str = "uu#p, xx#m, yy#n, zz#m"
I want to know how to convert the given string into a hash. (i.e my actual requirement is, how many values (before the # symbol) have the m, n and p. I don't want the counting, I need an exact value). The output would be better like this,
{"m" => ["xx", "zz"], "n" => ["yy"], "p" => ["uu"]}
Can help me anyone, please?
Direct copy/past of an IRB session:
>> str.split(/, /).inject(Hash.new{|h,k|h[k]=[]}) do |h, s|
.. v,k = s.split(/#/)
.. h[k] << v
.. h
.. end
=> {"p"=>["uu"], "m"=>["xx", "zz"], "n"=>["yy"]}
Simpler code for a newbie :)
str = "uu#p, xx#m, yy#n, zz#m"
h = {}
str.split(",").each do |x|
v,k = x.split('#')
h[k] ||= []
h[k].push(v)
end
p h
FP style:
grouped = str
.split(", ")
.group_by { |s| s.split("#")[1] }
.transform_values { |ss| ss.map { |x| s.split("#")[0] } }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}
This is a pretty common pattern. Using Facets.map_by:
require 'facets'
str.split(", ").map_by { |s| s.split("#", 2).reverse }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}
I have a simple array:
arr = ["apples", "bananas", "coconuts", "watermelons"]
I also have a function f that will perform an operation on a single string input and return a value. This operation is very expensive, so I would like to memoize the results in the hash.
I know I can make the desired hash with something like this:
h = {}
arr.each { |a| h[a] = f(a) }
What I'd like to do is not have to initialize h, so that I can just write something like this:
h = arr.(???) { |a| a => f(a) }
Can that be done?
Say you have a function with a funtastic name: "f"
def f(fruit)
fruit + "!"
end
arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]
will give you:
{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}
Updated:
As mentioned in the comments, Ruby 1.8.7 introduces a nicer syntax for this:
h = Hash[arr.collect { |v| [v, f(v)] }]
Did some quick, dirty benchmarks on some of the given answers. (These findings may not be exactly identical with yours based on Ruby version, weird caching, etc. but the general results will be similar.)
arr is a collection of ActiveRecord objects.
Benchmark.measure {
100000.times {
Hash[arr.map{ |a| [a.id, a] }]
}
}
Benchmark #real=0.860651, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.8500000000000005, #total=0.8500000000000005
Benchmark.measure {
100000.times {
h = Hash[arr.collect { |v| [v.id, v] }]
}
}
Benchmark #real=0.74612, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.740000000000002, #total=0.750000000000002
Benchmark.measure {
100000.times {
hash = {}
arr.each { |a| hash[a.id] = a }
}
}
Benchmark #real=0.627355, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.6199999999999974, #total=0.6299999999999975
Benchmark.measure {
100000.times {
arr.each_with_object({}) { |v, h| h[v.id] = v }
}
}
Benchmark #real=1.650568, #cstime=0.0, #cutime=0.0, #stime=0.12999999999999998, #utime=1.51, #total=1.64
In conclusion
Just because Ruby is expressive and dynamic, doesn't mean you should always go for the prettiest solution. The basic each loop was the fastest in creating a hash.
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
Ruby 2.6.0 enables a shorter syntax by passing a block to the to_h method:
arr.to_h { |a| [a, f(a)] }
This is what I would probably write:
h = Hash[arr.zip(arr.map(&method(:f)))]
Simple, clear, obvious, declarative. What more could you want?
I'm doing it like described in this great article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }
More info about inject method: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject
Another one, slightly clearer IMHO -
Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Using length as f() -
2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
=> ["apples", "bananas", "coconuts", "watermelons"]
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
=> {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11}
2.1.5 :028 >
in addition to the answer of Vlado Cingel (I cannot add a comment yet, so I added an answer).
Inject can also be used in this way: the block has to return the accumulator. Only the assignment in the block returns the value of the assignment, and an error is reported.
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }