How to Create a Custom Sort Method in Ruby

How to create a custom sort method in Ruby

The best answer is provided by @AJcodez below:

points.sort_by{ |p| [p.x, p.y] }

The "correct" answer I originally provided, while it technically works, is not code I would recommend writing. I recall composing my response to fit the question's use of if/else rather than stopping to think or research whether Ruby had a more expressive, succinct way, which, of course, it does.


With a case statement:

ar.sort do |a, b|
case
when a.x < b.x
-1
when a.x > b.x
1
else
a.y <=> b.y
end
end

With ternary:

ar.sort { |a,b| a.x < b.x ? -1 : (a.x > b.x ? 1 : (a.y <=> b.y)) }

How to custom sort with ruby

You're close. [3,1,2] specifies an ordering, but it doesn't tell the block how to relate it to your objects. You want something like:

arr.sort_by {|obj| [3,1,2].index(obj.id) }

So the comparison will order your objects sequentially by the position of their id in the array.

Or, to use the more explicit sort (which you seem to have sort_by slightly confused with):

arr.sort do |a,b|
ordering = [3,1,2]
ordering.index(a.id) <=> ordering.index(b.id)
end

Sort array using custom sorting preferences?

This is a task for group_by and values_at:

ORDER = %w[Orange Yellow Blue]
ary = [['Red','Blue'],['Green','Orange'],['Purple','Yellow']]

ary.group_by{ |a| a.last }.values_at(*ORDER)
# => [[["Green", "Orange"]], [["Purple", "Yellow"]], [["Red", "Blue"]]]

Here's what group_by brings to the party:

ary.group_by{ |a| a.last }
# => {"Blue"=>[["Red", "Blue"]],
# "Orange"=>[["Green", "Orange"]],
# "Yellow"=>[["Purple", "Yellow"]]}

Once you have the hash of values used to group each array, then values_at makes it easy to extract them in the right order.

This is an extremely fast and efficient way to do the task as it will barely slow down as ary grows due to the fact that there is no real sorting going on, it's just grouping by a value, then extracting from the hash in a given order.

If you want the exact same array-of-arrays as in your example, flatten the result once:

ary.group_by{ |a| a.last }.values_at(*ORDER).flatten(1)
# => [["Green", "Orange"], ["Purple", "Yellow"], ["Red", "Blue"]]

You won't want to do that if there are going to be multiple "Orange", "Yellow" or "Blue" elements as the result won't be very usable.

How do I perform a complex custom sort in Ruby?

ar=[
{ type: 'A', price: '0.01' },
{ type: 'B', price: '4.23' },
{ type: 'D', price: '2.29' },
{ type: 'B', price: '3.38' },
{ type: 'C', price: '1.15' }
]

SORT_ORDER = 'BADC' #could also be an array, eg ["monday", "tuesday", ...]
p ar.sort_by{|e| [SORT_ORDER.index(e[:type]), e[:price].to_f]}

Output:

[{:type=>"B", :price=>"3.38"},
{:type=>"B", :price=>"4.23"},
{:type=>"A", :price=>"0.01"},
{:type=>"D", :price=>"2.29"},
{:type=>"C", :price=>"1.15"}]

Ruby - Implementing custom sort

Without the distance requirement, Ruby's TSort is exactly the tool for the job. However, I couldn't figure out how to add an extra requirement to the topological sort, so...

One idea is to start with a sorted array, then rearrange it by pushing each element just past all of its dependents. E.g. Starting with the sorted order

[3, 4, 5, 2, 1]

we leave 3 alone (no dependents), leave 4 alone (all its dependents are left of it), leave 5 alone (no dependents), push 2 after 1 (because 1 is its dependent and to its right), then leave 1 alone (all its dependents are left of it), and leave 2 alone (all its dependents are left of it).

This will result in an infinite loop if you have circular dependencies. It can be defended against, but I'll leave it as an exercise to the reader :) (E.g. you could keep a Set of all nodes pushed at curr, and clear it when curr is incremented; if you try to re-push the same one, raise an error)

array = hash # because your naming makes no sense :)
hash = array.each.with_object({}) { |o, h| h[o[:project_id]] = o }

order = array.map { |o| o[:project_id] }.sort_by { |id| -hash[id][:distance] }

order.each_index do |curr|
dependents = hash[order[curr]][:project_dependents_ids]
max_dep_index = dependents.map { |d| order.index(d) }.max
if max_dep_index&.> curr
order[curr .. max_dep_index - 1], order[max_dep_index] =
order[curr + 1 .. max_dep_index], order[curr]
redo
end
end

