Problem Set 7 - Due Thu, Mar 23 at 23:59 EDT

Reading

  1. Slides and notebooks from Lec 12, Introduction to Recursion, Lec 13, Turtle Recursion
  2. Think Python, Sec. 5.8: Recursion

About this Problem Set

This problem set is intended to give you practice with recursion, list comprehensions, and data manipulation with lists and dictionaries.

  1. In Task 1 (Individual Task), you will define a recursive function named hourglass that prints an hourglass shape of characters to the screen.
  2. In Task 2 (Individual Task), you will write a recursive function that draws a pattern using Turtle World.
  3. In Task 3 (Partner Task), you will write a program to generate a path on a Google Map by building a URL, as well as perform some data analysis using list comprehensions and other dict/list/string operations. Use this shared Google Doc to find a pair programming partner. Remember that you can work with the same partner a maximum of TWICE this semester. We will deduct 2 points per day for any students who have not recorded their names on the pair programming google doc by Sunday, Mar 19 at 11:59pm.
  4. The CS111 Problem Set Guide gives an overview of psets, including a detailed description of individual and partner tasks.
  5. In Fall 2016, students spent these times:
    • Task 1: an average of 1.9 hours (min 0.5 hours, max 7 hours),
    • Task 2: an average of 2.2 hours (min 0.5 hours, max 8 hours),
    • Task 3: an average of 4.0 hours (min 1 hour, max 8 hours),

All code for this assignment is available in the ps07 folder in the cs111/download directory within your cs server account. This assignment also uses the Otter Inspector program to help you do a final check for Task 1, Task 2, Task 3 and your Honor Code form before you submit. This tool tests only for correct functionality in specific cases. It does not measure the quality of your program, which depends not just on correct functionality, but also the elegance, clarity, efficiency, documentation, and style of the implementation of this functionality.


Task 1: Hourglasses

This is an individual problem which you must complete on your own, though you may ask for help from the CS111 staff.

In this task, open the provided file named hourglass.py and define in it a recursive function named hourglass that prints an hourglass shape of characters to the screen. You must use recursion (no loops are allowed).

The function is invoked as hourglass(indent, width, char1, char2).

  1. indent is the number of spaces printed on the left of each line
  2. width is the maximum number of characters printed per line at the topmost and bottommost lines of the hourglass.
  3. char1 and char2 are the two characters in the hourglass. The hourglass should consist of alternating lines of each character, beginning with char1.

The top line of the hourglass shape consists of width copies of char1 and begins with no spaces. Each subsequent line has two fewer characters and begins with one more space than the previous line, until a line with just one or two characters is reached. Then the lines start growing again in the opposite order.

If width is less than or equal to zero, nothing should be printed.

The following invocations of the hourglass function produce the output shown below.

In [2]: hourglass(0, 11, '&', '%')
&&&&&&&&&&&
 %%%%%%%%%
  &&&&&&&
   %%%%%
    &&&
     %
    &&&
   %%%%%
  &&&&&&&
 %%%%%%%%%
&&&&&&&&&&&

In [3]: hourglass(0, 12, 'x', 'o')
xxxxxxxxxxxx
 oooooooooo
  xxxxxxxx
   oooooo
    xxxx
     oo
    xxxx
   oooooo
  xxxxxxxx
 oooooooooo
xxxxxxxxxxxx

In [4]: hourglass(5, 7, '*', '-')
     *******
      -----
       ***
        -
       ***
      -----
     *******

In [5]: hourglass(0, 1, 'A', 'B')
A

In [6]: hourglass(0, 2, 'C', 'D')
CC

In [7]: hourglass(4, 1, 'E', 'F')
    E

In [8]: hourglass(7, 2, 'G', 'H')
       GG

In [9]: hourglass(9, 0, 'I', 'J') # Nothing printed for this case

In [10]: hourglass(3, -17, 'K', 'L') # Nothing printed for this case

Note: unlike many of the recursion examples seen in class, hourglass has more than one base case.


Task 2: Drawing Ls

This is an individual problem which you must complete on your own, though you may ask for help from the CS111 staff.

