1

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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Sacha
  • 1,987
  • 1
  • 24
  • 41
  • 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 Answers6

3

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"}]
klaut
  • 210
  • 3
  • 10
  • 3
    or `{ |o| o[a] || o[b] }`. – Cary Swoveland Jan 01 '15 at 15:07
  • 1
    The 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
2

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.)

Community
  • 1
  • 1
ndbroadbent
  • 13,513
  • 3
  • 56
  • 85
1

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.

Yevgeniy Anfilofyev
  • 4,827
  • 25
  • 27
0

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
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
0

Assuming you want to sort based on field/parameter that may or may not be present, I am assuming:

  1. When the parameter is present sort by it.
  2. 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]
Jikku Jose
  • 18,306
  • 11
  • 41
  • 61
0

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>]
DGM
  • 26,629
  • 7
  • 58
  • 79
  • I wonder if that actually works... – Kenny Meyer Jan 01 '15 at 20:18
  • 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