Newbie: sorting an array of custom objects

Discussion in 'Ruby' started by Einar Høst, Mar 8, 2006.

  1. Einar Høst

    Einar Høst Guest

    Hi,

    As a project for learning Ruby, I'm writing a simple game. I have some
    simple objects that I would like to be able to sort 'automatically' in
    an array. In Java, I can implement the Comparable interface to make the
    Array.sort method do this for me. I'm sure I can do something similar or
    simpler i Ruby, I just don't know how. Can anyone help? An even more
    elegant solution would be to able to say to the array 'just put this
    object where it belongs', without really having to sort the whole array.

    Thanks a lot!

    Kind regards,
    Einar
     
    Einar Høst, Mar 8, 2006
    #1
    1. Advertising

  2. Einar Høst wrote:
    > Hi,
    >
    > As a project for learning Ruby, I'm writing a simple game. I have some
    > simple objects that I would like to be able to sort 'automatically' in
    > an array. In Java, I can implement the Comparable interface to make the
    > Array.sort method do this for me. I'm sure I can do something similar or
    > simpler i Ruby, I just don't know how. Can anyone help? An even more
    > elegant solution would be to able to say to the array 'just put this
    > object where it belongs', without really having to sort the whole array.
    >
    > Thanks a lot!
    >
    > Kind regards,
    > Einar


    arr.sort_by{|obj| obj.some_field}

    lopex
     
    Marcin Mielżyński, Mar 8, 2006
    #2
    1. Advertising

  3. Marcin Mielżyński wrote:
    > Einar Høst wrote:
    >> Hi,
    >>
    >> As a project for learning Ruby, I'm writing a simple game. I have some
    >> simple objects that I would like to be able to sort 'automatically' in
    >> an array. In Java, I can implement the Comparable interface to make
    >> the Array.sort method do this for me. I'm sure I can do something
    >> similar or simpler i Ruby, I just don't know how. Can anyone help? An
    >> even more elegant solution would be to able to say to the array 'just
    >> put this object where it belongs', without really having to sort the
    >> whole array.
    >>
    >> Thanks a lot!
    >>
    >> Kind regards,
    >> Einar

    >
    > arr.sort_by{|obj| obj.some_field}
    >


    there is also another method for array sorting (actually it was the
    first in standard library)

    arr.sort{|a,b| a.some_field <=> b.some_field}

    if you define <=> operator for your class

    def <=> arg
    @some_field <=> arg.some_field
    end

    ther you will be able to

    arr.sort

    lopex
     
    Marcin Mielżyński, Mar 8, 2006
    #3
  4. Marcin Mielżyński skrev:
    > Einar Høst wrote:
    >> Hi,
    >>
    >> As a project for learning Ruby, I'm writing a simple game. I have some
    >> simple objects that I would like to be able to sort 'automatically' in
    >> an array. In Java, I can implement the Comparable interface to make
    >> the Array.sort method do this for me. I'm sure I can do something
    >> similar or simpler i Ruby, I just don't know how. Can anyone help? An
    >> even more elegant solution would be to able to say to the array 'just
    >> put this object where it belongs', without really having to sort the
    >> whole array.
    >>
    >> Thanks a lot!
    >>
    >> Kind regards,
    >> Einar

    >
    > arr.sort_by{|obj| obj.some_field}
    >
    > lopex


    That solution requires some_field to be 'naturally' ordered, though,
    doesn't it? (I'm very new to Ruby...) What if some_field contains a
    string, and I want 'Oranges' to be sorted before 'Apples'? Actually, I'm
    writing a card game, so I want 'Spades' < 'Hearts' < 'Clubs' < 'Diamonds'.

    - Einar
     
    Einar Høst, Mar 8, 2006
    #4
  5. On Mar 8, 2006, at 3:38 PM, Einar H=F8st wrote:

    > As a project for learning Ruby, I'm writing a simple game. I have =20
    > some simple objects that I would like to be able to sort =20
    > 'automatically' in an array. In Java, I can implement the =20
    > Comparable interface to make the Array.sort method do this for me. =20
    > I'm sure I can do something similar or simpler i Ruby, I just don't =20=


    > know how. Can anyone help? An even more elegant solution would be =20
    > to able to say to the array 'just put this object where it =20
    > belongs', without really having to sort the whole array.


    You could use a SortedSet, however this has a downside. A Set =20
    contains no duplicate objects.

    require 'set'

    set =3D SortedSet.new
    set << 3
    set << 1
    set << 10
    set << 3
    p set # -> #<SortedSet: {1, 3, 10}>

    -- Daniel=
     
    Daniel Harple, Mar 8, 2006
    #5
  6. Einar Høst

    Xavier Noria Guest

    On Mar 8, 2006, at 15:38, Einar H=F8st wrote:

    > As a project for learning Ruby, I'm writing a simple game. I have =20
    > some simple objects that I would like to be able to sort =20
    > 'automatically' in an array. In Java, I can implement the =20
    > Comparable interface to make the Array.sort method do this for me. =20
    > I'm sure I can do something similar or simpler i Ruby, I just don't =20=


    > know how. Can anyone help? An even more elegant solution would be =20
    > to able to say to the array 'just put this object where it =20
    > belongs', without really having to sort the whole array.


    The analogous approach in Ruby uses the Comparable mixin:

    class Foo
    include Comparable

    def <=3D>(other)
    # custom order logic
    end
    end

    which, based on the custom <=3D>, in addition provides for free the =20
    operators <, <=3D, =3D=3D, >=3D, and >.

    -- fxn
     
    Xavier Noria, Mar 8, 2006
    #6
  7. On Wednesday 08 March 2006 08:48, Einar H=C3=B8st wrote:

    >
    > That solution requires some_field to be 'naturally' ordered, though,
    > doesn't it? (I'm very new to Ruby...) What if some_field contains a
    > string, and I want 'Oranges' to be sorted before 'Apples'? Actually, I'm
    > writing a card game, so I want 'Spades' < 'Hearts' < 'Clubs' < 'Diamonds'.
    >
    > - Einar


    Ideally you would have a Card class that has value and suit fields. Then yo=
    u=20
    could implement <=3D> and just use Array#sort.

    Alternatively, if you want to sort a specific way just the one time, you ca=
    n=20
    pass a block to sort which takes two parameters |a,b| and returns -1 if a <=
    =20
    b, 0 if a =3D=3D b and 1 if a >b.

    So, you could do something like:

    #an array of suit strings
    SUITS =3D %w{Spades Hearts Clubs Diamonds}

    # use a simple array for each card for this example
    cards =3D [[3, 'Clubs'], [2, 'Spades'], [10, 'Diamonds'], [5, 'Clubs'], [7,=
    =20
    'Hearts']]

    sorted =3D cards.sort { |a, b|=20
    ord =3D SUITS.index(a.last) <=3D> SUITS.index(b.last) #sort by suit
    ord =3D a.first <=3D> b.first if ord =3D 0 #suit matched, so sort value
    ord
    }
    =20
    #print out the cards in sorted order
    puts sorted.map{ |card| "#{card.first} of #{card.last}" }.join(', ')

    =2D-
    Brian Mattern
     
    Brian Mattern, Mar 8, 2006
    #7
  8. On Mar 8, 2006, at 3:48 PM, Einar H=F8st wrote:

    > That solution requires some_field to be 'naturally' ordered, =20
    > though, doesn't it? (I'm very new to Ruby...) What if some_field =20
    > contains a string, and I want 'Oranges' to be sorted before =20
    > 'Apples'? Actually, I'm writing a card game, so I want 'Spades' < =20
    > 'Hearts' < 'Clubs' < 'Diamonds'.
    >
    > - Einar


    As mentioned, you should use the Comparable mix-in.

    class Card
    include Comparable

    SUITES =3D %w{Spade Heart Club Diamond}
    VALUES =3D %w{Ace King Queen Jack} + ("1".."10").to_a.reverse

    def initialize(suite, value)
    @suite, @value =3D suite, value
    end
    attr_reader :suite, :value

    def <=3D>(card)
    if @suite =3D=3D card.suite
    VALUES.index(@value) <=3D> VALUES.index(card.value)
    else
    SUITES.index(@suite) <=3D> SUITES.index(card.suite)
    end
    end
    end

    and full example of a Card game skeleton:

    require 'pp'

    module CardGame
    class Deck
    def initialize
    @cards =3D []
    Card::SUITES.each do |suite|
    Card::VALUES.each { |v| @cards << Card.new(suite, v) }
    end
    # shuffle the deck
    @cards =3D @cards.sort_by { rand }
    end

    def draw_card
    @cards.pop
    end
    end

    class Card
    include Comparable

    SUITES =3D %w{Spade Heart Club Diamond}
    VALUES =3D %w{Ace King Queen Jack} + ("1".."10").to_a.reverse

    def initialize(suite, value)
    @suite, @value =3D suite, value
    end
    attr_reader :suite, :value

    def <=3D>(card)
    if @suite =3D=3D card.suite
    VALUES.index(@value) <=3D> VALUES.index(card.value)
    else
    SUITES.index(@suite) <=3D> SUITES.index(card.suite)
    end
    end
    end

    class Hand
    def initialize
    @cards =3D []
    end

    def <<(card)
    @cards << card
    @cards.sort!
    @cards
    end
    end
    end

    deck =3D CardGame::Deck.new
    hand1 =3D CardGame::Hand.new
    hand2 =3D CardGame::Hand.new

    # Draw some cards
    3.times do
    hand1 << deck.draw_card
    hand2 << deck.draw_card
    end

    pp hand1, hand2

    __END__

    -- Daniel=
     
    Daniel Harple, Mar 8, 2006
    #8
  9. On Mar 8, 2006, at 5:15 PM, Daniel Harple wrote:

    > and full example of a Card game skeleton:


    However, the SUITES and VALUES constants should be in the Deck class.
    And be sure to raise an error from Deck#draw_card it is empty ;)

    -- Daniel
     
    Daniel Harple, Mar 8, 2006
    #9
  10. Einar Høst

    George Ogata Guest

    "=?UTF-8?B?RWluYXIgSMO4c3Q=?=" <> writes:

    > That solution requires some_field to be 'naturally' ordered, though,
    > doesn't it? (I'm very new to Ruby...) What if some_field contains a
    > string, and I want 'Oranges' to be sorted before 'Apples'? Actually,
    > I'm writing a card game, so I want 'Spades' < 'Hearts' < 'Clubs' <
    > 'Diamonds'.


    arr.sort_by{|card| ~card.suit[0] & 0x1a}

    ;-)
     
    George Ogata, Mar 9, 2006
    #10
  11. Einar Høst

    Einar Guest

    Daniel Harple wrote:
    > On Mar 8, 2006, at 3:48 PM, Einar Høst wrote:
    >
    >> That solution requires some_field to be 'naturally' ordered, though,
    >> doesn't it? (I'm very new to Ruby...) What if some_field contains a
    >> string, and I want 'Oranges' to be sorted before 'Apples'? Actually,
    >> I'm writing a card game, so I want 'Spades' < 'Hearts' < 'Clubs' <
    >> 'Diamonds'.
    >>
    >> - Einar

    >
    >
    > As mentioned, you should use the Comparable mix-in.
    >
    > class Card
    > include Comparable
    >
    > SUITES = %w{Spade Heart Club Diamond}
    > VALUES = %w{Ace King Queen Jack} + ("1".."10").to_a.reverse
    >
    > def initialize(suite, value)
    > @suite, @value = suite, value
    > end
    > attr_reader :suite, :value
    >
    > def <=>(card)
    > if @suite == card.suite
    > VALUES.index(@value) <=> VALUES.index(card.value)
    > else
    > SUITES.index(@suite) <=> SUITES.index(card.suite)
    > end
    > end
    > end
    >
    > and full example of a Card game skeleton:
    >
    > require 'pp'
    >
    > module CardGame
    > class Deck
    > def initialize
    > @cards = []
    > Card::SUITES.each do |suite|
    > Card::VALUES.each { |v| @cards << Card.new(suite, v) }
    > end
    > # shuffle the deck
    > @cards = @cards.sort_by { rand }
    > end
    >
    > def draw_card
    > @cards.pop
    > end
    > end
    >
    > class Card
    > include Comparable
    >
    > SUITES = %w{Spade Heart Club Diamond}
    > VALUES = %w{Ace King Queen Jack} + ("1".."10").to_a.reverse
    >
    > def initialize(suite, value)
    > @suite, @value = suite, value
    > end
    > attr_reader :suite, :value
    >
    > def <=>(card)
    > if @suite == card.suite
    > VALUES.index(@value) <=> VALUES.index(card.value)
    > else
    > SUITES.index(@suite) <=> SUITES.index(card.suite)
    > end
    > end
    > end
    >
    > class Hand
    > def initialize
    > @cards = []
    > end
    >
    > def <<(card)
    > @cards << card
    > @cards.sort!
    > @cards
    > end
    > end
    > end
    >
    > deck = CardGame::Deck.new
    > hand1 = CardGame::Hand.new
    > hand2 = CardGame::Hand.new
    >
    > # Draw some cards
    > 3.times do
    > hand1 << deck.draw_card
    > hand2 << deck.draw_card
    > end
    >
    > pp hand1, hand2
    >
    > __END__
    >
    > -- Daniel
    >


    Thanks a lot! This sort of resembles the code I've written, apart from
    all the interesting bits! :) In particular the sorting bit, but also
    the shuffling - so much more elegant than my "manual" approach.

    In general I'm interested "idiomatic programming", so I'm very much
    looking for "the Ruby way" of doing these things. In fact, I'm also
    writing the game in C#, which is the language I know best. In a way, the
    more different the implementations become, the happier I'll be.

    - Einar
     
    Einar, Mar 9, 2006
    #11
  12. Einar Høst

    Einar Guest

    Also, let me add that I've heard lots of nice things about the Ruby
    community - now I know that it's not just talk. Thanks to everyone for
    their replies :)

    - Einar
     
    Einar, Mar 9, 2006
    #12
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Rob Meade
    Replies:
    4
    Views:
    30,422
    rahul891
    Nov 4, 2012
  2. TheVooDooChild

    Sorting an Array of String Objects

    TheVooDooChild, Mar 22, 2005, in forum: Java
    Replies:
    12
    Views:
    40,020
    Thomas G. Marshall
    Mar 25, 2005
  3. markspace
    Replies:
    1
    Views:
    390
    markspace
    Jun 25, 2009
  4. Roedy Green
    Replies:
    1
    Views:
    445
    Roedy Green
    Jun 25, 2009
  5. Jeremy
    Replies:
    2
    Views:
    220
    Ian Kelly
    Feb 24, 2011
Loading...

Share This Page