A document from MCS 260 Fall 2021, instructor David Dumas. You can also get the notebook file.

MCS 260 Fall 2021 Worksheet 12 Solutions

  • Course instructor: David Dumas
  • Solutions prepared by: David Dumas and Jennifer Vaccaro (Fall 2020 MCS 260 TA) and Kylash Viswanathan

Topics

This worksheet focuses on regular expressions, software licensing, and testing with pytest.

Resources

The main resources to refer to for this worksheet are:

(Lecture videos are not linked on worksheets, but are also useful to review while working on worksheets. Video links can be found in the course course Blackboard site.)

1. Regular expression puzzles

In each part, if a description is given, write a regular expression to match it. It a regular expression is given, write a description of what it matches.

Example 1

Matches the string fact, optionally followed by an underscore and another word consisting of latin alphabet letters, followed by a pair of parentheses with an integer between them. No spaces are permitted anywhere.

Example 1 solution

fact(_[A-Za-z]+)?\(\d+\)

Discussion: We use \( or \) to match an actual parenthesis** as opposed to ( and ) which create groups. Matches fact_recursive(5) or fact(15) or fact_iterative(0), but does not match fact() or fact_ (2) or fact_recursive( 67 ).

Example 2

(xx)+x([yz][yz])*

Example 2 solution

Matches an odd number of xs, requiring at least three to be present, followed by an even number of letters that are all y or z. It is permissible to not have any y or z there at all.

Discussion: This would match xxxzy or xxxxx or xxxzzyyzy but not x or xyz or xxxxyy. Note that the highlighter on pythex.org will highlight the first match it finds, and ignore any other match that overlaps with it. So if you test this expression with string xxxxyz, it will highlight the initial xxx match, even though there is also xxxyz later in the string which matches as well (but overlaps the first one).

A

Match the recommended style for Python variable names: A string consisting of lower case letters, underscores, and digits, and which doesn't start with a digit or underscore.

For example, all of these should match:

  • number_of_angry_pandas
  • draft91final_modified
  • i
  • res

And NONE of these should match:

  • _whatever
  • 5november
  • 오징어
  • AbstractJavaCourseFactory
  • M

A Solution

[a-z][A-Za-z0-9_]*

B

[Mm]on(day)?|[Tt]ue(s(day)?)?|[Ww]ed(nesday)|[Tt]hu(r(s(day)?)?)?|[Ff]ri(day)

