sort on multiple attributes

T

Terje Tjervaag

Hi,

Just thought I'd write this to the list, since it wasn't immediately
obvious to me how to do it and I couldn't find it written anywhere on
the web..

Say you have an array of these music tracks:
Track =3D Struct.new("Track", :name, :artist, :album, :track_number)

..and you want to sort the array first by artist, then by album within
that artist and finally by track number within the album, this can
still be done by a pretty neat sort block:

sorted_tracks =3D tracks.sort do |a,b|
if a.artist !=3D b.artist
a.artist <=3D> b.artist
elsif a.album !=3D b.album
a.album <=3D> b.album
else
a.track_number.to_i <=3D> b.track_number.to_i
end
end
 
B

Brian Schröder

Hi,

Just thought I'd write this to the list, since it wasn't immediately
obvious to me how to do it and I couldn't find it written anywhere on
the web..

Say you have an array of these music tracks:
Track =3D Struct.new("Track", :name, :artist, :album, :track_number)

..and you want to sort the array first by artist, then by album within
that artist and finally by track number within the album, this can
still be done by a pretty neat sort block:

sorted_tracks =3D tracks.sort do |a,b|
if a.artist !=3D b.artist
a.artist <=3D> b.artist
elsif a.album !=3D b.album
a.album <=3D> b.album
else
a.track_number.to_i <=3D> b.track_number.to_i
end
end

Or you can say
sorted_tracks =3D tracks.sort_by { | track |
[track.artist, track.album, track.track_number]
}

or even:

class Array
def sort_by_att(*attributes)
attributes =3D attributes.flatten
self.sort_by { | e | attributes.map { | a | e.send(a) } }
end
end

sorted_tracks =3D tracks.sort_by_att:)artist, :album, :track_number)

(warning, untested code, beware of the bug)

regards,

Brian
 
M

Martin DeMello

Terje Tjervaag said:
Say you have an array of these music tracks:
Track = Struct.new("Track", :name, :artist, :album, :track_number)

.and you want to sort the array first by artist, then by album within
that artist and finally by track number within the album, this can
still be done by a pretty neat sort block:

tracklist.sort_by {|i| [i.artist, i.album, i.track_number] }

works because ruby already defines Array#<=> to check the first entry,
break ties on the second entry, etc.

martin
 
T

Terje Tjervaag

Or you can say
sorted_tracks =3D tracks.sort_by { | track |
[track.artist, track.album, track.track_number]
}

nice! thanks. I had a feeling it could be improved.
 
T

Terje Tjervaag

------=_Part_2244_23842669.1130328010658
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Btw, IMHO you should not need track_number.to_i as they should really be
stored as numbers.

I agree - in this case I was playing around with making the most
minimal iTunes Music Library parser I could, hence my use of .to_i.
The current 14 line program is attached, if anyone is curious. It
could be 9 if it wasn't for the error checking.
Alternative

tracks.sort do |a,b|
[:artist, :album, :track_number].each do |field|
c =3D a.send(field) <=3D> b.send(field)
return c unless c =3D=3D 0
end
0
end

<shameless advertising>
I have also a related RCR pending that still can make it into the std
lib...
http://rcrchive.net/rcr/show/293
</shameless advertising>

I could see those additions being useful - it is a case of where to
hide the inevitable complexity I suppose, and I think your RCR deals
with it in a way that's very easy to read in the end.

--
Terje

------=_Part_2244_23842669.1130328010658
Content-Type: application/octet-stream; name=itunes.rb
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="itunes.rb"

require 'rexml/document'
if !ARGV[0] then puts "Give me the path to the iTunes Music Library.xml file"; exit end
Track = Struct.new("Track", :artist, :album, :track_number, :name, :location, :genre, :track_count)
tracks = []
begin
REXML::Document.new(File.new(ARGV[0])).elements.each("plist/dict/dict/dict") do |e|
t = Track.new
e.elements.each("key"){|e|m=e.text.downcase.gsub(" ","_")+"=";t.send(m,e.next_sibling.text) if t.respond_to?(m)}
tracks << t
end
rescue REXML::parseException
puts "iTunes Library file format not recognised"
end
tracks.sort_by{|t|[t.artist,t.album,t.track_number.to_i]}.each{|track| puts track.values[0, 4].join(" - ")}



------=_Part_2244_23842669.1130328010658--
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,184
Messages
2,570,978
Members
47,561
Latest member
gjsign

Latest Threads

Top