Inside the ps07 folder, open the file called drawLs.py. Within drawLs.py, you will define a new function, drawLs, so that it causes a turtle to draw a pattern of capital L letters. Your function should take two arguments:

  1. size indicates the length of the vertical edge of the L shape
  2. levels indicates the number of levels of recursion.

Each successive L is half the size of its predecessor. Below are example invocations of the drawLs function with different levels.


drawLs(200,0)

drawLs(200,1)

drawLs(200,2)

drawLs(200,3)

drawLs(200,4)

drawLs(200,5)

Details on how to draw a single L shape are shown below:

Notes:

    def run(levels):
      initializeTurtle()
      drawLs(200,levels)

 


Task 3: Location Tracker

This task is a partner problem in which you are required to work with a partner as part of a two-person team.

Tracks and Location Entries

Many fitness trackers or GPS devices generate tracks, where each track represents the path you took during some activity (e.g., taking a walk, riding a bike, driving a car) as timestamped sequence of locations that you traveled through. This data can be stored in a file where each line is a location entry with information about one location in the path.

For example, here is a sample track consisting of nine entries that represent a walk from the Science Center to Paramecium Pond:

4,09/25/2015 06:01:07 PM,42.29408,-71.30208
4,09/25/2015 06:01:55 PM,42.29405,-71.30114
4,09/25/2015 06:02:47 PM,42.29405,-71.30029
4,09/25/2015 06:03:42 PM,42.2948,-71.29971
4,09/25/2015 06:04:53 PM,42.2953,-71.29925
4,09/25/2015 06:06:09 PM,42.29547,-71.30099
4,09/25/2015 06:07:13 PM,42.29549,-71.30237
4,09/25/2015 06:08:25 PM,42.29501,-71.30384
4,09/25/2015 06:09:41 PM,42.29493,-71.30498

Each location entry is in so-called CSV (comma-separated value) format, in which fields are separated by commas. In this case, the fields are:

There are many ways to analyze the data in such a track. For example, we might want to know the total distance traveled between the points of the track, the average speed, or an estimate of the number of calories burned during the walk. We can even use the Google Static Maps API to display the path of this track on a map, by generating a URL that embeds the latitude/longitude data:

https://maps.googleapis.com/maps/api/staticmap?size=600x600&markers=label:S|42.29408,-71.30208&markers=label:E|42.29493,-71.30498&path=42.29408,-71.30208|42.29405,-71.30114|42.29405,-71.30029|42.2948,-71.29971|42.2953,-71.29925|42.29547,-71.30099|42.29549,-71.30237|42.29501,-71.30384|42.29493,-71.30498

Or click here to visit the long URL above

If you click on the above link or copy the long URL to a browser URL bar you should see the following image:

We've given you a sample data file, sampletracks.csv, containing 8 tracks whose entries are ordered by timestamp:

1,09/25/2015 06:23:01 PM,42.2941,-71.3019
1,09/25/2015 06:23:10 PM,42.29416,-71.30194
1,09/25/2015 06:24:12 PM,42.29404,-71.30106
...
6,09/29/2015 03:50:00 PM,42.20592,-71.2388
6,09/29/2015 03:50:19 PM,42.20449,-71.23774
7,09/29/2015 03:52:24 PM,42.29192,-71.303
7,09/29/2015 03:54:27 PM,42.2918,-71.30425
...
7,09/29/2015 05:31:24 PM,42.29106,-71.30122
8,09/29/2015 05:35:30 PM,42.20403,-71.23852
8,09/29/2015 05:36:53 PM,42.20501,-71.23873
...
8,09/29/2015 05:58:56 PM,42.22341,-71.22407
7,09/29/2015 06:20:39 PM,42.29256,-71.30184
...
7,09/29/2015 06:58:43 PM,42.29391,-71.30196

Entries from different tracks may be interleaved. For example, track 7 begins before track 8, but finishes after track 8.

Your Task

In this problem, you will write a program to read and analyze the tracks from such files. In particular, you'll determine the following statistics for any given track:

In the file analyzeTracks.py, we provide a function summarizeTrack that prints out a summary of the given trackID by calling three helper functions that you implement. For example, analyzing the track with ID 4 in sampletracks.csv should print:

