In Ruby, what would be the best way to sort an array of objects by an order
property that may or may not exist, and if it doesn't, then fall back to sorting based on a property named title
?
-
Jeff, I found your question interesting, but in need of clarification. A simple example, including the desired result, would be a big help. If you do an edit to clarify, there is a possibility the hold will be retracted. – Cary Swoveland Jan 02 '15 at 00:17
-
probably a duplicate of http://stackoverflow.com/questions/8888030/sorting-sort-array-based-on-multiple-conditions-in-ruby?rq=1 – DGM Jan 02 '15 at 16:03
-
Yes, it does seem like a pretty similar question. I actually asked it again because the other solutions I tried didn't work, but it turned out that was because of the data structure, not the solutions. Sorry! – Sacha Jan 05 '15 at 07:34
6 Answers
Not sure if this is what you are after, but a quick solution could be:
arr = [{a:"never", b:"anna"}, {a:"always", b:"bob"}, {b:"colin"}, {b:"abe"}]
arr.sort_by! {|o| o[:a] ? o[:a] : o[:b] }
#=> [{:b=>"abe"}, {:a=>"always", :b=>"bob"}, {:b=>"colin"}, {:a=>"never", :b=>"anna"}]

- 210
- 3
- 10
-
3
-
1The problem with this answer is that you will sometimes be comparing one record's `:a` with another record's `:b`. @Sacha - In your case, you would have an integer (order) being compared with a string (title), so I think you would see this error: `ArgumentError: comparison of String with 1 failed` – ndbroadbent Jan 02 '15 at 11:29
-
@nathan.f77 yes, this is what I understood from the question. That, indeed, when :a is not present sort by :b. So you would be sorting object1's :a with object2's :b, say. Of course, I also assumed that both :a and :b are of the same type. – klaut Jan 05 '15 at 16:06
Here's how to perform a sort with a fallback in Ruby:
Item = Struct.new(:order, :title)
items = [
Item.new(nil, "d"),
Item.new(nil, "b"),
Item.new(1, "a"),
Item.new(3, "c"),
Item.new(2, "e")
]
sorted_items = items.sort do |a, b|
if a.order && b.order
a.order <=> b.order
elsif a.order || b.order
# This prioritizes all items with an order
a.order ? -1 : 1
else
a.title.to_s <=> b.title.to_s
end
end
require 'awesome_print'
ap sorted_items
# [
# [0] {
# :order => 1,
# :title => "a"
# },
# [1] {
# :order => 2,
# :title => "e"
# },
# [2] {
# :order => 3,
# :title => "c"
# },
# [3] {
# :order => nil,
# :title => "b"
# },
# [4] {
# :order => nil,
# :title => "d"
# }
# ]
Let me also say that if you are fetching records from a database, then it would be better to do the sorting in your SQL query. If Item
was an ActiveRecord model, you could do something like:
Item.order('order ASC NULLS LAST, title ASC')
(NULLS LAST can be used in Postgres, check out this answer for MySQL.)

- 1
- 1

- 13,513
- 3
- 56
- 85
If I understand you right here is how to do this:
arr1 = [{order: 1, title: 2},{title: 4},{order: 2, title: 1}]
arr2 = [{order: 1, title: 2},{order: 7, title: 4},{order: 2, title: 1}]
def sort_it prop1, prop2, ar
ar.map{|el| el[prop1]}.include?(nil) ?
ar.sort_by{|el| el[prop2]}
:
ar.sort_by{|el| el[prop1]}
end
p sort_it(:order, :title, arr1)
p sort_it(:order, :title, arr2)
Which gives:
#=> [{:order=>2, :title=>1}, {:order=>1, :title=>2}, {:title=>4}]
#=> [{:order=>1, :title=>2}, {:order=>2, :title=>1}, {:order=>7, :title=>4}]
So, the algorythm is simple: select all objects' properties (:order
in our case) and if output temporary array contains at least one nil
then sort by second given property, otherwise -- by first.

- 4,827
- 25
- 27
You could try
def sort_array(array)
sort_by_property_name = sort_by_property_present?(array, :order, :title)
array.sort_by { |ob| ob.public_send(sort_by_property_name) }
end
def sort_by_property_present?(array, primary_name, fallback_name)
array.all? { |ob| ob.respond_to?(name) } || return fallback_name
primary_name
end

- 116,827
- 30
- 260
- 317
-
I'm not good still in proper method naming as per their roles.. Please suggest a good name. I'm still learning. – Arup Rakshit Jan 01 '15 at 11:33
Assuming you want to sort based on field/parameter that may or may not be present, I am assuming:
- When the parameter is present sort by it.
- When unavailable fall back to the next parameter.
Please review the following code that can sort an object array based on a number of fields, the system keeps falling back to the next field if the field is unavailable. Its developed with the assumption that the last field will definitely be present.
class CondtionalSort
def run(array: a, keys_to_order_by: keys)
array.sort do |e1, e2|
keys_to_order_by.each do |key|
break e1[key] <=> e2[key] if (e1.key?(key) && e2.key?(key))
end
end
end
end
ArrayTest = [{order: 1, title: 2},{title: 4},{order: 2, title: 1}]
ArrayTest_SORTED = [{:order=>1, :title=>2}, {:order=>2, :title=>1}, {:title=>4}]
sorter = CondtionalSort.new
sorter.run array: ArrayTest, keys_to_order_by: [:order, :title]

- 18,306
- 11
- 41
- 61
I just use an array as the sort_by:
# sample data:
Item = Struct.new(:property1, :property2, :property3)
collection = [Item.new("thing1", 3, 6), Item.new("thing1", 3, 1), Item.new("aaa", 1,1) ]
# sort
collection.sort_by{|item| [item.property1, item.property2, item.property3] }
# => [#<struct Item property1="aaa", property2=1, property3=1>,
#<struct Item property1="thing1", property2=3, property3=1>,
#<struct Item property1="thing1", property2=3, property3=6>]

- 26,629
- 7
- 58
- 79
-
-
I don't think this works. I get `ArgumentError: comparison of Array with Array failed` when I try this, both in Ruby IRb, and in the Rails console. – ndbroadbent Jan 02 '15 at 11:33
-
You have to actually line up your test conditions so it has comparable properties in the array, but I use it all the time. I'll add some sample test code. – DGM Jan 02 '15 at 16:00