简体   繁体   中英

Sort array of hashes by a value

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"}]}, 
             ] 
  },
]

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!

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]]

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

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}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM