[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Newbie: sorting an array of custom objects

Einar Høst

3/8/2006 2:39:00 PM

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
11 Answers

Marcin Mielzynski

3/8/2006 2:44:00 PM

0

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 Mielzynski

3/8/2006 2:48:00 PM

0

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

Einar Høst

3/8/2006 2:52:00 PM

0

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

Daniel Harple

3/8/2006 2:56:00 PM

0

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 Noria

3/8/2006 3:00:00 PM

0

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

3/8/2006 3:25:00 PM

0

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

--
Brian Mattern


Daniel Harple

3/8/2006 4:16:00 PM

0

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 Harple

3/8/2006 4:54:00 PM

0

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


George Ogata

3/9/2006 2:38:00 AM

0

"=?UTF-8?B?RWluYXIgSMO4c3Q=?=" <ebuffer@delete.hotmail.com> 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

3/9/2006 7:57:00 AM

0

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