I'm getting data from an API and need to format it differently. I have a car_array that consists of an array of hashes. However, sometimes there will be a sub-array as one of the hash values that contains more than 1 element. In this case, there should be a loop so that each element in the array gets mapped correctly as separate entries.
An example of data, note how prices and options_package are arrays with multiple elements.
[{
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}, {
dealer_id: 2,
dealer_name: "dealership 2",
car_make: "ford",
prices: ['50', '55'],
options_package: ['X', 'Y']
}, {
dealer_id: 3,
dealer_name: "dealership 3",
car_make: "dodge",
prices: ['70'],
options_package: ['A']
}]
I would like to create multiple entries when there are multiple array elements
for example, the data above should be broken out and mapped as:
some_array = [
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '30', options_package: 'A' },
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '32', options_package: 'B' },
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '35', options_package: 'C' },
{ dealer_id: 2, dealer_name: "dealership 2", car_make: "ford", price: '50', options_package: 'X' },
{ dealer_id: 2, dealer_name: "dealership 2", car_make: "ford", price: '55', options_package: 'Y' },
{ dealer_id: 3, dealer_name: "dealership 3", car_make: "dodge", price: '70', options_package: 'A' }
]
Here's what I've got so far:
car_arr.each do |car|
if car['Prices'].length > 1
# if there are multiple prices/options loop through each one and create a new car
car.each do |key, value|
if key == 'Prices'
value.each do |price|
formatted_car_array << {
dealer_id: car['dealer_id'],
dealer_name: car['dealer_name'],
car_make: car['make'],
options_package: ???????,
price: price,
}
end
end
end
else
# there's only element for price and options_package
formatted_car_array << {
dealer_id: car['dealer_id'],
dealer_name: car['dealer_name'],
car_make: car['make'],
options_package: car['options_package'],
price: car['prices']
}
end
end
Consider just one hash to start with, and how this problem can be solved for this simpler problem.
h = {
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}
Let's get combinations of prices and options packages using #zip.
h[:prices].zip(h[:options_package])
# => [["30", "A"], ["32", "B"], ["35", "C"]]
The length of 3 for this array corresponds with how many hashes we expect to get from it, so let's map over those, building a new hash each time.
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
# => [{:price=>"30", :options_package=>"A", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"32", :options_package=>"B", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"35", :options_package=>"C", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"}]
Now you just need to #flat_map this over your array.
car_arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
end
# => [{:price=>"30", :options_package=>"A", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"32", :options_package=>"B", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"35", :options_package=>"C", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"50", :options_package=>"X", :dealership=>nil, :dealer_id=>2, :car_make=>"ford"},
# {:price=>"55", :options_package=>"Y", :dealership=>nil, :dealer_id=>2, :car_make=>"ford"},
# {:price=>"70", :options_package=>"A", :dealership=>nil, :dealer_id=>3, :car_make=>"dodge"}]
If arr is the array of hashes given in the question one may write the following.
arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |p,o|
h.reject { |k,_| k == :prices }.merge(price: p, options_package: o)
end
end
#=> [{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"A", :price=>"30"},
# {:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"B", :price=>"32"},
# {:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"C", :price=>"35"},
# {:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford",
# :options_package=>"X", :price=>"50"},
# {:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford",
# :options_package=>"Y", :price=>"55"},
# {:dealer_id=>3, :dealer_name=>"dealership 3", :car_make=>"dodge",
# :options_package=>"A", :price=>"70"}]
Note that this code need not be changed if key-value pairs are added to the hash or if the keys :dealer_id and :dealer_name are deleted or renamed.
My answer is an extension upon the answer of Chris.
car_arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
end
This answer could be shortened if you're using the newest Ruby version.
Ruby 3.0 introduced Hash#except, which lets you easily create a copy of a hash without the specified key(s). This allows us to reduce the answer to:
cars.flat_map do |car|
car[:prices].zip(car[:options_package]).map do |price, pkg|
car.except(:prices).merge(price: price, options_package: pkg)
end
end
car.except(:prices) will create a new hash without the :prices key/value-pair, we don't need to remove :options_package, since merge will overwrite the the old :options_package value with a new value.
Ruby 3.1 introduced { x:, y: } as syntax sugar for { x: x, y: y }. This allows us to "reduce" the answer further to:
cars.flat_map do |car|
car[:prices].zip(car[:options_package]).map do |price, options_package|
car.except(:prices).merge(price:, options_package:)
end
end
Input
input = [{
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}, {
dealer_id: 2,
dealer_name: "dealership 2",
car_make: "ford",
prices: ['50', '55'],
options_package: ['X', 'Y']
}, {
dealer_id: 3,
dealer_name: "dealership 3",
car_make: "dodge",
prices: ['70'],
options_package: ['A']
}]
Code
result = input.map do |h|
prices = h[:prices]
options_package = h[:options_package]
h[:options_package].count.times.map do
h[:prices] = prices.shift
h[:options_package] = options_package.shift
h.dup
end
end
p result.flatten
Output
[{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"30", :options_package=>"A"},
{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"32", :options_package=>"B"},
{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"35", :options_package=>"C"},
{:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford", :prices=>"50", :options_package=>"X"},
{:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford", :prices=>"55", :options_package=>"Y"},
{:dealer_id=>3, :dealer_name=>"dealership 3", :car_make=>"dodge", :prices=>"70", :options_package=>"A"}]
Related
I have a hash with values that's an array. How do I delete repeated elements in the array and the corresponding ids in the most performant way?
Here's an example of my hash
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["Language", "Question", "Question", "Answer", "Answer"],
"option_ids" => ["12345", "23456", "34567", "45678", "56789"]
}
The idea I have is something like this
hash["options"].each_with_index { |value, index |
h = {}
if h.key?(value)
delete(value)
delete hash["option_ids"].delete_at(index)
else
h[value] = index
end
}
The result should be
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["Language", "Question", "Answer"],
"option_ids" => ["12345", "23456", "45678"]
}
I know I have to put into consideration that when I delete the values of the options and option_ids the indexes of those values are going to change. But not sure how to do this
The first idea I had is to zip the values and call uniq, then think a way to return back to the initial form:
h['options'].zip(h['option_ids']).uniq(&:first).transpose
#=> [["Language", "Question", "Answer"], ["12345", "23456", "45678"]]
Then, via parallel assignment:
h['options'], h['option_ids'] = h['options'].zip(h['option_ids']).uniq(&:first).transpose
h #=> {"id"=>"sjfdkjfd", "name"=>"Field Name", "type"=>"field", "options"=>["Language", "Question", "Answer"], "option_ids"=>["12345", "23456", "45678"]}
These are the steps:
h['options'].zip(h['option_ids'])
#=> [["Language", "12345"], ["Question", "23456"], ["Question", "34567"], ["Answer", "45678"], ["Answer", "56789"]]
h['options'].zip(h['option_ids']).uniq(&:first)
#=> [["Language", "12345"], ["Question", "23456"], ["Answer", "45678"]]
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["L", "Q", "Q", "Q", "A", "A", "Q"],
"option_ids" => ["12345", "23456", "34567", "dog", "45678", "56789", "cat"]
}
I assume that "repeated elements" refers to contiguous equal elements (2 only in [1,2,2,1]) as opposed to "duplicated elements" (both 1 and 2 in the previous example). I do show how the code would be altered (simplified, in fact) if the second interpretation applies.
idx = hash["options"].
each_with_index.
chunk_while { |(a,_),(b,_)| a==b }.
map { |(_,i),*| i }
#=> [0, 1, 4, 6]
hash.merge(
["options", "option_ids"].each_with_object({}) { |k,h| h[k] = hash[k].values_at(*idx) }
)
#=> {"id"=>"sjfdkjfd",
# "name"=>"Field Name",
# "type"=>"field",
# "options"=>["L", "Q", "A", "Q"],
# "option_ids"=>["12345", "23456", "45678", "cat"]}
If "repeated elements" is interpreted to mean that the values of "options" and "option_ids" are to only have the first three elements shown above, calculate idx as follows:
idx = hash["options"].
each_with_index.
uniq { |s,_| s }.
map(&:last)
#=> [0, 1, 4]
See Enumerable#chunk_while (Enumerable#slice_when could be used instead) and Array#values_at. The steps are as follows.
a = hash["options"]
#=> ["L", "Q", "Q", "Q", "A", "A", "Q"]
e0 = a.each_with_index
#=> #<Enumerator: ["L", "Q", "Q", "Q", "A", "A", "Q"]:each_with_index>
e1 = e0.chunk_while { |(a,_),(b,_)| a==b }
#=> #<Enumerator: #<Enumerator::Generator:0x000055e4bcf17740>:each>
We can see the values the enumerator e1 will generate and pass to map by converting it to an array:
e1.to_a
#=> [[["L", 0]],
# [["Q", 1], ["Q", 2], ["Q", 3]],
# [["A", 4], ["A", 5]], [["Q", 6]]]
Continuing,
idx = e1.map { |(_,i),*| i }
#=> [0, 1, 4, 6]
c = ["options", "option_ids"].
each_with_object({}) { |k,h| h[k] = hash[k].values_at(*idx) }
#=> {"options"=>["L", "Q", "A", "Q"],
# "option_ids"=>["12345", "23456", "45678", "cat"]}
hash.merge(c)
#=> {"id"=>"sjfdkjfd",
# "name"=>"Field Name",
# "type"=>"field",
# "options"=>["L", "Q", "A", "Q"],
# "option_ids"=>["12345", "23456", "45678", "cat"]}
Using Array#transpose
hash = {
"options" => ["Language", "Question", "Question", "Answer", "Answer"],
"option_ids" => ["12345", "23456", "34567", "45678", "56789"]
}
hash.values.transpose.uniq(&:first).transpose.map.with_index {|v,i| [hash.keys[i], v]}.to_h
#=> {"options"=>["Language", "Question", "Answer"], "option_ids"=>["12345", "23456", "45678"]}
After the OP edit:
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["Language", "Question", "Question", "Answer", "Answer"],
"option_ids" => ["12345", "23456", "34567", "45678", "56789"]
}
hash_array = hash.to_a.select {|v| v.last.is_a?(Array)}.transpose
hash.merge([hash_array.first].push(hash_array.last.transpose.uniq(&:first).transpose).transpose.to_h)
#=> {"id"=>"sjfdkjfd", "name"=>"Field Name", "type"=>"field", "options"=>["Language", "Question", "Answer"], "option_ids"=>["12345", "23456", "45678"]}
I have an array of hashes called events:
events = [
{:name => "Event 1", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "A"},
{:name => "Event 2", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "A"},
{:name => "Event 3", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "B"},
{:name => "Event 4", :date => "2019-02-21 08:00:00", :area => "South", :micro_area => "B"},
{:name => "Event 5", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "A"},
{:name => "Event 6", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "A"},
{:name => "Event 7", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "B"},
{:name => "Event 8", :date => "2019-02-21 08:00:00", :area => "North", :micro_area => "B"}
]
I want to know how to group_by first date, then area then micro_area to end up with a single array of hashes for example:
[
{
"2019-02-21 08:00:00": {
"South": {
"A": [
{:name=>"Event 1", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"A" },
{:name=>"Event 2", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"A" }
],
"B": [
{:name=>"Event 3", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"B" },
{:name=>"Event 4", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"B" }
]
},
"North": {
"A": [
{:name=>"Event 5", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"A" },
{:name=>"Event 6", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"A" }
],
"B": [
{:name=>"Event 7", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"B" },
{:name=>"Event 8", :date=>"2019-02-21 08:00:00", :area=>"North", :micro_area=>"B" }
]
}
}
}
]
Trying events.group_by { |r| [r[:date], r[:area], r[:micro_area]] } doesn't seem too work the way I want it to.
I think following will work for you,
events = [
{ name: 'Event 1', date: '2019-02-21 08:00:00', area: 'South', micro_area: 'A' }
]
events.group_by { |x| x[:date] }.transform_values do |v1|
v1.group_by { |y| y[:area] }.transform_values do |v2|
v2.group_by { |z| z[:micro_area] }
end
end
# {
# "2019-02-21 08:00:00"=>{
# "South"=>{
# "A"=>[
# {:name=>"Event 1", :date=>"2019-02-21 08:00:00", :area=>"South", :micro_area=>"A"}
# ]
# }
# }
# }
Another option is to build the nested structure as you traverse your hash:
events.each_with_object({}) do |event, result|
d, a, m = event.values_at(:date, :area, :micro_area)
result[d] ||= {}
result[d][a] ||= {}
result[d][a][m] ||= []
result[d][a][m] << event
end
Another option is grouping them like you did in the question. Then build the nested structure from the array used as key.
# build an endless nested structure
nested = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
# group by the different criteria and place them in the above nested structure
events.group_by { |event| event.values_at(:date, :area, :micro_area) }
.each { |(*path, last), events| nested.dig(*path)[last] = events }
# optional - reset all default procs
reset_default_proc = ->(hash) { hash.each_value(&reset_default_proc).default = nil if hash.is_a?(Hash) }
reset_default_proc.call(nested)
The above leaves the answer in the nested variable.
References:
Hash::new to create the nested hash.
Hash#default_proc to get the default proc of a hash.
Hash#default= to reset the hash default back to nil.
Hash#dig to traverse the nested structure until the last node.
Hash#[]= to set the last node equal to the grouped events.
Array decomposition and array to argument conversion to capture all but the last node into path and call #dig with the contents of path as arguments.
Here is a recursive solution that will handle arbitrary levels of nesting and arbitrary grouping objects.
def hashify(events, grouping_keys)
return events if grouping_keys.empty?
first_key, *remaining_keys = grouping_keys
events.group_by { |h| h[first_key] }.
transform_values { |a|
hashify(a.map { |h|
h.reject { |k,_| k == first_key } },
remaining_keys) }
end
Before executing this with the sample data from the questions let's add a hash with a different date to events.
events <<
{ :name=>"Event 8", :date=>"2018-12-31 08:00:00",
:area=>"North", :micro_area=>"B" }
grouping_keys = [:date, :area, :micro_area]
hashify(events, grouping_keys)
#=> {"2019-02-21 08:00:00"=>{
# "South"=>{
# "A"=>[{:name=>"Event 1"}, {:name=>"Event 2"}],
# "B"=>[{:name=>"Event 3"}, {:name=>"Event 4"}]
# },
# "North"=>{
# "A"=>[{:name=>"Event 5"}, {:name=>"Event 6"}],
# "B"=>[{:name=>"Event 7"}, {:name=>"Event 8"}]
# }
# },
# "2018-12-31 08:00:00"=>{
# "North"=>{
# "B"=>[{:name=>"Event 8"}]
# }
# }
# }
hashify(events, [:date, :area])
#=> {"2019-02-21 08:00:00"=>{
# "South"=>[
# {:name=>"Event 1", :micro_area=>"A"},
# {:name=>"Event 2", :micro_area=>"A"},
# {:name=>"Event 3", :micro_area=>"B"},
# {:name=>"Event 4", :micro_area=>"B"}
# ],
# "North"=>[
# {:name=>"Event 5", :micro_area=>"A"},
# {:name=>"Event 6", :micro_area=>"A"},
# {:name=>"Event 7", :micro_area=>"B"},
# {:name=>"Event 8", :micro_area=>"B"}
# ]
# },
# "2018-12-31 08:00:00"=>{
# "North"=>[
# {:name=>"Event 8", :micro_area=>"B"}
# ]
# }
# }
See Enumerable#group_by, Hash#transform_values and Hash#reject.
I need to group this YAML strutcture by array elements.
This is example of my YAML structure:
articles:
- title: 'Title 1'
url: 'https://example.com'
data: July 21, 2017
categories:
- 'category 1'
- 'category 2'
- title: 'Title 2'
url: 'https://example.com'
data: July 23, 2017
categories:
- 'category 2'
Result I need is this or similar:
['category 1' =>
[
{title: 'Title 1', url: 'https://example.com', data: July 21, 2017, categories: ['category 1', 'category 2']}
],
'category 2' => [
{title: 'Title 1', url: 'https://example.com', data: July 21, 2017, categories: ['category 1', 'category 2']},
{title: 'Title 2', url: 'https://example.com', data: July 23, 2017, categories: ['category 2']}
]
]
Can you help me? Thank you in advance
UPDATE:
I have tried this:
articles.group_by(&:categories).map{|v, a| [v, a] }
but key is not single category, but categories elements. I think I need a more loop but after some trials I haven't get the result.
Sorry for this, but I newbie for ruby
UPDATE 2:
Wrong result is:
[
[
[
"category 1",
"category 2"
],
[
{
"title": "Title 1",
"url": "https://example.com",
"data": "July 21, 2017",
"categories": [
"category 1",
"category 2"
]
}
]
],
[
[
"category 2"
],
[
{
"title": "Title 2",
"url": "https://example.com",
"data": "July 23, 2017",
"categories": [
"category 2"
]
}
]
]
]
It seems, in this case, the plain old good reducer would suit your needs better:
articles.each_with_object({}) do |article, acc|
acc.merge! article.categories.map { |c| [c, article] }.to_h
end
or even:
articles.map do |article|
article.categories.map { |c| [c, article] }.to_h
end.reduce(&:merge)
Do you need a group_by? Why not an easy nested loop?
result = {}
articels.each do |article|
article.categories.each do |cat|
result[cat] ||= []
result[cat] << article
end
end
I have this array of objects:
a = [#<Person id: 9, name: "Bob", bin_id: "114628">,
#<Person id: 10, name: "Sally", bin_id: "114626">,
#<Person id: 11, name: "Jessie", bin_id: "114627">,
#<Person id: 12, name: "Rapheal", bin_id: "114620">,
#<Company _id: 55295, name: "X", bin_id: "114619">,
#<Company _id: 55295, name: "Y", bin_id: "114629">,
#<Company _id: 55295, name: "Z", bin_id: "16074802">,
#<Company _id: 55295, name: "W", bin_id: "16074815">]
When I do
a.group_by { |objects| object.calculate_age }
I get this:
{
33 =>
[#<Person id: 9, name: "Bob", bin_id: "114628">,
#<Person id: 10, name: "Sally", bin_id: "114626">,
#<Person id: 11, name: "Jessie", bin_id: "114627">],
53 => [#<Company _id: 55295, name: "X", bin_id: "114619">],
45 => [#<Company _id: 55295, name: "Y", bin_id: "114629">,],
56 => [#<Company _id: 55295, name: "Z", bin_id: "16074802">],
60 => [#<Company _id: 55295, name: "W", bin_id: "16074815">]
}
But how I do get something that looks like this:
{
33 => [#<Person id: 9, name: "Bob", bin_id: "114628">],
33 => [#<Person id: 10, name: "Sally", bin_id: "114626">],
33 => [#<Person id: 11, name: "Jessie", bin_id: "114627">],
53 => [#<Company _id: 55295, name: "X", bin_id: "114619">],
45 => [#<Company _id: 55295, name: "Y", bin_id: "114629">,],
56 => [#<Company _id: 55295, name: "Z", bin_id: "16074802">],
60 => [#<Company _id: 55295, name: "W", bin_id: "16074815">]
}
Where each key is explicitely set to each value? Maybe group_by isn't the right method here. I want to return a hash like you see above.
For the purposes of your question, you could write your array:
arr = [obj0, obj1,...,obj7]
since the values of the each object's instance variables are irrelevant. You can't convert that to a hash with duplicate keys (age), but you could convert it to an array of hashes, each with a single key, age, if that would be helpful:
arr.map { |obj| { obj.calculate_age=>obj } }
To see how that would work, suppose we have:
class Friends
attr_reader :calculate_age
def initialize name, age
#name, #calculate_age = name, age
end
end
a = [["Amy", 21], ["Billy-Bob", 53], ["Wilber", 21], ["Trixi", 34], ["Bo", 53]]
arr = a.map { |name,age| Friends.new(name,age) }
#=> [#<Friends:0x007fc1f28b5518 #name="Amy", #calculate_age=21>,
# #<Friends:0x007fc1f28b54a0 #name="Billy-Bob", #calculate_age=53>,
# #<Friends:0x007fc1f28b5450 #name="Wilber", #calculate_age=21>,
# #<Friends:0x007fc1f28b5400 #name="Trixi", #calculate_age=34>,
# #<Friends:0x007fc1f28b5388 #name="Bo", #calculate_age=53>]
We can now convert this to an array of hashes:
a = arr.map { |obj| { obj.calculate_age=>obj } }
#=> [{21=>#<Friends:0x007fc1f28b5518 #name="Amy",...},
# {53=>#<Friends:0x007fc1f28b54a0 #name="Billy-Bob",...},
# {21=>#<Friends:0x007fc1f28b5450 #name="Wilber"...},
# {34=>#<Friends:0x007fc1f28b5400 #name="Trixi"...},
# {53=>#<Friends:0x007fc1f28b5388 #name="Bo",...}]
If you want those arrays sorted by age:
a.sort_by { |h| h.keys.first }
#=> [{21=>#<Friends:0x007fc1f28b5518 #name="Amy",...},
# {21=>#<Friends:0x007fc1f28b5450 #name="Wilber"...},
# {34=>#<Friends:0x007fc1f28b5400 #name="Trixi"...},
# {53=>#<Friends:0x007fc1f28b54a0 #name="Billy-Bob",...},
# {53=>#<Friends:0x007fc1f28b5388 #name="Bo",...}]
I have a collection (Products) and I use _.countBy like this :
var totalByBrand = _.countBy(result, "Brand");
and I have this result :
{
'Brand 1 ' : 5,
'Brand 2 ' : 45,
'Brand 3 ' : 2,
...
'Brand 99 ' : 25
}
I try to sort this result to obtain that :
{
'Brand 3 ' : 2,
'Brand 1 ' : 5,
'Brand 99 ' : 25,
...
'Brand 2 ' : 45
}
Is it possible with _.sortBy() ?
Properties order cannot be guaranteed in JavaScript Does JavaScript Guarantee Object Property Order? and that means you can't sort them.
You would have to use a different structure, maybe a list of objects like {brand: ..., count: ...} and sort on on count. For example
var totalByBrand = _.countBy(products, 'Brand');
var sorted = _.chain(totalByBrand).
map(function(cnt, brand) {
return {
brand: brand,
count: cnt
}
})
.sortBy('count')
.value();
And a demo
var products = [
{Brand: "Brand 1", id: 1},
{Brand: "Brand 1", id: 2},
{Brand: "Brand 2", id: 3},
{Brand: "Brand 3", id: 4},
{Brand: "Brand 3", id: 5},
{Brand: "Brand 1", id: 6},
];
var totalByBrand = _.countBy(products, 'Brand');
var sorted = _.chain(totalByBrand).
map(function(cnt, brand) {
return {
brand: brand,
count: cnt
}
}).sortBy('count')
.value();
console.dir(
sorted
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.0/underscore-min.js"></script>