result = hash.values_at(*order)

EDIT: This is not terribly efficient, all those .index calls, and I have this feeling it should be possible to do it better...

EDIT2: Made the loop more Rubyish.

Ruby custom string sort

Since @hirolau's already taken swapcase, I offered an alternative (even though I prefer his answer). Alas, @Stefan identified a flaw, but suggested a nice fix:

str = '1654AaBcDddeeFF'

order_array = [*'0'..'9',*'a'..'z',*'A'..'Z']
str.each_char.sort_by { |c| order_array.index(c) }.join
#=> "1456acdeABDF"

(I am a mere scribe.)

One advantage of this approach is that you could use it for other orderings. For example, if:

str = '16?54!AaBcDdde,eFF'

and you also wanted to group the characters `'!?,' at the beginning, in that order, you could write:

order_array = [*'!?,'.chars,*'0'..'9',*'a'..'z',*'A'..'Z']
str.each_char.sort_by { |c| order_array.index(c) }.join
#=> "!?,1456acdeABDF"

We can make this a bit more efficient by converting order_array to a hash. For the last example:

order_hash = Hash[order_array.each_with_index.to_a]

then:

str.each_char.sort_by { |c| order_hash[c] }.join
# => "!?,1456acddeeABDFF"

Rails 4, how to ordering using custom method

I think the answer depends on what you want to see in the view because some of the problem could actually be solved in how you call @lists there. Also, some of the links you found make sorting by a model method sound more difficult than it is.

In your case, you can sort your conversations by a custom method like so:
Conversation.all.sort_by(&:custom_method)

Or specifically:
Conversation.all.sort_by(&:last_answered_to_i)

Specifically, you cannot use SQL to sort or order by something not in the actual database, so you use the Ruby sort_by method. For more info on the ampersand, see this post.

For your actual view, I'm not sure really how you want to organize it. I recently did something where I needed to group my resource by another resource called "categories", and then sort the original resource by "netvotes" which was a custom model method, then order by name. I did it by:

  • Ordering by name in the controller: @resources = Resource.order(:name)
  • Grouping by category in the outer loop of the view: <% @resources.group_by(&:category).each do |category, resources| %>
  • Then sorting the resources by votes in the partial for resources: <%= render resources.sort_by(&:netvotes).reverse %>

The view is a bit confusing, so here is the full view loop in index.html.erb:

<% @resources.group_by(&:category).each do |category, resources| %>
<div class="well">
<h3 class="brand-text"><%= category.name %></h3>
<%= render resources.sort_by(&:netvotes).reverse %>
</div>
<% end %>

And here is the _resource.html.erb partial:

<div class="row resource">
<div class="col-sm-2 text-center">
<div class="vote-box">
<%= link_to fa_icon('chevron-up lg'), upvote_resource_path(resource), method: :put %><br>
<%= resource.netvotes %><br>
<%= link_to fa_icon('chevron-down lg'), downvote_resource_path(resource), method: :put %>
</div>
</div>
<div class="col-sm-10">
<%= link_to resource.name, resource.link, target: "_blank" %>
<p><%= resource.notes %></p>
</div>
</div>

I hope that helps you think through some more ways to address your problem.

Custom sort array of hashes based on multiple key/value pairs

You can also use Enumerable#sort_by. The example builds an array which is compared element by element when sorting.

array_group.sort_by { |e| [e[:operator] == "_NOT_PRESENT" ? 1 : 0, 
e[:status] ? 0 : 1,
e[:name]] }

The example above orders records with operator: "_NOT_PRESENT" also by :status. The following snippet precisely performs the ordering from the question.

def priority(h)
case
when h[:operator] == "_NOT_PRESENT" then 3
when h[:status] == false then 2
# h[:status] == true
else 1
end
end

array_group.sort_by { |e| [priority(e), e[:name]] }

Creating a sort method using recursive method in Ruby (without using .sort)

The problem with what you have is that you have a couple of unassigned variables result and new_array.

Here's an example of a custom sort method that uses partition with a recursive method. It takes the first element of your array and splits your array into two parts using partition into a group containing elements lesser and greater than this first element using recursion.

def sort(arr)
return [] if arr.length == 0

f_element = arr.shift
less, greater = arr.partition {|x| x < f_element }
sort(less) + [f_element] + sort(greater)
end

arr = "happiness".chars
print sort(arr)
#=> ["a", "e", "h", "i", "n", "p", "p", "s", "s"]


Related Topics



Leave a reply



Submit