I have an array of hashes lets say offers = {offer1,...,offer6}. Each offer is a hash.
Each offer has various keys one of which is a price hash:
offer1 = {..., :price => {:amount => 400, :amount2 => 300, :currency => "INR",...}
Now I want to return a hash with only the unique hashes/offers. Also, I want :price hash to have the values averaged.
So, the final offers array will have:
offers=[offer1, offer2,...]
offer1[:price] will have values equal to average of the values of each key from duplicated offer hashes and same with offer2[:price] and so on so that in end I end with an hash with only unique offers.
offers1..6 can be duplicate with same id and price hash different. If they are duplicate we need to do the averaging otherwise not.
Is there an elegant way to do all of this?
I have tried grouping the hashes with a unique key and merging the price hashes of each. But am unable to reach the final solution.
my attempt:
rooms_hash = rooms.map do |room|
unless room[:offers].uniq.count == room[:offers].count
grouped_offers = room[:offers].group_by{|x| x[:room_category_id]}
offer_values = grouped_offers.values
price_array = offer_values.map do |v|
v.inject do |k, v|
k.merge!(price: k[:price].merge(v[:price]){|_, a, b| [a, b].flatten })
end
end
price_array.map do |o|
o[:price] = {}.tap{ |h| o[:price].each {|k, list| h[k] = list.all?{|e| [Fixnum, NilClass].include? e.class} ? list.map(&:to_i).sum/list.size : list.compact.first ; h } }
end
price_array
end
end
rooms.zip(rooms_hash).map do |room,averaged_offers|
if room[:offers].count > 1
room[:offers] = averaged_offers.select{|offer| offer[:room_category_id] == room[:room_category_id]}
room
end
end
This code actually fails when I don't get any duplicates. So how can I check for that as well?
Edit:
Yes offers is an array and offers1..6 are hashes.
offer1..._hash = {:offer_id=>"uuid", :price=>{:amount=>4422380, :gross_amount=>4422380, :currency=>"INR", :tax=>434318, :hotel_fees=>0, :base_fare=>3988062}, .....}
Related
I am trying to iterate over a hash returned from an active record search.
the data comes back {[k, v] => v, [k,v] => v, etc.} and I need to place that data in 3 separate columns of a table.
what I have so far in a helper is
data = {}
connector = 0
us_cords = 0
eu_cords = 0
molex_connector = 0
chart_data_two.each do |key, value|
data[key[0]] ||= Hash.new
data[key[0]][key[1]] = value
end
return data
This gives me the k out of the k,v pair above and then a hash with "v" => v from above.
so I am having a hard time wrapping my head around iterating over the data hash and putting it into the view table in each column.
The view has #table_data_two = chart_qty_monthly_data(#chart_data_two) and then #table_data_two.each do |k,v| for generating each row/column.
Eventually it might be nice to do only one merged cell for month with 4 cells for model and then the quantities
Here's one way to build that:
data = Hash.new { |h, k| h[k] = {} }
chart_data_two.each do |key, value|
model_quantity = { key[0] => value }
data[key[1]].merge!(model_quantity)
end
If you want it to be in order by month, you can change the keys to integers and sort it:
data = Hash.new { |h, k| h[k] = {} }
chart_data_two.each do |key, value|
model_quantity = { key[0] => value }
data[key[1].to_i].merge!(model_quantity)
end
sorted_data = data.sort.to_h
It's not perfect, so I'll look it over again and see what could be improved, but it will at least get you started, assuming I understood your question correctly.
ended up doing it a bit different:
data = {}
#initialize hash keys in the order you want them to end up in
(1..12).each do |month|
data[month] = {"Connector" => 0, "US Cords" => 0, "EU Cords" => 0}
end
chart_data_two.each do |key, value|
#key[1] is month. we're referencing the keys we initialized above
#key[2] is model. we're adding a new key to the nested hash we initialized above
#logger.info "#{key[0]}, #{key[1]}, #{value}"
data[key[1].to_i][key[0]] = value
#logger.info "%%%%%%%% inside iterator #{data}"
end
#logger.info "$$$$$ passing #{data} into chart"
return data
end
I got help with figuring that out, but I wanted to post it here.
I have the following snippet of code to fetch certain columns from the db:
#data = Topic.select("id,name").where("id in (?)",#question.question_topic.split(",")).map(&:attributes)
In the resulting Array of Hashes which is :
Current:
#data = [ { "id" => 2, "name" => "Sports" }]
To be changed to:
#data = [ { "id" => "2", "name" => "Sports" }]
I want to convert "id" to string from fixnum. Id is integer in the db. What is the cleanest way to do this?
Note: After using .map(&:attributes) it is not an active record relation.
You can do it with proper map usage:
topics = Topic.select("id,name").where("id in (?)",#question.question_topic.split(","))
#data = topics.map do |topic|
{
'id' => topic.id.to_s,
'name' => topic.name
}
end
What you're looking for is simply
#data.each { |obj| obj["id"] = obj["id"].to_s }
There isn't really a simpler way (I think that's straightforward enough anyway).
Going by the title which implies a different question - converting every value in the hash to a string you can do this:
#data.each do |obj|
obj.map do |k, v|
{k => v.to_s}
end
end
Just leaving that there anyway.
You can use Ruby's #inject here:
#data.map do |datum|
new_datum = datum.inject({}) do |converted_datum, (key, value)|
converted_datum[key] = value.to_s
converted_datum
end
end
This will work to convert all values to strings, regardless of the key.
If you are using Rails it can be even cleaner with Rails' #each_with_object:
#data.map do |datum|
datum.each_with_object({}) do |(key, value), converted_datum|
converted_datum[key] = value.to_s
end
end
This will iterate all the key names in the hash and replace the value with the #to_s version of the datum associated with the key. nil's converted to empty strings. Also, this assumes you don't have complex data within the hash like embedded arrays or other hashes.
def hash_values_to_string(hash)
hash.keys.each {|k| hash[k]=hash[k].to_s}; hash
end
Here's a sample of array:
{"C1"=>[
{:upc=>"51857195821952", :product_id=>"1234", :name=>"name", :price=>" $15 ", :color=>"green", :size=>"L", :description=>"descr"},
{:upc=>"352353wegs", :product_id=>"456", :name=>"name2", :price=>"$21", :color=>"black", :size=>"S", :description=>"descr"}, # ...
],
#...
}
And here as I am trying to fetch data from that array:
#array.each do |p|
product = Product.new
product.sku = p[0]
product.name = p[1][0][:name] #can't convert Symbol into Integer
price = p[1].select{ |pr| !pr[:price].nil? and pr[:price] != "0" }.min_by{ |i| i[:price].to_f }[:price]
product.price = "%.2f" % (price.to_f)
...
end
Every time I try to fetch data from the array, I get on the line product.name = the error can't convert Symbol into Integer.
What is wrong in this case? I spent a part of afternoon on this issue, but unfortunately I still cannot figure out it...
Thanky you
Your #array is actually a hash. It is formated like following:
{
'name1' => [{:upc => "..."},{:upc => "..."}],
'name2' => [{:upc => "..."},{:upc => "..."}],
#...
}
Since it is a Hash, you can use 2 arguments in the each (works for map also) method (one for the key, the other for the value):
#array.each do |name, array|
product = Product.new
product.sku = name # returns "C1"
array.each do |data|
data[:upc]
data[:name]
#etc...
end
end
The fundamental problem is that the sample array you showed above is not actually an array. It's a hash with key-value pairs. Therefore, your code like p[0] or p[1][0] doesn't make sense because a hash doesn't have index like array. Hash is not ordered. Hashes values are accessed with a "key" rather than an "index" like array.
Iterating through key-value pairs of a hash is done something like this.
1.9.3p194 :001 > x = {:x => 10, :y => 9, :z => 10}
=> {:x=>10, :y=>9, :z=>10}
1.9.3p194 :002 > x.each do |key, value|
1.9.3p194 :003 > puts "#{key} : #{value}"
1.9.3p194 :004?> end
x : 10
y : 9
z : 10
=> {:x=>10, :y=>9, :z=>10}
It looks like you may be confusing Arrays and Hashes a bit.
Given this:
#array = {"C1"=>[
{:upc=>"51857195821952", :product_id=>"1234", :name=>"name", :price=>" $15 ", :color=>"green", :size=>"L", :description=>"descr"},
{:upc=>"352353wegs", :product_id=>"456", :name=>"name2", :price=>" $21 ", :color=>"black", :size=>"S", :description=>"descr"}
] }
Then #array.class.name is Hash
You can get the actual array by accessing it like so:
#actual_array = #array["C1"]
Then, #actual_array.class.name will be Array
So, taking this approach and re-writing:
#array = {"C1"=>[
{:upc=>"51857195821952", :product_id=>"1234", :name=>"name", :price=>" $15 ", :color=>"green", :size=>"L", :description=>"descr"},
{:upc=>"352353wegs", :product_id=>"456", :name=>"name2", :price=>" $21 ", :color=>"black", :size=>"S", :description=>"descr"}
] }
#actual_array = #array["C1"]
#actual_array.each do |p|
puts p[:name]
end
If you do this, you'll find that the value of the :name element will be printed neatly out.
Lets say I have an Array of content_categories (content_categories = user.content_categories)
I now want to add every element belonging to a certain categorie to content_categories with the category as a key and the the content-item IDs as elements of a set
In PHP something like this is possible:
foreach ($content_categories as $key => $category) {
$contentsByCategoryIDArray = Category.getContents($category[id])
$content_categories[$key][$contentsByCategoryIDArray]
}
Is there an easy way in rails to do this?
Greets,
Nico
Your question isn't really a Rails question, it's a general Ruby programming question.
Your description isn't very clear, but from what I understand, you want to group IDs for common categories using a Hash. There are various other ways of doing this, but this is easy to understand::
ary = [
'cat1', {:id => 1},
'cat2', {:id => 2},
'cat1', {:id => 3}
]
hsh = {}
ary.each_slice(2) { |a|
key,category = a
hsh[key] ? hsh[key] << category[:id] : hsh[key] = [category[:id]]
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
I'm using a simple Array with a category, followed by a simple hash representing some object instance, because it makes it easy to visualize. If you have a more complex object, replace the hash entries with those objects, and tweak how you access the ID in the ternary (?:) line.
Using Enumerable.inject():
hsh = ary.each_slice(2).inject({}) { |h,a|
key,category = a
h[key] ? h[key] << category[:id] : h[key] = [category[:id]]
h
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
Enumerable.group_by() could probably shrink it even more, but my brain is fading.
I'd use Enumerable#inject
content_categories = content_categories_array.inject({}){ |memo, category| memo[category] = Category.get_contents(category); memo }
Hash[content_categories.map{|cat|
[cat, Category.get_contents(cat)]
}]
Not really the right answer, because you want IDs in your array, but I post it anyway, because it's nice and short, and you might actually get away with it:
content_categories.group_by(&:category)
content_categories.each do |k,v|
content_categories[k] = Category.getContents(v)
end
I suppose it's works
If i understand correctly, content_categories is an array of categories, which needs to be turned into a hash of categories, and their elements.
content_categories_array = content_categories
content_categories_hash = {}
content_categories_array.each do |category|
content_categories_hash[category] = Category.get_contents(category)
end
content_categories = content_categories_hash
That is the long version, which you can also write like
content_categories = {}.tap do |hash|
content_categories.each { |category| hash[category] = Category.get_contents(category) }
end
For this solution, content_categories must be a hash, not an array as you describe. Otherwise not sure where you're getting the key.
contents_by_categories = Hash[*content_categories.map{|k, v| [k, Category.getContents(v.id)]}]
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