Submission Details
For this assignment, you’ll make a simple collection of information about music tracks (we’re calling this a library). This library is just a list of track objects - (listof track)
- so anytime you see library
think (listof track)
. Feel free to use your own tracks, other people’s tracks, or just make them up (Note that this is different than the pre-recorded lecture examples which deal with albums
). Finally, you’ll write expressions and functions that query some particular library for different kinds of information.
Important
Throughout this assignment, as with any assignment, you are encouraged when possible to reuse functions from one problem to solve subsequent problems. However, don’t just COPY AND PASTE CODE. Instead…call the function by name! That simultaneously saves you work and makes the code easier to read. Plus, if you find a bug later, you only have to fix it in one place: the original definition.
In general, these problems only require simple answers. Most have two or three line answers, although the exact number of lines depends on how you put line breaks into your code. That’s not to say that if you have an answer that’s longer that it’s wrong, but it may mean that you’re making life unnecessarily difficult for yourself. One good way to make life easier on yourself is to reuse code you’ve already written.
Write your functions to work with any input. Don’t assume our test data will look like your test data. Follow these instructions exactly. If we say to remove duplicates, make sure you remove duplicates. Make sure your type signatures exactly match the ones in the assignment as you go.
Part 0. Making Your Library
You’ll be adding your code to the exercise_3.rkt
we’ve provided for you. Each track object has three fields: the title
, the artist
name, and the genre
(e.g. “Pop”, “Rock”, “Acid-house”, “Country”, whatever). We’ve provided the track data definition for you at the top of the file:
(define-struct track (title artist genre))
How to use the track
type?
The define-struct
form automatically creates the following functions for the track
type: -
make-track
is a function for creating instances oftrack
. For example, to create ntrack
object with title"Orinoco Flow"
, artist"Enya"
, and genre"New-age"
, we would do the following:(make-track "Orinoco Flow" "Enya" "New-age")
-
track-<field-name>
(for each field) functions for accessing values of each field in atrack
object. This would mean we'd have the functionstrack-title
,track-artist
, andtrack-genre
. The following example creates a track and then accesses its fields:(define orinoco-flow (make-track "Orinoco Flow" "Enya" "New-age") (track-title orinoco-flow) ; returns "Orinoco Flow" (track-artist orinoco-flow) ; returns "Enya" (track-genre orinoco-flow) ; returns "New-age"
-
track?
is a predicate (function which returns a boolean) that identifies whether a value is atrack
object. It returnstrue
when given atrack
object andfalse
otherwise. Using theorinoco-flow
track defined in the previous example:(track? orinoco-flow) ; returns true (track? 111) ; returns false (track? "hi") ; returns false
Fill in the definitions for hip-hop-lib
and pop-lib
in your definitions file:
- Define the library
hip-hop-lib
that has exactly onetrack
object. The genre of this object should be"Hip-hop"
. - Define the library
pop-lib
that has exactly threetrack
objects. The genre of these three objects should be"Pop"
. Moreover, there should be exactly two distinct artists. Put differently, two of the threetrack
objects should be composed/sung by the same person.
Hint
Type something like this (but with the correct genres):; a library is a (listof track)
; a track is a (make-track string string string)
(define hip-hop-lib
(list (make-track "N.Y. State of Mind" "Nas" "Hip-hop")))
(define pop-lib
(list (make-track "Pyramids" "Frank Ocean" "R&B")
(make-track "Call Me Maybe" "Carly Rae Jepsen" "Pop")
...etc...))
Why these libraries?
One of the questions involves writing a function that finds all the tracks of a given genre. If all the tracks in the library are in the rock genre, then there's only one genre and when you ask for all the pop tracks and it gives back all the tracks, you don't know whether that's because the code really works, or because it's not even paying attention to the genre.Therefore, you want to make sure there are multiple artists and genres, some artists with only one track or genre, others with multiple artists or genres, etc. As an example, you can use
(append hip-hop-lib pop-lib)
to create a library that has exactly two genres. Again, they may not all be your tracks or even actual tracks you own or exist! But you need to make sure there are enough tracks and that they are varied in the right ways to test your code. For example, one of the functions you will write is intended to find artists who work in multiple genres which means you need to make sure there’s at least one artist in the library who does work in multiple genres and at least one who does not. That way you can check that the one who does appears in the output but the one who only works in one genre doesn’t appear in the output. Feel free to create more libraries other than
hip-hop-lib
and pop-lib
as you go through the assignment if you realize you need more data to properly test your code. Note that we will be testing your code against our own library, not yours. So in your tests you should test against a couple of different libraries. We’ve also included several exemplar libraries in the file for your reference.
Part 1. all-titles
Write a function to find all the titles in a library. Call this function all-titles
. It should take a (listof track)
(a library) as input, so your answer will look something like (you fill in the expression):
; all-titles : (listof track) -> (listof string)
; Gets all titles of all tracks in the library
(define all-titles
(lambda (lib) …fill this in…))
(check-expect (all-titles (list (make-track "a" "somebody" "rock")
(make-track "b" "somebody 2" "country")))
(list "a" "b"))
Hint: Here you are transforming a list of tracks into a list of titles.
Part 2. all-artists
Now write one to find all the artists in the library. Call this one all-artists
. Again, this function will take a library as input. Its signature is:
; all-artists: (listof track) -> (listof string)
It’s important that each artist should appear only once in the output. Be sure to write a test case (a check-expect
) for all-artists
that verifies this (give it a library where there are two tracks by the same artist and verify the artist name only appears once in the output).
You can use the remove-duplicates
function (which we’ve provided for you) to take a list that has the same items repeated many times and gives you back a new list that only has the one copy of each item. So if we run:
> (remove-duplicates (list "a" "b" "c" "a"))
We get the result:
(list "a" "b" "c")
Part 3. all-genres
Now write a function, all-genres
, to return all the genres, again with each genre mentioned only once. Again, write at least one test case for it so that you know it works. Its signature is:
; all-genres: (listof track) -> (listof string)
Part 4. artist-tracks
Write a function, artist-tracks
, that takes an artist name and library as inputs and outputs all the tracks by that artist. That is,
(artist-tracks "K.Flay" some-library)
should return a list with all the tracks by K.Flay. Again, write test cases to verify the operation of this function. This should output the track
objects, not just the titles of the tracks!
Hints!
You will need to usefilter
, and you will need put a lambda expression inside of the call to filter
, so your code will look roughly like: ;; artist-tracks : string (listof track) -> (listof track)
;; Get all tracks in the library by the given artist
(define artist-tracks
(λ (desired-artist lib)
(filter (λ (track) ...fill this in...)
lib)))
; your tests here
filter
with the name of a function you’ve defined separately, as in: (define is-the-right-artist?
(λ (track) ...some magic code...))
(define artist-tracks
(λ (desired-artist lib)
(filter is-this-the-right-artist? lib))
desired-artist
. But only code inside of artist-tracks
can access the desired-artist
variable. So use a λ
expression inside the call to filter
as we did above. Part 5. artist-genres
Now write a function, artist-genres
, to return all the genres of a given artist (without duplicates).
Its signature is:
; artist-genres: string (listof track) -> (listof string)
Again, you should write at least one test case (i.e. a check-expect), for this and all other problems.
Hint: Just call
artist-tracks
to find the tracks by the artist. Don’t rewrite code to do something you’ve already written code for!
Part 6. artist-is-multi-genre?
Now write a function, artist-is-multi-genre?
, that takes the name of an artist and the library as inputs and returns true
if an artist has tracks in more than one genre. Its signature is:
; artist-is-multi-genre?: string (listof track) -> boolean
Hint: Your solution should call artist-genres
.
Part 7. multi-genre-artists
Now write a function, multi-genre-artists
, that returns a list of the names of all artists who work in more than one genre. Its signature is:
; multi-genre-artists: (listof track) -> (listof string)
Your solution should call artist-is-multi-genre?
.
Part 8. artist-track-counts
Write a function, artist-track-counts
, to count the number of tracks by each artist. It should return a list of lists, where each sub-list is the name of an artist followed by the number of tracks they have in the library. Its signature is:
; artist-track-counts: (listof track) -> (listof (listof string number))
For example, assuming your library had two Snoop Dogg tracks, 1 Hayley Kiyoko track, and 1 Lido track (and nothing else), you’d get:
(check-expect (artist-track-counts some-library)
(list (list "Snoop Dogg" 2)
(list "Hayley Kiyoko" 1)
(list "Lido" 1)))
Hints
Start by writing a function that computes the number of tracks by a single artist.; artist-track-count: string (listof track) -> number
Now use this function to write a function that, given the name of an artist, returns the two-element list, the artist’s name followed by their track count.
; artist-track-count-list: string (listof track) -> (listof string number)
Now use that function, along with the
all-artists
function, to write artist-track-counts
. Part 9. genre-track-counts
Now do the same thing, but count the number of tracks in each genre, rather than the number by each artist. Call the function genre-track-counts
. Its signature is:
; genre-track-counts: (listof track) -> (listof (list string number))
Hint: start by writing a genre-tracks
function, then a genre-track-count
function, etc.
Common Problems
- Make sure that all of your functions match the provided signatures and that the function is named exactly as specified (and takes the inputs in the order specified).
- Make sure you follow instructions completely. For example, make sure that artist-genres calls
remove-duplicates
. - Don’t include any references to
testing-library-1
in your function definitions. This test library should only be used for your tests (i.e.check-expects
)!. - Remember that strings are case sensitive. So “Rock” and “rock” are different strings, and hence
(string=? "Rock" "rock")
is false.
Double Checking your Work
Before turning your assignment in, run the file one last time to make sure that it runs properly and doesn’t generate any exceptions, and all the tests pass. Make sure you’ve also spent some time writing your OWN check-expect
calls to test your code. Remember, we will use our OWN TRACKS as test cases on your functions. That means nothing in your functions can depend on the test tracks we gave you (e.g. check to make sure you actually use your inputs; check to make sure it’s actually processing those inputs; etc.).