简体   繁体   中英

How to sort Ruby Array of Hashes based on multiple keys and multiple directions?

I'm trying to sort an array of hashs based on multiple keys and multiple directions (ASC and DESC).

Assume the array looks like:

items = [ {field1: '1', field2: 5, field3: 5}, 
          {field1: '1', field2: 1, field3: 3}, 
          {field1: '3', field2: 3, field3: 2},
          {field1: '3', field2: 1, field3: 8}, 
          {field1: '7', field2: 5, field3: 6}, 
          {field1: '7', field2: 5, field3: 1} ]

I want to create multiple_sort(items,options) method which works like:

multiple_sort(items, [{field: 'feed1', dir: 'asc'}, {field: 'feed3', dir: 'asc'}])

Will generate:

        [ {field1: '1', field2: 1, field3: 3},
          {field1: '1', field2: 5, field3: 5}, 
          {field1: '3', field2: 3, field3: 2},
          {field1: '3', field2: 1, field3: 8}, 
          {field1: '7', field2: 5, field3: 1}, 
          {field1: '7', field2: 5, field3: 6} ]

And

multiple_sort(items, [{field: 'feed1', dir: 'asc'}, {field: 'feed3', dir: 'desc'}])

Will output:

[ {field1: '1', field2: 5, field3: 5},
      {field1: '1', field2: 1, field3: 3}
      {field1: '3', field2: 1, field3: 8},
      {field1: '3', field2: 3, field3: 2}, 
      {field1: '7', field2: 5, field3: 6}, 
      {field1: '7', field2: 5, field3: 1} ]

Any help would be appreciated.

I took the liberty of interpreting feed1 as field1 , and changing the strings to symbols for efficiency.

def multiple_sort(array, criteria)
  array.sort do |a, b|
    cmp = 0
    criteria.each do |criterion|
      cmp = a[criterion[:field]] <=> b[criterion[:field]]
      cmp = -cmp if criterion[:dir] == :desc
      break if cmp != 0
    end
    cmp
  end
end


require 'pp'

pp multiple_sort(items, [
  {field: :field1, dir: :asc},
  {field: :field3, dir: :asc}
])
# [{:field1=>"1", :field2=>1, :field3=>3},
#  {:field1=>"1", :field2=>5, :field3=>5},
#  {:field1=>"3", :field2=>3, :field3=>2},
#  {:field1=>"3", :field2=>1, :field3=>8},
#  {:field1=>"7", :field2=>5, :field3=>1},
#  {:field1=>"7", :field2=>5, :field3=>6}]

pp multiple_sort(items, [
  {field: :field1, dir: :asc},
  {field: :field3, dir: :desc}
])
# [{:field1=>"1", :field2=>5, :field3=>5},
#  {:field1=>"1", :field2=>1, :field3=>3},
#  {:field1=>"3", :field2=>1, :field3=>8},
#  {:field1=>"3", :field2=>3, :field3=>2},
#  {:field1=>"7", :field2=>5, :field3=>6},
#  {:field1=>"7", :field2=>5, :field3=>1}]

You could use Enumerable#sort_by :

Code

def multiple_sort(items, options)
  items.sort_by { |h| options.map { |g|
    ((g[:dir]=='asc') ? 1 : -1) * h[g[:field].to_sym].to_i } }
end

Examples

items = [ {field1: '1', field2: 5, field3: 5}, 
          {field1: '1', field2: 1, field3: 3}, 
          {field1: '3', field2: 3, field3: 2},
          {field1: '3', field2: 1, field3: 8}, 
          {field1: '7', field2: 5, field3: 6}, 
          {field1: '7', field2: 5, field3: 1} ]

options = [{field: 'field1', dir: 'asc'}, {field: 'field3', dir: 'asc'}]
multiple_sort(items, options)
     #=> [{:field1=>"1", :field2=>1, :field3=>3},
     #    {:field1=>"1", :field2=>5, :field3=>5},
     #    {:field1=>"3", :field2=>3, :field3=>2},
     #    {:field1=>"3", :field2=>1, :field3=>8},
     #    {:field1=>"7", :field2=>5, :field3=>1},
     #    {:field1=>"7", :field2=>5, :field3=>6}] 

options = [{field: 'field1', dir: 'desc'}, {field: 'field3', dir: 'desc'}]
multiple_sort(items, options)
  # => [{:field1=>"7", :field2=>5, :field3=>6},
  #     {:field1=>"7", :field2=>5, :field3=>1},
  #     {:field1=>"3", :field2=>1, :field3=>8},
  #     {:field1=>"3", :field2=>3, :field3=>2},
  #     {:field1=>"1", :field2=>5, :field3=>5},
  #     {:field1=>"1", :field2=>1, :field3=>3}] 

items << {:field1=>"1", :field2=>6, :field3=>5}
  #=> [{:field1=>"1", :field2=>5, :field3=>5},
  #    {:field1=>"1", :field2=>1, :field3=>3},
  #    {:field1=>"3", :field2=>3, :field3=>2},
  #    {:field1=>"3", :field2=>1, :field3=>8},
  #    {:field1=>"7", :field2=>5, :field3=>6},
  #    {:field1=>"7", :field2=>5, :field3=>1},
  #    {:field1=>"1", :field2=>6, :field3=>5}] 

options = [{field: 'field1', dir: 'asc'}, {field: 'field3', dir: 'desc'},
           {field: 'field2', dir: 'desc'}]
multiple_sort(items, options)
  #=> [{:field1=>"1", :field2=>6, :field3=>5},
  #    {:field1=>"1", :field2=>5, :field3=>5},
  #    {:field1=>"1", :field2=>1, :field3=>3},
  #    {:field1=>"3", :field2=>1, :field3=>8},
  #    {:field1=>"3", :field2=>3, :field3=>2},
  #    {:field1=>"7", :field2=>5, :field3=>6},
  #    {:field1=>"7", :field2=>5, :field3=>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