0

I have an array of strings which need to be sorted by multiple criteria (two string attributes). However the two sorts need to be sorted in opposite directions.

Example:

Array must be sorted by attribute_a in desc order and then within that sorted by attribute_b in asc order.

I have been using .sort_by! which works fine, however I am just unsure how to implement two criteria sorting in opposite sort directions

3
  • Can you give us an example sorted array? It'd be easier to understand exactly what you want with an example. Commented Jul 31, 2014 at 11:35
  • The array is an output from a model which produces a list of organisations which have multiple attributes but I need them sorted by their "type" in desc order and by their "name" in asc order so for example when sorted it should output sorted something like this "Type z" - "name a" - "name b" - "name e" - "name f" "Type y" - "name c" - "name d" - "name g" etc. Hope this helps Commented Jul 31, 2014 at 11:50
  • I think understand what you're asking, but in the future you should edit your question and give more explicit examples. You want to make it as easy as possible for someone to help you. You should put the unsorted array, your original data, in your question, then put the desired output. That way people can test their own possible answers by inputting your input and checking if it outputs your desired result. Commented Jul 31, 2014 at 12:04

4 Answers 4

4

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 %>
Sign up to request clarification or add additional context in comments.

Comments

1

You can do something like this:

sorted = a.group_by(&:attribute_a).each do |k, v| 
  v.sort_by!(&:attribute_b)
end.sort_by(&:first).map(&:last)

sorted.reverse.flatten

This solution groups all the elements by attribute_a, sorts every group by attribute_b (asc), and the groups by attribute_a (asc).

The second line reverses the order of the groups (without changing the order of the elements within the groups), and then flattens the results, resulting in the original list sorted by attribute_a (desc), attribute_b (asc)

Comments

0

Try something like that:

a.sort! do |x, y|
    xa = get_a(x)
    ya = get_a(y)

    c = ya - xa

    return c unless c == 0

    xb = get_b(x)
    yb = get_b(y)

    return xb - yb  
end

get_a and get_b are functions to extract a and b params

1 Comment

the block must return -1, 0 or 1
0

This is a straight forward solution using sort. The reverse order is created by negating the result of <=>:

sorted = some_things.sort do |x,y|
  if x.a == y.a
    x.b <=> y.b
  else
    -(x.a <=> y.a) # negate to reverse the order
  end
end

Here's the complete program that tests it:

class Thing
  attr_reader :a, :b

  def initialize(a, b)
    @a = a
    @b = b
  end
end

# Create some Things
some_things = [Thing.new("alpha","zebra"), Thing.new("gamma", "yoda"), 
               Thing.new("delta", "x-ray"), Thing.new("alpha", "yoda"), 
               Thing.new("delta", "yoda"), Thing.new("gamma", "zebra")]

sorted = some_things.sort do |x,y|
  if x.a == y.a
    x.b <=> y.b
  else
    -(x.a <=> y.a) # negate to reverse the order
  end
end

p sorted

Which produces this output (newlines inserted):

[#<Thing:0x007fddca0949d0 @a="gamma", @b="yoda">, 
#<Thing:0x007fddca0947f0 @a="gamma", @b="zebra">, 
#<Thing:0x007fddca094958 @a="delta", @b="x-ray">, 
#<Thing:0x007fddca094868 @a="delta", @b="yoda">, 
#<Thing:0x007fddca0948e0 @a="alpha", @b="yoda">, 
#<Thing:0x007fddca094a48 @a="alpha", @b="zebra">]

2 Comments

-(x.a <=> y.a) is equivalent to y.a <=> x.a
Oh, right. I just thought the intent was clearer. Also, I didn't realize the question had already been answered as I had left the window open for a couple hours before I posted my answer. The one selected is more complete than mine for sure.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.