TrackID: 4
Number of locations: 9
Total distance: 0.49 miles
Total time: 0.14 hours
Average speed: 3.46 miles per hour
Path URL: https://maps.googleapis.com/maps/api/staticmap?size=600x600&markers=label:S|42.29408,-71.30208&markers=label:E|42.29493,-71.30498&path=42.29408,-71.30208|42.29405,-71.30114|42.29405,-71.30029|42.2948,-71.29971|42.2953,-71.29925|42.29547,-71.30099|42.29549,-71.30237|42.29501,-71.30384|42.29493,-71.30498

(you can horizontally scroll the above box to view the entire URL) and analyzing the track with ID = 5 should print:

TrackID: 5
Number of locations: 62
Total distance: 4.46 miles
Total time: 0.51 hours
Average speed: 8.69 miles per hour
Path URL: https://maps.googleapis.com/maps/api/staticmap?size=600x600&markers=label:S|42.20417,-71.23804&markers=label:E|42.20414,-71.23802&path=42.20417,-71.23804|42.20457,-71.23657|42.20434,-71.23506|42.20478,-71.23385|42.20568,-71.23345|42.20609,-71.23211|42.20549,-71.23119|42.20455,-71.23127|42.2035,-71.23155|42.20217,-71.23172|42.20139,-71.23238|42.20148,-71.23362|42.20072,-71.23429|42.20028,-71.23536|42.19946,-71.23592|42.19889,-71.23691|42.19863,-71.23813|42.19757,-71.23806|42.19659,-71.23863|42.19549,-71.23887|42.19458,-71.23862|42.19378,-71.23797|42.19343,-71.2368|42.1925,-71.2369|42.19125,-71.23647|42.19008,-71.2371|42.18914,-71.23675|42.18783,-71.2362|42.18655,-71.23632|42.18598,-71.23521|42.18505,-71.23462|42.18396,-71.23465|42.18516,-71.23464|42.1859,-71.23553|42.18578,-71.23689|42.18646,-71.23812|42.18753,-71.23766|42.18846,-71.23836|42.18964,-71.2384|42.19004,-71.23705|42.1908,-71.2363|42.19194,-71.23651|42.19283,-71.23682|42.19365,-71.2379|42.19346,-71.23924|42.19357,-71.24075|42.1945,-71.24153|42.19546,-71.24141|42.19633,-71.24108|42.19728,-71.23995|42.19835,-71.24009|42.19964,-71.23988|42.20097,-71.23974|42.20179,-71.23909|42.20275,-71.23918|42.20347,-71.24039|42.20425,-71.24165|42.20515,-71.24104|42.20607,-71.23975|42.20533,-71.23906|42.20474,-71.23781|42.20414,-71.23802

You will complete three helper functions used by summarizeTrack and a fourth function that reads tracks from a file and returns a dictionary of tracks in the format required by summarizeTrack. The following subtasks will guide you through implementing the track analysis:

Subtask A: Familiarize yourself with track entry lists.

Begin by studying analyzeTracks.py. This contains the function summarizeTrack, which has already been implemented for you, plus the contracts for four functions that you are required to implement.

Several functions must use a list of entries, where each entry is a dictionary that represents one location in the path of a track. Each entry dictionary has two keys:

(Here, pair just means a tuple with two elements.)

For example, the track file line:

  1,09/25/2015 06:23:01 PM,42.2941,-71.3019

should become the entry dictionary:

  {"date": "09/25/2015 06:23:01 PM",
   "lat/lon": (42.2941, -71.3019)}

The variable track4Entries is a predefined list of 9 dictionary entries that correspond to the 9 locations in the track with trackID '4':

