Sort a list of objects by using their attributes in Ruby

前端 未结 4 520
有刺的猬
有刺的猬 2021-01-03 04:11

I have a list of Fruit structs called basket. Each Fruit struct has a name (a string) and a calories (an int

相关标签:
4条回答
  • 2021-01-03 04:27

    If you need to sort Fruits a lot, you should probably do a little more work up front and make your objects comparable.

    For this, you need to implment the Spaceship-Operator (<=>) and include Comparable.

    class Fruit
      attr_accessor :name, :color
    
      def <=>(other)
        # use Array#<=> to compare the attributes
        [self.name.downcase, self.color] <=> [other.name.downcase, other.color]
      end
    
      include Comparable
    end
    

    then you can simply do:

    list_of_fruits.sort
    

    Comparable also gives you many other methods (==, <, >) for free, so you can do things like if (apple < banana) (see the documentation for the Comparable Module for more info)

    <=>, is specified to return -1 if self is smaller than other, +1 if other is smaller and 0 if both objects are equal.

    0 讨论(0)
  • 2021-01-03 04:29

    The easy solution is

    basket.sort_by { |f| [-f.calories, f.name] }
    

    Of course, if this is the canonical sort order for fruit then it should be defined using the <=> method and with the Comparable module mixed into Fruit

    0 讨论(0)
  • 2021-01-03 04:42

    Let's assume that your basket is an Array or a subclass thereof.

    The Fast Way

    Enumerable.sort_by

    As Gareth pointed out, Enumerable (included by Array) has a sort_by method that runs through each list item once. This is faster to run and faster to write once you get the hang of it.

    # -f.calories to sort descending
    # name.downcase to do a case-insensitive sort
    basket = basket.sort_by { |f| [-f.calories, f.name.downcase] }
    

    The Perl Way

    Array.sort

    Coming from a Perl background, my first impulse is to grab the spaceship operator <=>. Cheeky little devil. Array has the sort and sort! methods that make it very useful. This solution is slower, and because it's longer it is more likely to introduce bugs. The only reason to use it is if you're dealing with people unfamiliar with Ruby and unwilling to find the right way on StackOverflow.

    baseket.sort! { |a,b|
      if a.calories == b.calories
        a.name.downcase <=> b.name.downcase
      else
        # Reverse the result to sort highest first.
        -(a.calories <=> b.calories)
      end
    }
    
    0 讨论(0)
  • 2021-01-03 04:49

    See Array#sort (API doc). You can pass in a block that returns -1, 0, or 1 given two Fruit objects, and your block can determine these values using whatever attributes you please.

    0 讨论(0)
提交回复
热议问题