Forum: Ruby Newbie: sorting an array of custom objects

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Einar Høst (Guest)
on 2006-03-08 16:43
(Received via mailing list)
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
Marcin MielżyÅ?ski (Guest)
on 2006-03-08 16:46
(Received via mailing list)
Einar Høst wrote:
> Thanks a lot!
>
> Kind regards,
> Einar

arr.sort_by{|obj| obj.some_field}

lopex
Marcin MielżyÅ?ski (Guest)
on 2006-03-08 16:49
(Received via mailing list)
Marcin MielżyÅ?ski wrote:
>> 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
Einar Høst (Guest)
on 2006-03-08 16:49
(Received via mailing list)
Marcin MielżyÅ?ski skrev:
>> 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
Daniel H. (Guest)
on 2006-03-08 16:58
(Received via mailing list)
On Mar 8, 2006, at 3:38 PM, Einar Høst wrote:

> 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.

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

require 'set'

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

-- Daniel
Xavier N. (Guest)
on 2006-03-08 17:01
(Received via mailing list)
On Mar 8, 2006, at 15:38, Einar Høst wrote:

> 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.

The analogous approach in Ruby uses the Comparable mixin:

   class Foo
     include Comparable

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

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

-- fxn
Brian Mattern (Guest)
on 2006-03-08 17:26
(Received via mailing list)
On Wednesday 08 March 2006 08:48, 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

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

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

So, you could do something like:

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

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

sorted = cards.sort { |a, b|
  ord = SUITS.index(a.last) <=> SUITS.index(b.last) #sort by suit
  ord = a.first <=> b.first if ord = 0 #suit matched, so sort value
  ord
}

#print out the cards in sorted order
puts sorted.map{ |card| "#{card.first} of #{card.last}" }.join(', ')
Daniel H. (Guest)
on 2006-03-08 18:18
(Received via mailing list)
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
Daniel H. (Guest)
on 2006-03-08 18:54
(Received via mailing list)
On Mar 8, 2006, at 5:15 PM, Daniel H. 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
George O. (Guest)
on 2006-03-09 04:49
(Received via mailing list)
"=?UTF-8?B?RWluYXIgSMO4c3Q=?=" <removed_email_address@domain.invalid> 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}

;-)
Einar (Guest)
on 2006-03-09 10:01
(Received via mailing list)
Daniel H. wrote:
>
>   end
>
>       end
>     include Comparable
>       if @suite == card.suite
>     end
> hand1 = CardGame::Hand.new
> __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 (Guest)
on 2006-03-09 10:04
(Received via mailing list)
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
This topic is locked and can not be replied to.