In [3]: track4Entries
Out[3]:
[{'date': '09/25/2015 06:01:07 PM', 'lat/lon': (42.29408, -71.30208)},
 {'date': '09/25/2015 06:01:55 PM', 'lat/lon': (42.29405, -71.30114)},
 {'date': '09/25/2015 06:02:47 PM', 'lat/lon': (42.29405, -71.30029)},
 {'date': '09/25/2015 06:03:42 PM', 'lat/lon': (42.2948, -71.29971)},
 {'date': '09/25/2015 06:04:53 PM', 'lat/lon': (42.2953, -71.29925)},
 {'date': '09/25/2015 06:06:09 PM', 'lat/lon': (42.29547, -71.30099)},
 {'date': '09/25/2015 06:07:13 PM', 'lat/lon': (42.29549, -71.30237)},
 {'date': '09/25/2015 06:08:25 PM', 'lat/lon': (42.29501, -71.30384)},
 {'date': '09/25/2015 06:09:41 PM', 'lat/lon': (42.29493, -71.30498)}]

There is another predefined variable track5Entries that holds the 62 dictionary entries in the track with trackID '5'.

Subtask B: Implement track analysis functions.

Start by implementing the functions trackTime, trackDistance, and trackURL to inspect tracks. These functions all expect a list of entry dictionaries as their single argument. Test each of your function implementations on at least track4Etnries and track5Enteries before continuing.

  1. trackTime(entryDicts): Returns the total time in hours for the track consisting of the given list of entry dictionaries. For example:

    In [5]: trackTime(track4Entries)
    Out[5]: 0.14277777777777778
    # The number of hours between 6:01:07 PM and 6:09:41 PM
    
    In [6]: trackTime(track5Entries)
    Out[6]: 0.5133333333333333
  2. trackDistance(entryDicts): Returns the total distance in miles in the path of the track whose entries are the given list of entry dictionaries. For example:

    In [7]: trackDistance(track4Entries)
    Out[7]: 0.4939330869379443 # The total length in miles of the 8 segments between the 9 points of the track
    
    In [8]: trackDistance(track5Entries)
    Out[8]: 4.459767440350546
  3. trackURL(entryDicts): Returns a Google Static Maps URL that displays the track of this path and marks the locations of the first and last entries. For example:

    trackURL(track4Entries)
    Out[9]: 'https://maps.googleapis.com/maps/api/staticmap?size=600x600&markers=label:S|42.29408,-71.30208&markers=label:E|42.29493,-71.30498&path=42.29408,-71.30208|42.29405,-71.30114|42.29405,-71.30029|42.2948,-71.29971|42.2953,-71.29925|42.29547,-71.30099|42.29549,-71.30237|42.29501,-71.30384|42.29493,-71.30498'
    
    trackURL(track5Entries)
    Out[10]: 'https://maps.googleapis.com/maps/api/staticmap?size=600x600&markers=label:S|42.20417,-71.23804&markers=label:E|42.20414,-71.23802&path=42.20417,-71.23804|42.20457,-71.23657|42.20434,-71.23506|42.20478,-71.23385|42.20568,-71.23345|42.20609,-71.23211|42.20549,-71.23119|42.20455,-71.23127|42.2035,-71.23155|42.20217,-71.23172|42.20139,-71.23238|42.20148,-71.23362|42.20072,-71.23429|42.20028,-71.23536|42.19946,-71.23592|42.19889,-71.23691|42.19863,-71.23813|42.19757,-71.23806|42.19659,-71.23863|42.19549,-71.23887|42.19458,-71.23862|42.19378,-71.23797|42.19343,-71.2368|42.1925,-71.2369|42.19125,-71.23647|42.19008,-71.2371|42.18914,-71.23675|42.18783,-71.2362|42.18655,-71.23632|42.18598,-71.23521|42.18505,-71.23462|42.18396,-71.23465|42.18516,-71.23464|42.1859,-71.23553|42.18578,-71.23689|42.18646,-71.23812|42.18753,-71.23766|42.18846,-71.23836|42.18964,-71.2384|42.19004,-71.23705|42.1908,-71.2363|42.19194,-71.23651|42.19283,-71.23682|42.19365,-71.2379|42.19346,-71.23924|42.19357,-71.24075|42.1945,-71.24153|42.19546,-71.24141|42.19633,-71.24108|42.19728,-71.23995|42.19835,-71.24009|42.19964,-71.23988|42.20097,-71.23974|42.20179,-71.23909|42.20275,-71.23918|42.20347,-71.24039|42.20425,-71.24165|42.20515,-71.24104|42.20607,-71.23975|42.20533,-71.23906|42.20474,-71.23781|42.20414,-71.23802'

