Adding exercises from the Software Carpentry 4.0 website
authorJon Pipitone <jon.pipitone@utoronto.ca>
Fri, 1 Mar 2013 23:17:30 +0000 (18:17 -0500)
committerW. Trevor King <wking@tremily.us>
Fri, 1 Nov 2013 16:34:57 +0000 (09:34 -0700)
W. Trevor King: I dropped everything from the original da170d3 except
for the exercises/test.markdown addition.  From Jon's initial pull
request [1]:

  Here are a bunch of exercises, markdown-ized, that we used to have
  as part of the website for various topics.

  I wasn't sure where to put them exactly, so I've created a new
  top-level folder for them, and placed them there, flatly. I've named
  things so that the assets for a page of exercises have the same file
  name prefix (e.g. vc.markdown, and vc_planets.txt).

[1]: https://github.com/swcarpentry/boot-camps/pull/34

exercises/test.markdown [new file with mode: 0644]

diff --git a/exercises/test.markdown b/exercises/test.markdown
new file mode 100644 (file)
index 0000000..fb25fe1
--- /dev/null
@@ -0,0 +1,137 @@
+The following exercises do not contain solutions. Yet. Instead, we will be
+asking you to submit your solutions to these exercises and then we will post up
+solutions at the start of next week. We encourage you to discuss your approaches
+or solutions on the course forum!
+
+To submit your exercises, please create a `testing` folder in your personal
+folder in the course repository. Place all of the code and files for theses
+exercises in that folder and be sure to check it in.
+
+
+## Exercise 1: Mileage
+
+The function 'convert_mileage' converts miles per gallon (US style) to liters
+per 100 km (metric style):
+
+```python
+    gal_to_litre = 3.78541178
+    mile_to_km = 1.609344
+    
+    def convert_mileage(mpg):
+        '''Converts miles per gallon to liters per 100 km'''
+        litres_per_100_km = 100 / mpg / mile_to_km * gal_to_litre
+        return litres_per_100_km
+```
+
+Create a subdirectory in your version control directory called `testing`, then
+copy this function into a file in that directory called `mileage.py`. Add more
+code to that file to repeatedly ask the user for a mileage in miles per gallon,
+and output the mileage in liters per 100 km, until the user enters the string
+"`q`". You will need to use the `float()` function to convert from string to a
+floating point number. Use the '`if __name__ == "__main__":`' trick to ensure
+that the module can be imported without executing your testing code.
+
+1. Copy `mileage.py` to create `tryexcept.py` Add a try/except block to the new
+program to display a helpful message instead of crashing when users enter
+invalid input (such as the number "0" or the name of their favorite hockey
+team). 
+
+2. Reading the function again, you realize that accepting 0 or negative values
+make no sense and should be reported as an error. Look at the exceptions defined
+in the `exceptions` module (use the built-in `help(...)` or `dir(...)`
+functions) and decide which of Python's built-in exceptions is most appropriate
+to use for invalid input. Create a copy of 'tryexcept.py' called 'raiser.py'
+that raises this exception; modify the main body of your program to catch it;
+and add a comment inside the file explaining why you chose the exception you
+did. (Note: you have to call this file `raiser.py`, not `raise.py` because
+'import raise' is an error. Â Can you see why?)
+
+3. [According to
+Google](http://www.google.ca/search?q=20+miles+per+gallon+in+litres+per+100+km&gbv=1),
+20 miles per gallon are equivalent to 11.7607292 liters per 100 km. Use these
+values to write a unit test. Keep in mind that these floating values are subject
+to truncation and rounding errors. Save the test case in a file called
+`test_mileage.py` and run it using the `nosetests` command. Â Note:
+`test_mileage.py` should use '`from raiser import convert_mileage`' to get the
+final version of your mileage conversion function.
+
+4. Now add a second test case, for 40 miles per gallon equivalent to 5.88036458
+liters per 100 km and run the tests again. Â Unless you have already fixed the
+error that was present in the initial function, your test should fail. Â Find
+and fix the error; submit your new function in a file called 'final_mileage.py'. 
+
+
+## Exercise 2: Testing Averages
+
+The results of a set of experiments are stored in a file, where the _i-th_ line
+stores the results of the _i-th_ experiment as a comma-separated list of
+integers. A student is assigned the task of finding the experiment with the
+smallest average value. She writes the following code: 
+
+```python
+    def avg_line(line):
+           values = line.split(',')
+           count = 0
+           total = 0
+           for value in values:
+                total += int(value)
+                count += 1
+           return total / count
+    
+       def min_avg(file_name):
+           contents = open(file_name)
+           averages = []
+           for (i, line) in enumerate(contents):
+               averages.append((avg_line(line), i))
+           contents.close()
+           averages.sort()
+           min_avg, experiment_number = averages[0]
+           return experiment_number
+```
+
+1. Refactor `min_avg` so that it can be tested without depending on external
+files. Submit your code in a file called `first_averages.py`.
+
+2. Write Nose test cases for both functions. Consider what should happen if the
+file is empty. Submit your tests in a file called `test_first_averages.py`.
+Note: you may assume for now that all input is well formatted, i.e., you do
+_not_ have to worry about empty lines, lines containing the names of hockey
+teams, etc.
+
+3. The given specification is ambiguous: what should the result be if two or
+more experiments are tied for the minimum average?  Copy 'first_averages.py' to
+create a new file 'second_averages.py'; modify it to handle this case; add a
+comment to the top explaining what rule you decided to use; and create a file
+'test_second_averages.py' that tests your changes.
+
+4. Another student proposed an alternative implementation of the min_avg
+function:
+
+```python
+    def min_avg(file_name):
+        contents = open(file_name).readlines()
+        min_avg = avg_line(contents[0])
+        min_index = 0
+        for (i,line) in enumerate(contents):
+            current_avg = avg_line(line)
+            if current_avg <= min_avg:
+                min_avg = current_avg
+                min_index = i
+        return min_index
+```    
+
+This implementation also finds an experiment with the smallest average, but
+possibly a different one than the your function.  Modify your test cases so that
+both your implementation and this one will pass.  (Hint: use the 'in' operator.)
+
+5. One way to avoid the ambiguity of this specification is to define a
+'min_avg_all' function instead, which returns a list with all the experiments
+with the smallest average, and let the caller select one. Write tests for the
+'min_avg_all' function, considering the following situations: an empty file,
+exactly one experiment with minimum average, and more than one experiment with
+minimum average. Keep in mind that in the last case, implementations could
+return the list in different order. Write the tests the file "test_averages.py".
+Use the same data as for the previous tests, if possible. You should use
+variables to avoid code duplication. You don't need to implement the
+'min_avg_all' function, but your test cases should be comprehensive enough to
+serve as a specification for it.