Reputation: 258
I have an array that looks like this:
[
{"timestamp" => 1347119549, "category" => nil},
{"timestamp" => 1347119547, "category" => "Monkeys"},
{"timestamp" => 1347119543, "category" => nil},
{"timestamp" => 1347119542, "category" => "Monkeys"}
]
I want to sort it by timestamp (descending), UNLESS it has a category not being nil, in which case it should appear with it's "siblings", even though it is "older" than an uncategorized entry. I need to sort this array, so it appears like this:
[
{"timestamp" => 1347119549, "category" => nil},
{"timestamp" => 1347119547, "category" => "Monkeys"},
{"timestamp" => 1347119542, "category" => "Monkeys"},
{"timestamp" => 1347119543, "category" => nil}
]
I am trying to figure out how to get the correct result by using group_by
and sort
, but have had no success.
Upvotes: 2
Views: 121
Reputation: 126742
This is simply done using the tools you've tried.
First sort
the entire array by tiemstamp, and then allocate them groups by category using group_by
:
arr = [
{'timestamp' => 1347119549, 'category' => nil},
{'timestamp' => 1347119547, 'category' => 'Monkeys'},
{'timestamp' => 1347119543, 'category' => nil},
{'timestamp' => 1347119542, 'category' => 'Monkeys'},
{'timestamp' => 1347119541, 'category' => nil},
{'timestamp' => 1347119548, 'category' => nil},
{'timestamp' => 1347119545, 'category' => nil},
]
sorted = arr.sort_by { |elem| 0 - elem['timestamp'] }
groups = sorted.group_by { |elem| elem['category'] or Object.new }
sorted = groups.values.flatten
puts sorted
output
{"timestamp"=>1347119549, "category"=>nil}
{"timestamp"=>1347119548, "category"=>nil}
{"timestamp"=>1347119547, "category"=>"Monkeys"}
{"timestamp"=>1347119542, "category"=>"Monkeys"}
{"timestamp"=>1347119545, "category"=>nil}
{"timestamp"=>1347119543, "category"=>nil}
{"timestamp"=>1347119541, "category"=>nil}
You could, of course, pipeline the whole thing, at the cost of readability.
sorted = arr.sort_by { |elem| 0 - elem['timestamp'] }.group_by { |elem| elem['category'] or Object.new }.values.flatten
Upvotes: 1
Reputation: 13574
It looks a little ugly, but it works:
a = [
{"timestamp"=>1347119549, "category"=>nil},
{"timestamp"=>1347119547, "category"=>"Monkeys"},
{"timestamp"=>1347119543, "category"=>nil},
{"timestamp"=>1347119542, "category"=>"Monkeys"},
{"timestamp"=>1347119548, "category"=>"Dog"},
{"timestamp"=>1347119544, "category"=>"Dog"}
]
groups = a.sort_by {|h| -h['timestamp']}.group_by {|h| h['category']}
sorted = (groups.delete(nil) || []) + groups.values
sorted = sorted.sort_by{|i| i.is_a?(Hash) ? -i['timestamp'] : -i.first['timestamp']}.flatten
This gives you the following in sorted
:
[
{"timestamp"=>1347119549, "category"=>nil},
{"timestamp"=>1347119548, "category"=>"Dog"},
{"timestamp"=>1347119544, "category"=>"Dog"},
{"timestamp"=>1347119547, "category"=>"Monkeys"},
{"timestamp"=>1347119542, "category"=>"Monkeys"},
{"timestamp"=>1347119543, "category"=>nil}
]
I sort first by 'timestamp'
, so that the groups are sorted later.
After grouping by 'category'
, I move the values of the nil
category in an array. Here, I use (groups.delete(nil) || [])
in case the nil
group is empty.
Now it can be sorted by 'timestamp'
again, with the timestamp
of an array being the timestamp
of its first hash.
Finally flatten
gives us the desired array.
Upvotes: 1
Reputation: 27207
The trick required here is to assign a unique group instead of nil. You can do that simply by creating a generic Ruby Object
.
orig = [
{"timestamp"=>1347119549, "category"=>nil},
{"timestamp"=>1347119547, "category"=>"Monkeys"},
{"timestamp"=>1347119543, "category"=>nil},
{"timestamp"=>1347119542, "category"=>"Monkeys"}]
# The "tricky bit"
grouped = orig.group_by { |x| x["category"] ? x["category"] : Object.new }
# Sort the siblings within the groups (note negation causes reverse order)
grouped.values.each { |siblings| siblings.sort_by! { |a| -a["timestamp"] } }
# Sort the list by first (i.e. "best" sort order) timestamp in each group
sorted_groups = grouped.sort_by { |group_id,siblings| -siblings.first["timestamp"] }
# Remove group ids and flatten the list:
result = sorted_groups.map { |group_id,siblings| siblings }.flatten
=> [
{"timestamp"=>1347119549, "category"=>nil},
{"timestamp"=>1347119547, "category"=>"Monkeys"},
{"timestamp"=>1347119542, "category"=>"Monkeys"},
{"timestamp"=>1347119543, "category"=>nil}
]
Upvotes: 1
Reputation: 118289
require 'pp'
ar = [
{"timestamp" => 1347119549, "category" => nil},
{"timestamp" => 1347119547, "category" => "Monkeys"},
{"timestamp" => 1347119543, "category" => nil},
{"timestamp" => 1347119542, "category" => "Monkeys"}
]
pp ar.group_by{|h| h['category'] ? h['category'] : h['timestamp']}.
map{|k,v| v.sort_by{|h| -h['timestamp']}}.
sort_by{|a| -a[0]['timestamp']}.flatten
# >> [{"timestamp"=>1347119549, "category"=>nil},
# >> {"timestamp"=>1347119547, "category"=>"Monkeys"},
# >> {"timestamp"=>1347119542, "category"=>"Monkeys"},
# >> {"timestamp"=>1347119543, "category"=>nil}]
require 'pp'
a = [
{"timestamp"=>1347119549, "category"=>nil},
{"timestamp"=>1347119547, "category"=>"Monkeys"},
{"timestamp"=>1347119543, "category"=>nil},
{"timestamp"=>1347119542, "category"=>"Monkeys"},
{"timestamp"=>1347119548, "category"=>"Dog"},
{"timestamp"=>1347119544, "category"=>"Dog"}
]
pp a.group_by{|h| h['category'] ? h['category'] : h['timestamp']}.
map{|k,v| v.sort_by{|h| -h['timestamp']}}.
sort_by{|a| -a[0]['timestamp']}.flatten
# >> [{"timestamp"=>1347119549, "category"=>nil},
# >> {"timestamp"=>1347119548, "category"=>"Dog"},
# >> {"timestamp"=>1347119544, "category"=>"Dog"},
# >> {"timestamp"=>1347119547, "category"=>"Monkeys"},
# >> {"timestamp"=>1347119542, "category"=>"Monkeys"},
# >> {"timestamp"=>1347119543, "category"=>nil}]
Upvotes: 2