After that little diversion into arrays and hashes, we're now ready to
implement the jukebox's
SongList
. Let's invent a basic list of
methods we need in our
SongList
. We'll want to add to it as we go
along, but it will do for now.
- append( aSong ) � list
-
Append the given song to the list.
- deleteFirst() � aSong
-
Remove the first song from the list, returning that song.
- deleteLast() � aSong
-
Remove the last song from the list, returning that song.
- [ anIndex } � aSong
-
Return the song identified by anIndex, which may be an
integer index or a song title.
This list gives us a clue to the implementation. The ability to append
songs at the end, and remove them from both the front and end, suggests a
dequeue---a double-ended queue---which we know we can implement using
an
Array
. Similarly, the ability to return a song at an integer
position in the list is supported by arrays.
However, there's also the
need to be able to retrieve songs by title, which might suggest using a
hash, with the title as a key and the song as a value. Could we use a
hash? Well, possibly, but there are problems. First a hash is
unordered, so we'd probably need to use an ancillary array to keep
track of the list. A bigger problem is that a hash does not support
multiple keys with the same value. That would be a problem for our
playlist, where the same song might be queued up for playing multiple
times. So, for now we'll stick with an array of songs, searching it
for titles when needed. If this becomes a performance bottleneck, we
can always add some kind of hash-based lookup later.
We'll start our class with a basic
initialize
method, which
creates the
Array
we'll use to hold the songs and stores a
reference to it in the instance variable
@songs
.
class SongList
def initialize
@songs = Array.new
end
end
|
The
SongList#append
method adds the given song to the end of the
@songs
array. It also returns
self, a reference to the
current
SongList
object. This is a useful convention, as it lets
us chain together multiple calls to
append
. We'll see an
example of this later.
class SongList
def append(aSong)
@songs.push(aSong)
self
end
end
|
Then we'll add the
deleteFirst
and
deleteLast
methods, trivially implemented using
Array#shift
and
Array#pop
, respectively.
class SongList
def deleteFirst
@songs.shift
end
def deleteLast
@songs.pop
end
end
|
At this point, a quick test might be in order. First, we'll append
four songs to the list. Just to show off, we'll use the fact that
append
returns the
SongList
object to chain together
these method calls.
list = SongList.new
list.
append(Song.new('title1', 'artist1', 1)).
append(Song.new('title2', 'artist2', 2)).
append(Song.new('title3', 'artist3', 3)).
append(Song.new('title4', 'artist4', 4))
|
Then we'll check that songs are taken from the start and end of the
list correctly, and that
nil
is returned when the list becomes
empty.
list.deleteFirst
|
� |
Song: title1--artist1 (1)
|
list.deleteFirst
|
� |
Song: title2--artist2 (2)
|
list.deleteLast
|
� |
Song: title4--artist4 (4)
|
list.deleteLast
|
� |
Song: title3--artist3 (3)
|
list.deleteLast
|
� |
nil
|
So far so good. Our next method is
[]
, which accesses elements
by index. If the index is a number (which we check using
Object#kind_of?
), we just return the
element at that position.
class SongList
def [](key)
if key.kind_of?(Integer)
@songs[key]
else
# ...
end
end
end
|
Again, testing this is pretty trivial.
list[0]
|
� |
Song: title1--artist1 (1)
|
list[2]
|
� |
Song: title3--artist3 (3)
|
list[9]
|
� |
nil
|
Now we need to add the facility that lets us look up a song by
title. This is going to involve scanning through the songs in the
list, checking the title of each. To do this, we first need to spend a