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– Cary Swoveland2015-01-02 00:17:41 +00:00Commented Jan 2, 2015 at 0:17
-
probably a duplicate of stackoverflow.com/questions/8888030/…DGM– DGM2015-01-02 16:03:57 +00:00Commented Jan 2, 2015 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– Sacha2015-01-05 07:34:47 +00:00Commented Jan 5, 2015 at 7: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"}]
3 Comments
{ |o| o[a] || o[b] }.: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 failedHere'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.)
Comments
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.
Comments
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
1 Comment
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]
Comments
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>]
3 Comments
ArgumentError: comparison of Array with Array failed when I try this, both in Ruby IRb, and in the Rails console.