Ruby sort by multiple fields and multilple directions for different data types

I need to write a multi_sort method to sort an array of hash which
accepts a hash as an argument, eg: { a_sort: 1, display_sort: 1 }. This
hash is sort fields and sort direction. (1 means ascending, -1 means
descending).

items = [ {name: ‘Album 1’, a_sort: 5, display_sort: 3},
{name: ‘Album 2’, a_sort: 1, display_sort: 5},
{name: ‘Album 3’, a_sort: 3, display_sort: 2},
{name: ‘Album 5’, a_sort: 1, display_sort: 8},
{name: ‘Album 7’, a_sort: 5, display_sort: 1},
{name: ‘Album 7’, a_sort: 5, display_sort: 6} ]

multi_sort(items, {a_sort: 1, display_sort: 1})

I can’t figure it out after 3 hours. The expected output is the array
that is sorted correctly.

  [ {name: 'Album 2', a_sort: 1, display_sort: 5},
    {name: 'Album 5', a_sort: 1, display_sort: 8},
    {name: 'Album 3', a_sort: 3, display_sort: 2},
    {name: 'Album 7', a_sort: 5, display_sort: 1}
    {name: 'Album 1', a_sort: 5, display_sort: 3},
    {name: 'Album 7', a_sort: 5, display_sort: 6} ]

The way I would approach it is this: write two classes CompareForward
and CompareBackward. Each instance holds a key name and has a method
#compare which receives two arguments (items to compare). That method
extracts values by key name and does the comparison either way. Then in
your method #multi_sort put multiple instances of the two classes into
an Array and invoke #sort on the original collection where in each
comparison operation you evaluate comparators until you find one that
has a non null result.

PS: Defining the sort ordering via a Hash is somewhat fragile IMHO
because it relies on Ruby’s Hash to maintain insertion order - something
that is not necessarily given for all potential hash implementations.

Hello,

Here a simple version, (it suppose that each column have value
less than 10000…)

require ‘pp’
items = [ {name: ‘Album 1’, a_sort: 5, display_sort: 3},
{name: ‘Album 2’, a_sort: 1, display_sort: 5},
{name: ‘Album 3’, a_sort: 3, display_sort: 2},
{name: ‘Album 4’, a_sort: 1, display_sort: 8},
{name: ‘Album 5’, a_sort: 5, display_sort: 1},
{name: ‘Album 6’, a_sort: 5, display_sort: 6}
]

def ms(data,opt)
p opt
data.sort_by {|record|
opt.map {|k,v| v*record[k]}.each_with_index.map {|crit,ord|
(10000**(opt.size-ord))*crit
}.inject(0) {|a,s| a+s}
}
end

pp ms(items, {a_sort: 1, display_sort: 1})
pp ms(items, {a_sort: -1, display_sort: 1})
pp ms(items, {a_sort: 1, display_sort: -1})

s.rb
{:a_sort=>1, :display_sort=>1}
[{:name=>“Album 2”, :a_sort=>1, :display_sort=>5},
{:name=>“Album 4”, :a_sort=>1, :display_sort=>8},
{:name=>“Album 3”, :a_sort=>3, :display_sort=>2},
{:name=>“Album 5”, :a_sort=>5, :display_sort=>1},
{:name=>“Album 1”, :a_sort=>5, :display_sort=>3},
{:name=>“Album 6”, :a_sort=>5, :display_sort=>6}]
{:a_sort=>-1, :display_sort=>1}
[{:name=>“Album 5”, :a_sort=>5, :display_sort=>1},
{:name=>“Album 1”, :a_sort=>5, :display_sort=>3},
{:name=>“Album 6”, :a_sort=>5, :display_sort=>6},
{:name=>“Album 3”, :a_sort=>3, :display_sort=>2},
{:name=>“Album 2”, :a_sort=>1, :display_sort=>5},
{:name=>“Album 4”, :a_sort=>1, :display_sort=>8}]
{:a_sort=>1, :display_sort=>-1}
[{:name=>“Album 4”, :a_sort=>1, :display_sort=>8},
{:name=>“Album 2”, :a_sort=>1, :display_sort=>5},
{:name=>“Album 3”, :a_sort=>3, :display_sort=>2},
{:name=>“Album 6”, :a_sort=>5, :display_sort=>6},
{:name=>“Album 1”, :a_sort=>5, :display_sort=>3},
{:name=>“Album 5”, :a_sort=>5, :display_sort=>1}]

This version is better :

def ms(data,opt)
p opt
str=opt.map {|(k,v)|
a= “%s[:%s]” % [v==1? “a”:“b”,k]
b= “%s[:%s]” % [v==1? “b”:“a”,k]
“(%s != %s) ? %s<=>%s : " % [a,b,a,b]
}.join(” ")
str= “lambda {|a,b| #{str} 0}”
puts str
lambda = eval(str)
data.sort {|a,b| lambda.call(a,b) }
end

s2.rb
{:a_sort=>1, :display_sort=>1}
lambda {|a,b| (a[:a_sort] != b[:a_sort]) ? a[:a_sort]<=>b[:a_sort] :
(a[:display_sort] != b[:display_sort]) ?
a[:display_sort]<=>b[:display_sort] :
0}
[{:name=>“Album 2”, :a_sort=>1, :display_sort=>5},
{:name=>“Album 4”, :a_sort=>1, :display_sort=>8},
{:name=>“Album 3”, :a_sort=>3, :display_sort=>2},
{:name=>“Album 5”, :a_sort=>5, :display_sort=>1},
{:name=>“Album 1”, :a_sort=>5, :display_sort=>3},
{:name=>“Album 6”, :a_sort=>5, :display_sort=>6}]