Sort a list with multiple conditions Ruby on Rails
Use sort_by and supply an array of values. It will compare them in order from left to right. Default sort is ascending, so if you want the opposite (e.g. matches_won should sort descendingly so that most matches one comes first, as opposed to matches_lost, which should sort lowest to highest), you need to negate the value.
Here's an example
require 'pp'
Team = Struct.new :won, :lost, :demerits, :style
teams = Array.new(20) { Team.new rand(5), rand(5), rand(5), rand(5) }
puts "Before sort:"
pp teams
puts "", "After sort:"
pp teams
teams.sort_by! { |team| [-team.won, team.lost, team.demerits, -team.style] }
# >> Before sort:
# >> [#<struct Team won=1, lost=2, demerits=4, style=3>,
# >> #<struct Team won=0, lost=4, demerits=2, style=2>,
# >> #<struct Team won=4, lost=1, demerits=2, style=3>,
# >> #<struct Team won=1, lost=4, demerits=1, style=4>,
# >> #<struct Team won=3, lost=1, demerits=1, style=4>,
# >> #<struct Team won=3, lost=4, demerits=1, style=2>,
# >> #<struct Team won=4, lost=0, demerits=4, style=0>,
# >> #<struct Team won=3, lost=4, demerits=1, style=2>,
# >> #<struct Team won=1, lost=4, demerits=1, style=2>,
# >> #<struct Team won=3, lost=0, demerits=1, style=1>,
# >> #<struct Team won=3, lost=4, demerits=3, style=4>,
# >> #<struct Team won=1, lost=4, demerits=0, style=0>,
# >> #<struct Team won=3, lost=4, demerits=2, style=0>,
# >> #<struct Team won=3, lost=0, demerits=3, style=0>,
# >> #<struct Team won=1, lost=2, demerits=0, style=1>,
# >> #<struct Team won=3, lost=0, demerits=1, style=0>,
# >> #<struct Team won=0, lost=4, demerits=1, style=4>,
# >> #<struct Team won=1, lost=4, demerits=3, style=0>,
# >> #<struct Team won=3, lost=3, demerits=2, style=3>,
# >> #<struct Team won=0, lost=4, demerits=4, style=4>]
# >>
# >> After sort:
# >> [#<struct Team won=4, lost=0, demerits=4, style=0>,
# >> #<struct Team won=4, lost=1, demerits=2, style=3>,
# >> #<struct Team won=3, lost=0, demerits=1, style=1>,
# >> #<struct Team won=3, lost=0, demerits=1, style=0>,
# >> #<struct Team won=3, lost=0, demerits=3, style=0>,
# >> #<struct Team won=3, lost=1, demerits=1, style=4>,
# >> #<struct Team won=3, lost=3, demerits=2, style=3>,
# >> #<struct Team won=3, lost=4, demerits=1, style=2>,
# >> #<struct Team won=3, lost=4, demerits=1, style=2>,
# >> #<struct Team won=3, lost=4, demerits=2, style=0>,
# >> #<struct Team won=3, lost=4, demerits=3, style=4>,
# >> #<struct Team won=1, lost=2, demerits=0, style=1>,
# >> #<struct Team won=1, lost=2, demerits=4, style=3>,
# >> #<struct Team won=1, lost=4, demerits=0, style=0>,
# >> #<struct Team won=1, lost=4, demerits=1, style=4>,
# >> #<struct Team won=1, lost=4, demerits=1, style=2>,
# >> #<struct Team won=1, lost=4, demerits=3, style=0>,
# >> #<struct Team won=0, lost=4, demerits=1, style=4>,
# >> #<struct Team won=0, lost=4, demerits=2, style=2>,
# >> #<struct Team won=0, lost=4, demerits=4, style=4>]
How to sort array of strings with multiple conditions?
If these attributes are database columns, you can use:
Organization.order(attribute_a: :desc, attribute_b: :asc)
Otherwise, use sort
with an array:
Arrays are compared in an “element-wise” manner; the first two elements that are not equal will determine the return value for the whole comparison.
Exchanging the first elements sorts them in descending order:
array.sort { |x, y| [y.attribute_a, x.attribute_b] <=> [x.attribute_a, y.attribute_b] }
# ^ ^
# | |
# +-------- x and y exchanged -------+
To generate a list as mentioned in your comment, you can use group_by
:
<% sorted_array.group_by(&:attribute_a).each do |attr, group| %>
<%= attr %> #=> "Type z"
<% group.each do |item| %>
<%= item.attribute_b %> #=> "name a", "name b", ...
<% end %>
<% end %>
Sorting: Sort array based on multiple conditions in Ruby
You should always use sort_by
for a keyed sort. Not only is it much more readable, it is also much more efficient. In addition, I would also prefer to use destructuring bind, again, for readability:
array.sort_by {|name, age| [age, name] }
Sort an array and make it unique on multiple conditions - Ruby
you can use sort_by
method and uniq
and values_at
hashForAnimals.sort_by{ |a| a[:sortOrder] }.uniq{ |k| k.values_at(:animalCd, :animalType) }
# => [{:animalCd=>"Cow", :animalType=>"Carnivore", :sortOrder=>1}, {:animalCd=>"Rabbit", :animalType=>"Herbivore", :sortOrder=>2}, {:animalCd=>"Tiger", :animalType=>"Carnivore", :sortOrder=>3}, {:animalCd=>"Shark", :animalType=>"Carnivore", :sortOrder=>4}, {:animalCd=>"Cow", :animalType=>"Herbivore", :sortOrder=>5}, {:animalCd=>"Bear", :animalType=>"Omnivore", :sortOrder=>7}]
Sorting By Multiple Conditions in Ruby
You should always sort by the same criteria to insure a meaningful order. If comparing two nil
dates, it is fine that the position
will judge of the order, but if comparing one nil
date with a set date, you have to decide which goes first, irrespective of the position (for example by mapping nil
to a day way in the past).
Otherwise imagine the following:
a.date = nil ; a.position = 1
b.date = Time.now - 1.day ; b.position = 2
c.date = Time.now ; c.position = 0
By your original criteria, you would have: a < b < c < a. So, which one is the smallest??
You also want to do the sort at once. For your <=>
implementation, use #nonzero?
:
def <=>(other)
return nil unless other.is_a?(Post)
(self.category <=> other.category).nonzero? ||
((self.date || AGES_AGO) <=> (other.date || AGES_AGO)).nonzero? ||
(self.position <=> other.position).nonzero? ||
0
end
If you use your comparison criteria just once, or if that criteria is not universal and thus don't want to define <=>
, you could use sort
with a block:
post_ary.sort{|a, b| (a.category <=> ...).non_zero? || ... }
Better still, there is sort_by
and sort_by!
which you can use to build an array for what to compare in which priority:
post_ary.sort_by{|a| [a.category, a.date || AGES_AGO, a.position] }
Besides being shorter, using sort_by
has the advantage that you can only obtain a well ordered criteria.
Notes:
sort_by!
was introduced in Ruby 1.9.2. You canrequire 'backports/1.9.2/array/sort_by'
to use it with older Rubies.- I'm assuming that
Post
is not a subclass ofActiveRecord::Base
(in which case you'd want the sort to be done by the db server).
How can I sort by multiple conditions with different orders?
How about:
ordered_list = [[1, "b"], [1, "a"], [2, "a"]]
ordered_list.sort! do |a,b|
[a[0],b[1]] <=> [b[0], a[1]]
end
sorting and rearranging an array of hashes based on multiple conditions
Here is another option using what you already have as a base (Since you were basically all the way there)
a = [
{ "name" => "X", "year" => "2013-08"},
{ "name" => "A", "year" => "2017-01"},
{ "name" => "X", "year" => "2000-08"},
{ "name" => "B", "year" => "2018-05"},
{ "name" => "D", "year" => "2016-04"},
{ "name" => "C", "year" => "2016-04"}
]
a.sort do |a,b|
a_ord, b_ord = [a,b].map {|e| e["name"] == "X" ? 0 : 1 }
[a_ord,b["year"],a["name"] ] <=> [b_ord, a["year"],b["name"]]
end
Here we just make sure that "X" is always in front by assigning it a 0 and everything else a 1. Then since 0 and 0 would be equivalent X will fall back to the same logic you already have applied as will all the others. We can make this a bit fancier as:
a.sort do |a,b|
[a,b].map {|e| e["name"] == "X" ? 0 : 1 }.zip(
[b["year"],a["year"]],[a["name"],b["name"]]
).reduce(:<=>)
end
How to sort a ruby array by two conditions
This should do it:
require 'ostruct'
arr = [
OpenStruct.new(percent: 73, type: 1),
OpenStruct.new(percent: 70, type: 4),
OpenStruct.new(percent: 60, type: 4),
OpenStruct.new(percent: 50, type: 4),
OpenStruct.new(percent: 64, type: 1),
OpenStruct.new(percent: 74, type: 2)
]
puts arr.sort_by { |a| [a.type % 4, -a.percent] }
output:
#<OpenStruct percent=70, type=4>
#<OpenStruct percent=60, type=4>
#<OpenStruct percent=50, type=4>
#<OpenStruct percent=73, type=1>
#<OpenStruct percent=64, type=1>
#<OpenStruct percent=74, type=2>
Ruby Sort an array of arrays of numbers based on multiple conditions
My understanding is that when a[2] >= 0
, sorting is to on the array [a[1], a[2]]
, and elements for which a[2] < 0
are to be at the end of the sorted array and sorted by [-a[1], -a[2]]
.
biggest_plus_1 = to_sort.map { |a| a[2] }.max + 1
#=> 3
to_sort.sort_by { |a| a[2] >= 0 ? [0, a[1], a[2]] : [biggest_plus_1, -a[1], -a[2]] }
#=> [[6, 27, 1, 11.0], [7, 27, 1, 12.0], [8, 27, 1, 13.0], [9, 27, 2, 14.0],
# [5, 27, -2, 5.0], [2, 27, -2, 2.0], [3, 27, -2, 3.0], [4, 27, -2, 4.0],
# [1, 27, -3, 1.0]]
Array#sort and Enumerable#sort_by rely on the method Array#<=> for determining the ordering of each pair of arrays being sorted. Two arrays, a
and b
are ordered lexicographically, meaning the following. If a[0] < b[0]
then a
is less than b
(a < b
), or equivalently, a <=> b #=> -1
. Similarly, if a[0] > b[0]
then a
is greater than b
(a > b
) and a <=> b #=> 1
. If a[0] == b[0]
, the tie is broken by the comparing the second elements in the same way, and so on. If a
is smaller than b
(a.size < b.size
), and the first a.size
elements of each array are equal, a < b
. a
and b
are equal if and only if a <=> b #=> 0
.
Since elements a
for which a[2] < 0
are to be placed at the end of the sorted array, we need to sort by arrays whose first elements place the array at the front or back of the sorted array. It is for that reason that I made the first element of the sort-by array zero when a[2] >= 0
and biggest_plus_1
when a[2] < 0
, where biggest_plus_1
is the largest value of a[2]
plus 1.
The remaining elements of the sort-by arrays determine how each of the two groups of arrays are to be sorted.
Note that biggest_plus_1
will be non-positive if all a[2] < 0
, but that doesn't matter, as no element will be sorted by an array whose first element is zero.
Sort array of active records by multiple columns
A good way is to use sort
and <=>
and nonzero?
like this:
jobs.sort{|a,b|
(a.organization <=> b.organization).nonzero? ||
(b.created_at <=> a.created_at)
}
This code says:
- Compare A and B by organization.
- If they differ, then we have our answer.
- If they are the same, then we need to do more.
- Compare B and A by time. (Note B & A are in reverse order)
- If they differ, then we have our answer.
- If they are the same, then the sort order doesn't matter. (Ruby sort is "unstable")
Example code independent of ActiveRecord:
require 'time'
require 'ostruct'
jobs = [
OpenStruct.new(_id: 1, created_at: Time.parse("2014-07-15 19:18:40 UTC"), organization: "Acme Inc"),
OpenStruct.new(_id: 3, created_at: Time.parse("2014-05-20 09:27:38 UTC"), organization: "Baxter"),
OpenStruct.new(_id: 2, created_at: Time.parse("2014-11-25 12:21:00 UTC"), organization: "Wizard"),
OpenStruct.new(_id: 3, created_at: Time.parse("2015-01-15 07:20:10 UTC"), organization: "Baxter")
]
Related Topics
How to Install and Use Slim Template Engine with Middleman
Error Nomethoderror: Undefined Method 'Debug_Rjs=' for Actionview::Base:Class
Rails Routing: Giving Default Values for Path Helpers
Ruby on Rails - Rack-Cors Multiple Origins with Different Resources
Using %I and %I Symbol Array Literal
Getting All Links of a Webpage Using Ruby
Where Is the Best Place to Add Methods to the Integer Class in Rails
Ror, Can't Iterate from Datetime/Timewithzone
Error When Installing Ruby 2.1.3 with Rvm
Ruby/Rails - How to Create a Class and Access It from the Controller
How to Store a Ruby Array into a File
Best Way to Remove File Extension