Reputation: 2533
I have an array which contains hashes. I'd like to sort it by the created_at
value. Here's an example of the array structure:
Note, I've written human readable dates, the values will be a timestamp.
[
{"group1"=>[
{:item1=>[{"name" => "Tim", "created_at"=>"4 weeks ago"}]},
{:item2=>[{"name" => "Jim", "created_at"=>"3 weeks ago"}]},
{:item3=>[{"name" => "Ted", "created_at"=>"2 weeks ago"}]},
]
},
{"group2"=>[
{:item1=>[{"name" => "Sally", "created_at"=>"1 month ago"}]},
{:item2=>[{"name" => "Willa", "created_at"=>"2 months ago"}]},
{:item3=>[{"name" => "Sammi", "created_at"=>"4 months ago"}]},
]
},
{"group3"=>[
{:item1=>[{"name" => "Jeff", "created_at"=>"1 month ago"}]},
{:item2=>[{"name" => "Lois", "created_at"=>"1 day ago"}]},
{:item3=>[{"name" => "Lisa", "created_at"=>"1 week ago"}]},
]
}
]
I'd like to arrange the above data so the output would be group3
first, as it contains an item
with a created_at
value of 1 day ago. Next would be group1
as it contains an item with a value of 2 weeks ago, group2 would be last as it the most recent date is a month ago.
How can I rearrange this data?
I was thinking I might have to do something like
array_of_nested_hashes.each do |a|
a.sort_by { |k, v| v[:created_at] }
end
to sort the data within each group by date, then sort each group by the date of it's first hash - as that would be the most recent hash in each group, giving me the fully sorted hash, which would look like this:
[
{"group3"=>[
{:item2=>[{"name" => "Lois", "created_at"=>"1 day ago"}]},
{:item3=>[{"name" => "Lisa", "created_at"=>"1 week ago"}]},
{:item1=>[{"name" => "Jeff", "created_at"=>"1 month ago"}]},
]
},
{"group1"=>[
{:item3=>[{"name" => "Ted", "created_at"=>"2 weeks ago"}]},
{:item2=>[{"name" => "Jim", "created_at"=>"3 weeks ago"}]},
{:item1=>[{"name" => "Tim", "created_at"=>"4 weeks ago"}]},
]
},
{"group2"=>[
{:item1=>[{"name" => "Sally", "created_at"=>"1 month ago"}]},
{:item2=>[{"name" => "Willa", "created_at"=>"2 months ago"}]},
{:item3=>[{"name" => "Sammi", "created_at"=>"4 months ago"}]},
]
},
]
Upvotes: 0
Views: 1603
Reputation: 2140
If you have this hash,
{:z => { :z => 1 , :a => 3 }, :a => { :z => 6, :a => 7} }
a = {:z => { :z => 1 , :a => 3 }, :a => { :z => 6, :a => 7} }
a.each_with_object({}) { |e, hash| hash[e[0].to_sym] = e[1].sort.to_h }.sort.to_h
will gives you..
{:a=>{:a=>7, :z=>6}, :z=>{:a=>3, :z=>1}}
Upvotes: 0
Reputation: 110675
Edit:
Here's an answer for the correct interpretation of the question:
arr = [
{"g1"=>[{i1: [{"ca"=>-28}]}, {i2: [{"ca"=>-21}]}, {i3: [{"ca"=>-14} ]}]},
{"g2"=>[{i1: [{"ca"=>-30}]}, {i2: [{"ca"=>-60}]}, {i3: [{"ca"=>-120}]}]},
{"g3"=>[{i1: [{"ca"=>-30}]}, {i2: [{"ca"=>-1}]}, {i3: [{"ca"=>-7} ]}]}
]
arr.sort_by { |h| h.first.last.map { |g| g["ca"] }.max }.reverse
#=> [{"g3"=>...}, {"g1"=>...}, {"g2"=>...}]
Most of the explanation below applies to this answer as well.
tidE
This is one way you could do it, letting arr
denote the array of hashes you wish to sort.
Code
PER_SIZE = { 'day'=>1, 'week'=>7, 'month'=>30 }
arr.sort_by do |g|
g.first.last.map do |h|
n, period = h.first.last.first["created_at"].scan(/(\d+) ([a-rt-z]+)/).first
n.to_i * PER_SIZE[period]
end.min
end
#=>[{"group3"=>[{:item2=>[{"name"=>"Lois", "created_at"=>"1 day ago"}]},
# {:item3=>[{"name"=>"Lisa", "created_at"=>"1 week ago"}]},
# {:item1=>[{"name"=>"Jeff", "created_at"=>"1 month ago"}]}]},
# {"group1"=>[{:item3=>[{"name"=>"Ted", "created_at"=>"2 weeks ago"}]},
# {:item2=>[{"name"=>"Jim", "created_at"=>"3 weeks ago"}]},
# {:item1=>[{"name"=>"Tim", "created_at"=>"4 weeks ago"}]}]},
# {"group2"=>[{:item1=>[{"name"=>"Sally", "created_at"=>"1 month ago"}]},
# {:item2=>[{"name"=>"Willa", "created_at"=>"2 months ago"}]},
# {:item3=>[{"name"=>"Sammi", "created_at"=>"4 months ago"}]}]}]
Explanation
The sorting can be done by converting each date string to numbers of days. We begin by assigning a variable to the enumerator arr.sort_by
. We can then use Enumerator#next to obtain each value of the enumerator, which we then pass to the block.
enum = arr.sort_by
#=> #<Enumerator:
# [{"group1"=>[{:item1=>
# [{"name"=>"Tim", "created_at"=>"4 weeks ago"}]},...
# :sort_by>
Now assign the first value of the enumerator to the block variable:
g = enum.next
#=> {"group1"=>[{:item1=>[{"name"=>"Tim", "created_at"=>"4 weeks ago"}]},
# {:item2=>[{"name"=>"Jim", "created_at"=>"3 weeks ago"}]},
# {:item3=>[{"name"=>"Ted", "created_at"=>"2 weeks ago"}]}]}
arr1 = g.first.last
#=> ["group1", [{:item1=>[{"name"=>"Tim", "created_at"=>"4 weeks ago"}]},
# {:item2=>[{"name"=>"Jim", "created_at"=>"3 weeks ago"}]},
# {:item3=>[{"name"=>"Ted", "created_at"=>"2 weeks ago"}]}]]
arr1
#=> [{:item1=>[{"name"=>"Tim", "created_at"=>"4 weeks ago"}]},
# {:item2=>[{"name"=>"Jim", "created_at"=>"3 weeks ago"}]},
# {:item3=>[{"name"=>"Ted", "created_at"=>"2 weeks ago"}]}]
map
passes the first element of arr
to the block, assigning it to the block variable:
h = {:item1=>[{"name"=>"Tim", "created_at"=>"4 weeks ago"}]}
arr2 = h.first.last
#=> [{"name"=>"Tim", "created_at"=>"4 weeks ago"}]
s = arr2.first["created_at"]
#=> "4 weeks ago"
arr3 = s.scan(/(\d+) ([a-rt-z]+)/)
#=> [["4", "week"]]
n, period = arr3.first
#=> ["4", "week"]
n #=> "4"
period #=> "week"
n.to_i * PER_SIZE[period]
#=> 4 * PER_SIZE['week']
#=> 4 * 7 => 28
Similarly, the second and third elements of arr1
are mapped to 21
and 14
(days), respectively. We then compute:
[28, 21, 14].min
#=> 14
which is the value sort_by
uses for arr[0]
. Similarly, the sort_by
values for arr[1]
are:
[30, 60, 120].min
#=> 30
and for arr[2]
are:
[30, 1, 7].min
#=> 1
Therefore, arr
is sorted to:
[arr[3], arr[1], arr[2]]
Upvotes: 2
Reputation: 42799
After knowing that they are actually timestamps ..
here's my answer
obj = {that huge array}
sorted_obj = obj.sort_by do |groups|
groups.values.map do |items|
items.map do |item|
item.values.flatten.first['created_at']
end.max
end
end
Upvotes: 0
Reputation: 59601
Here's my attempt. The worflow is:
1) Sort all inner arrays to get the biggest value (meaning most recent numeric timestamp) to the first index.
2) With the biggest value in a known position (index 0) in the inner array, sort the outer arrays according the value of the the first index in their inner array.
# Part 1
outer_list.map! do |h|
Hash[h.map do |k, v|
v = v.sort_by do |hsh|
hsh.first[1][0]['created_at'].to_i
end.reverse!
[k, v]
end]
end
# Part 2
sorted = outer_list.sort_by do |h|
h.first[1][0].first[1][0]['created_at'].to_i
end.reverse!
Upvotes: 3