(Hint: the intent is probably clear; but what's going on with all the nested parentheses and question marks? Does Thusday match? Does Thur? Why or why not?)

B Solution

Matches the name of one day of the week in English, capitalized or not, allowing the abbreviations

  • Mon
  • Tue
  • Tues
  • Wed
  • Thu
  • Thur
  • Thurs
  • Fri

The nested parentheses handle the logic to allow certain abbreviations. For example, in Thursday it's optional to have anything after the u, but if something is there, it should begin with r and optionally continue. If it continues, the next letter should be s, optionally followed by day.

C

Match a Canadian postcode, which consists of a capital letter, a digit, a capital letter, a space, a digit, a capital letter, and another digit.

e.g. K8N 5W6 and V8K 2S0 match while A8K2L3 and K8n 5W6 and 1N2 S74 and ASK DAD do not match.

C Solution

[A-Z]\d[A-Z] \d[A-Z]\d

D

\(\s*[0-9]+(\s*,\s*[0-9]+)\s*\)

D Solution

A list of integers, separated by commas and optionally spaces, and surrounded by parentheses. Must have at least one integer present (e.g. (58) or (1,2, 3, 4 , 5) matches but () or 2,3 does not).

2. Testing the geometry module

Here is a link to the geometry.py module we developed in a previous lecture. It allows you to represent Circles, Rectangles, and Squares in the plane and to perform some operations on them.

Download this file and save it in its own folder (e.g. geomtesting would be a good name for the folder).

Now, create a file test_geometry.py in the same folder. In that file, create at least 4 functions whose names begin with test_ that perform tests on the classes in test_geometry.py. Running this script with the python interpreter should do nothing, since it should only declare test functions, and should not call them.

In a terminal opened to the same directory containing test_geometry.py, run pytest with

python3 -m pytest

and confirm that all of your tests are discovered, and that they all pass.

Examples of suggested tests:

  • two Circle objects created to have the same center an radius compare as equal
  • two Rectangle objects created to have the same center, width, and height compare as equal
  • using the .scale() method of a specific Rectangle you choose for testing purposes gives the same results as what you expect based on computing by hand.
  • adding two example rectangles gives the same bounding box as you compute by hand.

Note that the equality test for objects in geometry.py is potentially quite fragile, because it tests floats for equality. One way to avoid problems with floating point error is to choose examples where every value involved (width, height, center coordinates, radius, scale factor, etc.) is actually an integer.

Solution

In [ ]:
# Contents of `test_geometry.py`
# Based on Fall 2020 solution by Jennifer Vaccaro
'''pytest test functions for the module geometry'''

import geometry

def test_circleeq():
    '''Confirms that two circles with the same 
    center and radius are equal'''
    C1 = geometry.Circle(1, 2, 5)
    C2 = geometry.Circle(1, 2, 5)
    assert C1 == C2
    
def test_circleineq1():
    '''Confirms that two circles with differing
    centers are not equal'''
    C1 = geometry.Circle(1, 2, 5)
    C2 = geometry.Circle(0, 2, 5)
    assert C1 != C2 

def test_circleineq2():
    '''Confirms that two circles with differing
    centers are not equal'''
    C1 = geometry.Circle(1, 2, 5)
    C2 = geometry.Circle(1, 3, 5)
    assert C1 != C2 
    
def test_circleineq3():
    '''Confirms that two circles with differing
    radii are not equal'''
    C1 = geometry.Circle(1, 2, 5)
    C2 = geometry.Circle(1, 2, 89)
    assert C1 != C2 
    
def test_circlescale():
    '''Confirms that the circle scaling 
    behaves as expected'''
    C = geometry.Circle(5,5,5)
    C.scale(3)
    assert C.cx == 5
    assert C.cy == 5
    assert C.r == 15
    
def test_circletranslate():
    '''Confirms that circle translating 
    behaves as expected.'''
    C = geometry.Circle(5,5,5)
    C.translate(4,3)
    assert C.cx == 9
    assert C.cy == 8
    assert C.r == 5

def test_recteq():
    '''Confirms that two rectangles with the 
    same centers and sidelengths are equal'''
    R1 = geometry.Rectangle(1,-3, 3, 2)
    R2 = geometry.Rectangle(1,-3, 3, 2)
    assert R1 == R2
    
def test_rectineq1():
    '''Confirms that two rectangles that differ
    in center or sidelength are not equal'''
    R1 = geometry.Rectangle(1,-3, 3, 2)
    R2 = geometry.Rectangle(2,-3, 3, 2)
    assert R1 != R2
    
def test_rectineq2():
    '''Confirms that two rectangles that differ
    in center or sidelength are not equal'''
    R1 = geometry.Rectangle(1,-3, 3, 2)
    R2 = geometry.Rectangle(1,-4, 3, 2)
    assert R1 != R2
    
    
def test_rectineq3():
    '''Confirms that two rectangles that differ
    in center or sidelength are not equal'''
    R1 = geometry.Rectangle(1,-3, 3, 2)
    R2 = geometry.Rectangle(1,-3, 5, 2)
    assert R1 != R2
    
    
def test_rectineq4():
    '''Confirms that two rectangles that differ
    in center or sidelength are not equal'''
    R1 = geometry.Rectangle(1,-3, 3, 2)
    R2 = geometry.Rectangle(1,-3, 3, 1)
    assert R1 != R2

def test_rectscale():
    '''Confirms that the rectangle scaling 
    behaves as expected'''
    R = geometry.Rectangle(1,1,2,2)
    R.scale(2)
    assert R.w == 4
    assert R.h == 4
    assert R.cx == 1
    assert R.cy == 1

def test_bbox():
    '''Confirms sum of rectangles gives
    axis-aligned bounding box for this example:
     (1,12)               (23,12)
      ┌─────────────────────┐
      │                     │
      │     (8,9)   (18,9)  │
      │      ┌─────────┐    │
      │      │         │    │
      │      │         │    │      
      │  R2  │         │    │
      │      │         │    │
      └──────┼─────────┼────┘
     (1,4)   │         │ (23,4)
             │   R1    │
             └─────────┘
            (8,1)   (18,1)
    
    '''
    R1 = geometry.Rectangle(13,5,10,8)
    R2 = geometry.Rectangle(12,8,22,8)
    assert R1 + R2 == geometry.Rectangle(12,6.5,22,11)
    
# No need for any code outside of the functions to call them! Pytest will run them for us.

3. Integer function call finder

Write a program intcalls.py that takes a filename as a command line argument. It should read the file and look for places where a Python function is called that meet all of the following characteristics:

  • The name of the function is a single lower-case word (letters a-z)
  • There is no space between the function name and its parentheses
  • Inside the parentheses is an integer, possibly surrounded by spaces

For each match it finds, the match text and the line number should be printed.

For example, if you run python3 intcalls.py findphone.py where findphone.py is the example program from lecture, the output should be:

Line 8: exit(1)
Line 15: group(1)
Line 16: group(2)
Line 17: group(3)

Solution

In [ ]:
# Contents of intcalls.py
# MCS 260 Fall 2021 Worksheet 12
import re
import sys

if len(sys.argv)<2:
    print("Usage: {} INPUTFILE".format(sys.argv[0]))

f = open(sys.argv[1],"r",encoding="UTF-8")
for i,line in enumerate(f):
    for m in re.finditer(r"[a-z]+\(\s*\d+\s*\)",line):
        print("Line {}: {}".format(i,m.group()))

4. MIT License practice

The MIT License is a popular and very permission open-source software license. Find a copy of the MIT License as a text file, and save it in the directory where you are doing your MCS 260 project work under the name LICENSE.txt. Imagine you are applying it to a piece of software you've written. Edit LICENSE.txt to fill in the relevant info. (The license you download is really a template, and has blank spaces for certain things like your name and the date the software was created.)

Solution

- Content of practice LICENSE.txt -

Copyright 2021 David Dumas

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Revision history

  • 2021-11-11 Initial release