Newbie: sorting an array of custom objects


#1

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


#2

Einar Høst wrote:

Thanks a lot!

Kind regards,
Einar

arr.sort_by{|obj| obj.some_field}

lopex


#3

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


#4

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

#5

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


#6

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


#7

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(’, ')


#8

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 :wink:

– Daniel


#9

“=?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}

:wink:


#10

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


#11

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 :slight_smile:

  • Einar

#12

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! :slight_smile: 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