Notes:

We recommend completing the functions in this order:

  1. Define the function trackTime first. It does not require any looping or list comprehensions. Use appropriate indexing to access the date values, and then invokes the function diffTime. It should return the time in hours. Test it via trackTime(track4Entries) and trackTime(track5Entries).

  2. Define trackDistance. Do not use explicit for loops in this function. Instead, use list comprehensions. Also use the built-in sum function. You may wish to use the built-in zip function as well. Test it via trackDistance(track4Entries) and trackDistance(track5Entries).

  3. Define trackURL. Do not use explicit for loops. Instead, use list comprehensions. Use the built-in string method join and the built-in function str. Test it via trackURL(track4Entries) and trackURL(track5Entries).

Subtask C: Implement readEntriesFromFile.

Using the provided linesFromFile helper function, readEntriesFromFile should read the contents of the specified file, and create and return a track dictionary that maps each trackID to a chronological entry list for the track with that trackID.

For example, the reading entries from the track file track4.csv should create a dictionary where the key '4' maps to the same entry list value shown for track4Entries above:

   In [11]: readEntriesFromFile('track4.csv')
   Out[11]:
   {'4': [{'date': '09/25/2015 06:01:07 PM', 'lat/lon': (42.29408, -71.30208)},
          {'date': '09/25/2015 06:01:55 PM', 'lat/lon': (42.29405, -71.30114)},
          {'date': '09/25/2015 06:02:47 PM', 'lat/lon': (42.29405, -71.30029)},
          {'date': '09/25/2015 06:03:42 PM', 'lat/lon': (42.2948, -71.29971)},
          {'date': '09/25/2015 06:04:53 PM', 'lat/lon': (42.2953, -71.29925)},
          {'date': '09/25/2015 06:06:09 PM', 'lat/lon': (42.29547, -71.30099)},
          {'date': '09/25/2015 06:07:13 PM', 'lat/lon': (42.29549, -71.30237)},
          {'date': '09/25/2015 06:08:25 PM', 'lat/lon': (42.29501, -71.30384)},
          {'date': '09/25/2015 06:09:41 PM', 'lat/lon': (42.29493, -71.30498)}]}

We have provided several helper functions in the file tracksHelper.py that significantly simplify this task. You do not have to understand how these functions work; you only have to understand what they do.

Notes:

Subtask D: Put it all together.

The provided function summarizeTrack takes (1) a trackID and (2) a track dictionary of the form produced by readEntriesFromFile. It prints out a summary of the given trackID by calling the three functions described above. Do not edit the summarizeTrack function, but read it so that you understand what your three functions should do.

Once you have implement all four required functions, you are ready to test the whole system using summarizeTrack. Call test_summarizeTrack('sampletracks.csv'). This provided testing function uses readEntriesFromFile to read the contents of the file whose name is given as an argument. It then calls summarizeTrack for each track, where summarizeTrack itself calls trackDistance, trackTime, and trackURL. The file sampletracks.csv has 429 lines and 9 tracks.

Task 4: Honor Code Form and Final Checks

As in the previous problem sets, your honor code submission for this pset will involve defining entering values for the variables in the honorcode.py file. This is a Python file, so your values must be valid Python code (strings or numbers).

If you wrote any function invocations or print statements in your Python files to test your code, please remove them, comment them out before you submit, or wrap them in a if __name__=='__main__' block. Points will be deducted for isolated function invocations or superfluous print statements.

Remember to run otterInspect.py one final time before you submit to check that your hourglass.py and analyzeTracks.py programs work as intended, and that your honor code form is complete. While running your code through otterInspect is not required, it is highly recommended since it may catch errors in functionality that you haven't noticed. Remember that this tool tests only for correctness in specific cases. It does not measure the quality of your program, which depends not just on correctness, but also elegance, clarity, efficiency, documentation, and style.

If you have issues running otterInspect.py, first check for the following common issues.

If you're unable to resolve the issue even after going through this checklist, get in touch with an instructor.


How to turn in this Problem Set