ICS 31 -- Winter 2013 -- Quiz 8

  1. On the web there is a copy of the original ICStunes music manager program that you've been working on:

    http://www.ics.uci.edu/~kay/python/ICStunes0.py

    Open this file in a separate window; you will need to refer to it for this quiz.


    1. Fill in each blank below with one data type from this group:


       int  float  bool  str 
      list namedtuple tuple set dict
      Song Album Songdisplay


      What data structure does this code use to represent:




      1. ... the music collection? a ____________ of ____________


      2. ... a single track on an album? a ____________ called ____________


      3. ... a single song along with info about that song's album? a ____________ called ____________


      4. ... the collection of songs on an album? a ____________ of ____________

      Answer Feedback:
      ANSWERS:



      1. ... the music collection? a list of album

      2. ... a single track on an album? a namedtuple called Song

      3. ... a single song along with info about that song's album? a namedtuple called Songdisplay

      4. ... the collection of songs on an album? a list of Song

    2. Write the necessary code to sort the collection MUSIC into alphabetical order by the album artist's name.  Following the existing code, this should consist of one statement plus one short function definition.

      Answer Feedback:
      ANSWER:
      def Album_artist(a: Album) -> str:
      ''' Return the artist field of the album
      '''
      return a.artist
      MUSIC.sort(key=Album_artist)


      This precisely follows the pattern of sorting the collection on
      other fields (so much so that we might start thinking about how
      to refactor this code to avoid duplication---but that's a question
      for another time). The point here is that it's an important
      programmer's skill to be able to find similar tasks in the code
      you already have and adapt them to the new task at hand.

    3. Write the function Album_average_length that takes an album and returns the average length in seconds of a song on that album (as a float).  If any of the functions already defined in the file are useful, you should use them for full credit.

      Answer Feedback:
      ANSWER:
      def Album_average_length(a: Album) -> float:
      ''' Return the average length in seconds of a song on the album
      '''
      if len(a.songs == 0):
      return 0
      else:
      return Album_length(a) / len(a.songs)

      The problem didn't explicitly say to check for albums with zero
      songs, so we would give full credit just for the return statement
      with the correct division. But for programs outside of exams, it's
      always good practice when you're dividing to check that the divisor
      isn't zero (since trying to divide by zero produces an execution error).

    4. Write the one statement that will sort the collection MUSIC in order by the average length of each album's songs, greatest average first.  

      Answer Feedback:
      ANSWER:
      MUSIC.sort(key=Album_average_length, reverse=True)

    5. The function top_n_played (the last definition in the file) uses play_count_from_songdisplay as the key argument to the sort method. Why doesn't it use Song_play_count instead?

      Answer Feedback:
      ANSWER:


      Because top_n_played sorts a list of Songdisplays. If we're sorting
      a list of Songdisplays, the key function we use has to take a
      Songdisplay as its argument. (Recall that the function that's the
      value of the key argument to sort() takes one of the objects being
      sorted and returns a value based on that object, which is used for
      comparisons during the sort). play_count_from_songdisplay does take
      a Songdisplay, while Song_play_count takes a Song.

  2. Below are two code segments; each one generates an execution error whose message is shown. Fix the code as simply as possible to remove the error and produce the intended result.

    1. L = ['Huey', 'Dewey', 'Louie', 'Donald', 'Daisy']
      for i in range(10):
      print(L[i])

      Traceback (most recent call last):
      File "/ICS/31/Quizzes/Quiz Code/quiz8.py", line 3, in
      print(L[i])
      IndexError: list index out of range
      Answer Feedback:
      ANSWER:
      The index (or subscript or position) number, i, goes from 0 to 9
      according to the for-loop. But when it hits 5, it runs off the end of the
      five-element list. The best correction is
          for i in range(len(L)):

      so the loop just ranges through the actual size of L. This approach would
      also work:
          for duck in L:
      print(duck)
    2. # This code uses the ICStunes definitions
      def songs_from_year(MC: 'list of Album', selected_year: int) -> 'list of Songdisplay':
      ''' Return a list of Songdisplays for all songs released in the specified year
      '''
      list_of_Songdisplays = all_Songdisplays(MC)
      result = [ ]
      for sd in list_of_Songdisplays:
      if sd.year == selected_year:
      result.append(sd)
      return sd

      print("Names of songs from 1969:")
      songs_from_1969 = songs_from_year(MUSIC, 1969)
      for each_song in songs_from_1969:
      print(each_song.s_title)

      Traceback (most recent call last):
      File "/ICS/31/Quizzes/Quiz Code/quiz8.py", line 14, in
      print(each_song.s_title)
      AttributeError: 'str' object has no attribute 's_title'
      Answer Feedback:
      ANSWER:


      The short answer here is that songs_from_year is supposed to return a list (of Songdisplays)
      but instead it returns just a single Songdisplay. When we try to use that value as if it were
      a list of Songdisplays, we get the error. The correction is to have the function return
      result instead of sd.


      Here's the complete chain of reasoning:


      A message like 'str' objet has no attribute 's_title'
      says that we're trying to apply s_title (which should give us the song title from a Songdisplay)
      to something for which s_title doesn't make sense---that is, something that isn't a Songdisplay.
      So where do we apply s_title? To each_song in the print statement. We EXPECT each_song to be
      a Songdisplay, but it must not be. Where did we go wrong?
      In the bottom for-loop, each_song takes on each item in songs_from_1969, which is the value
      that was returned by songs_from_year. We EXPECT that to be a list of Songdisplays, but it
      must not be---at least one thing in songs_from_1969 (and thus in what was returned by
      songs_from_year) must NOT be a Songdisplay.


      So we'd better look at what songs_from_year actually returns.
      The songs_from_year function is supposed to return a list of Songdisplays.
      But instead it returns sd, a single Songdisplay. The bottom for-loop in the calling
      program iterates through the fields of that one Songdisplay; the fields in question are
      strings; it makes no sense to take the s_title field of a single string, which is what
      gave us the error.


      The function returns sd, but it should return result. In fact, just
      looking at the function definition might tell us that if we're familiar
      with the pattern of building up a result by scanning through a list.
      We set the result to empty, we go through the list appending what we
      want to append, and then at the end we return the result. Here, we got
      that wrong and returned the control variable of the for loop instead.
      There's the mistake.