CSE 163, Spring 2020: Homework 1: Python Crash Course

Overview

Ed Link

This homework is a Python problem set that asks you to implement many small functions to get CSE 142 students used to Python and to be a refresher for CSE 160 students. It is technically split into two main parts, but we recommend that you do them concurrently since the last part is about testing your code. One school of thought for best developer practices is to write all your tests BEFORE you write any of your real code so you are certain it works as you are writing everything.

If you would like to set up Python for local development on your computer, please see instructions here.

Learning Objectives

After this homework, students will be able to:

  • Follow a Python development workflow for this course, including:
    • Writing a Python script from scratch and turning in the assignment.
    • Use the course infrastructure (flake8, test suites, course resources).
  • Use Python to review CS1 programming concepts implement programs that follow a specification, including:
    • Use/manipulation of various data types including numbers and strings.
    • Control structures (conditional statements, loops, parameters, returns) to implement basic functions provided by a specification.
    • Basic text file processing.
    • Documenting code.
  • Write unit tests to test each function written including their edge cases.

Expectations

Here are some baseline expectations we expect you to meet:

  • Follow the course collaboration policies

  • In hw1.py you should not use any import statements or features in Python we have not yet discussed in class, section, or HW specs. All of these problems should be solved using the basic constructs we've learned in class so far. However, for the test program you may import anything you want that is provided by the cse163 environment (using something outside will not work on turn-in)

Format

In this document and future homework specs, you will see teal boxes like the ones below that provide extra information. You should read all of these unless they are indicated as Optional.

For example:

This will contain some useful information about the assignment

This box might contain background information or further information that's not necessary for the assignment.

Part 0

This is the main part of the assignment that will have you implement many small functions in Python.

We recommend reading over this part and Part 1 before writing any code so that you get into the practice of writing your tests alongside the code you are developing.

For this part you will be using hw1.py. There is a little bit of starter code there for a function called total. There is an example test in hw1_test.py to test total.

For all of the following problems, you should add a function with the expected name to hw1.py and implement the function as described. For every problem in this homework, you should make no assumptions about the parameters unless otherwise described. For every problem, you may assume we pass parameters of the expected types described for that problem and that those parameters are not None.

Problem 1: count_divisible_digits

Write a function called count_divisible_digits that takes two integer numbers n and m as an arguments and returns the number of digits in n that are divisible by m. If m is 0, then count_divisible_digits should return 0. For this problem, any digit in n that is 0 is divisible by any number. You may assume m is a single digit (i.e., \(0 \le m < 10\)). Here are some example calls:

count_divisible_digits(650899, 3) # returns 4 because 0, 6, 9 are divisible by 3
count_divisible_digits(-204, 5) # returns 1 because 0 is divisible by 5
count_divisible_digits(24, 5) # returns 0
count_divisible_digits(1, 0) # returns 0

To receive full credit on this problem, you should avoid building up a string containing the digits of the number. Instead, you should solve this problem by manipulating the number itself. You can assume m is not negative.

One of the really handy things about Python is that it has a lot of built-in functions to handle a lot of common tasks. Part of writing "Pythonic" (i.e. readable) code is knowing what these functions are so you can make your code simpler to understand. Here is a full list of Python built-in functions and their documentation and below is a list of useful ones for this assignment except the "casting" functions to convert between data-types that we saw in class

Name Description
abs Takes the absolute value of the given number
len Returns the number of elements inside any object that stores multiple elements (string, list, dictionary)
max Returns the largest number of the numbers provided
min Returns the smallest number of the numbers provided
open Opens a file with the given name
print Prints the given object
range Returns a generator that specifies the range of numbers provided
round Rounds the given number
sum Returns the sum of the given values

For example, if you want to call the max function you would write the line:

x = max(1, 7)  # x will store 7

Hint: You will likely want to use a strategy that involves integer division and the mod operator. A common pattern when working with digits is to use n // 10 and n % 10 to pull a number apart, digit by digit.

Problem 2: is_relatively_prime

Write a function called is_relatively_prime that takes two integer numbers n and m and returns True if n and m are relatively prime to each other, returning False otherwise. Two numbers are relatively prime if they share no common factors besides 1. 1 is relatively prime with every number. You may assume that n and m are at least 1 for this problem.

Some example calls are shown below where the comment underneath the call shows the factors for each of the given numbers:

is_relatively_prime(12, 13)  # True
# factors of 12 = [1, 2, 3, 4, 6, 12] and factors of 13 = [1, 13]
is_relatively_prime(4, 24)   # False, shares 2 and 4 as common factors
# factors of 4 = [1, 2, 4] and factors of 24 = [1, 2, 3, 4, 6, 8, 12, 24]
is_relatively_prime(5, 9)    # True
# factors of 5 = [1, 5] and factors of 9 = [1, 3, 9]
is_relatively_prime(8, 9)    # True
# factors of 8 = [1, 2, 4, 8] and factors of 9 = [1, 3, 9]
is_relatively_prime(8, 1)    # True
# factors of 8 = [1, 2, 4, 8] and factors of 1 = [1]

Requirements:

  • You should not create any data structures (e.g. list) to actually store these factors. You can solve this problem without needing to store all the factors.

Problem 3: travel

Write a function travel which takes a string of North, East, South, West directions, a starting location on a grid x, and a starting location on a grid y. Your function should return a string that indicates the new position after following the directions starting from the given x, y. The returned string should be in the format '(x_new, y_new)'.

The directions string will use 'N' to indicate increasing the y-coordinate, 'E' to indicate increasing the x-coordinate, 'S' to indicate decreasing the y-coordinate, and 'W' to indicate decreasing the x-coordinate. The case of the characters should be ignored. Any characters that are not 'N', 'E', 'W', or 'S' (ignoring letter-casing) should be ignored.

travel('NW!ewnW', 1, 2)  # '(-1, 4)'

# 'N', (1,2) => (1,3)
# 'W', (1,3) => (0,3)
# '!', ignored
# 'e', (0,3) => (1,3)
# 'w', (1,3) => (0,3)
# 'n', (0,3) => (0,4)
# 'W', (0,4) => (-1,4)

Problem 4: swip_swap

Write a function called swip_swap that takes a string source and characters c1 and c2 and returns the string source with all occurrences of c1 and c2 swapped. You may assume that the c1 and c2 are single characters. Here are some example calls:

swip_swap('foobar', 'f', 'o')  # 'offbar'
swip_swap('foobar', 'b', 'c')  # 'foocar'
swip_swap('foobar', 'z', 'c')  # 'foobar'

Problem 5: compress

Write a function compress which takes a string as an argument and returns a new string such that each character is followed by its count, and any adjacent duplicate characters are removed (replaced by the single character). You may assume the string only contains letters. Here is an example call

compress('cooooooooooooooooolkangaroo')  # 'c1o17l1k1a1n1g1a1r1o2'
compress('aaa') # 'a3'
compress('')  # ''

Problem 6a: longest_line_length

Write a function longest_line_length that takes a string file_name and returns the length of the longest line in the file. The length of the line is just the number of characters in it (whitespace or not). If the file is empty, the function should return None. You may assume the file exists for the given file name.

Suppose we had a file called poem.txt with the contents:

she sells
sea
shells by
the sea shore

Then the following call would return:

longest_line_length('poem.txt')  # 13

In this file, line 2 would have 4 characters because of the new-line character at the end. This is not something you need to think about though since len will return the proper thing for the strings. Because the last line has 13 characters (there is no new-line), the return of this function is 13.

In Python (and pretty much every other programming language), we us a special character called \n to represent a line-break or a new-line. While that looks like two characters (\ and n), we call it one because the \ is an "escape sequence". You do not need to really understand escape sequences besides the fact that \n is treated as a single character and represents a new-line (you can learn more here). In this example file, the last line of the file does not end with a new-line character (which is why its length is 13). There is no reason this has to be the case, it just happens to be this way in our example.

When students try making their own txt files for testing purposes on Ed, they notice that Ed always adds a new-line character at the end! This is intended behavior according to the Ed folk, but it really is not that big of a deal. Two ways to deal with this:

  • Just ignore it and change your example to account for the fact that there is a new-line at the end.
  • Create the file on your computer (using some text-editor) and then upload that to Ed.

Either way, you should always trust the behavior on Ed since this is the system we use when grading your assignment.

In Python, None is a special value that represents "missing data". For those that have taken 143 or beyond, None in Python is the same as null in Java.

What is the difference between 0 and None?

0 is a number that has a value (it's 0) that has meaning in most contexts (i.e. you can add zero to a number and you get back that number). None, on the other hand, is the absence of a value at all! For this problem, we want to be able to differentiate between returning 0, which is a valid digit to return, and some special value to indicate that this function call has no meaningful answer.

What does None do?

Nothing! You can't really do anything with None because it is a value that's missing. For example, if you were to run

x = 3
y = None
z = x + y
# TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

That seems bad. How do I detect if something is None to avoid this?

All you need is an if statement with a special condition! It looks like:

x = some_method_that_might_return_none()
if x is None:
    # do whatever you need to do in this case
else:
    # you know x is not None, so it will be a valid value!

Problem 6b: longest_word

Write a function longest_word that takes a string file_name and returns the longest word in the file with which line it appears on. If there are ties for the longest word, it should return the one that appears first in the file. If the file is empty or there are no words in the file, the function should return None. You may assume that the file_name describes a file that exists.

Suppose we had a file called poem.txt with the contents:

she sells
sea
shells by
the sea shore

Then the following call would return:

longest_word('poem.txt')  # '3: shells'

Because the longest word is "shells" and appears on line 3.

Problem 7: mode_digit

Write a function mode_digit that takes an integer number n and returns the digit that appears most frequently in that number. The given number may be positive or negative, but the most frequent digit returned should always be non-negative. If there is a tie for the most frequent digit, the digit with the greatest value should be returned.

To receive full credit, your solution must not use any nested loops or recursion (if you don't know what that is, you aren't using it), and you must have no more than 4 if/elif/else branches. You are allowed to use extra storage to solve this problem. Here are some example calls

mode_digit(12121)       # 1
mode_digit(0)           # 0
mode_digit(-122)        # 2
mode_digit(1211232231)  # 2

If you want to create a list with N zeroes in, you can write the line

a = [0] * N,
# If N = 3, then a = [0, 0, 0]

Part 1

In this part of the assignment, you will write tests for your solutions in Part 0.

We use testing to help us feel confident that the programs we write are correct. Testing involves calling the functions you wrote with some input and comparing the output with some expected value. Recall that from HW0, we tested our code by writing test cases in another file dedicated to making calls to the provided assert_equals function. You may want to revisit HW0 to understand how we set up the testing code.

We have provided an example function in hw1.py and some example tests in hw1_test.py to remind you of the setup.

For this part of the assignment, you will add functions to hw1_test.py to test all the functions that you wrote for Part 0.

Part of your testing functions that work with files might involve creating new files to test. When Ed runs your program in the Mark command, it ends up running it in a different directory than the one you developed your code in. What this means is that if you made a test txt file named test.txt and tried to read it in your code, your test program would crash since that txt file won't get put in that new directory Ed is running your code in.

To fix this, anytime you want to reference a specific file in your test program on Ed, you should use an absolute path to specify the file instead of just the file name. Specifically, this means any time you want to reference one of your own file names you uploaded, you should prepend it with an absolute path (e.g. /home/test.txt). This is not a Python thing, but rather how Ed sets up how they run your code which then requires you to specify this full path.

Evaluation

Your submission will be evaluated on the following dimensions:

  • Your solution works externally for the cases shown.
  • For this assignment, we will show you all tests that will be run against your submission on Ed to give you a better idea how to test your code. In future weeks, we will withhold some tests to evaluate your solution that you will not see when you submit your code. The withheld tests will be things described in the spec so they shouldn't be a surprise, but an important part of this class is learning how to test your own solution to be very sure it's correct.
  • Your code meets our style requirements:
    • All files submitted pass flake8.
    • Your program should be written with good programming style. This means you should use the proper naming convention for methods (snake_case), your code should not be overly redundant and should avoid unnecessary computations.
    • Every function written is commented using a doc-string format that describes its behavior, parameters, returns, and highlights any special cases. These comments must be in your own words.
    • There is a comment at the top of each file you write with your name, section, and a brief description of what that program does.
    • Any expectations on this page or the sub-pages for the assignment are met as well as all requirements for each of the problems are met.

For full credit, your hw1_test.py must satisfy all of the following conditions:

  • Uses the main method pattern shown in class (the starter file will use it).
  • Has a function to test each of the functions in the hw1.py that you were asked to write.
  • Each test function must have at least one test for each of the example calls shown in the spec and must have two additional tests that are different from the ones shown in the spec. A single test is considered a call on assert_equals.
  • Each of these test functions should have a descriptive name that indicates which function is being tested (e.g. test_total).
  • Each of the test functions must be called from main.

A lot of students usually ask questions like "Can I use this method" or "Can I use this language feature in this class?". The general answer to this question is it depends on what you want to use, what the problem is asking you to do and if there are any restrictions that problem places on your solution.

There is no automatic deduction for using some advanced feature or using material that we have not covered in class yet, but if it violates the restrictions of the assignment, it is possible you will lose points. It's not possible for us to list out every possible thing you can't use on the assignment, but we can say with certainty that you are safe to use anything we have covered in class so far as long as it meets what the specification asks and you are appropriately using it as we showed in class.

For example, some things that are probably okay to use even though we didn't cover them:

  • Using the update method on the set class even though I didn't show it in lecture. It was clear we talked about sets and that you are allowed to use them on future assignments and if you found a method on them that does what you need, it's probably fine as long as it isn't violating some explicit restriction on that assignment.
  • Using something like a ternary operator in Python. This doesn't make a problem any easier, it's just syntax.

For example, some things that are probably not okay to use:

  • Importing some random library that can solve the problem we ask you to solve in one line.
  • If the problem says "don't use a loop" to solve it, it would not be appropriate to use some advanced programming concept like recursion to "get around" that restriction.

These are not allowed because they might make the problem trivially easy or violate what the learning objective of the problem is.

You should think about what the spec is asking you to do and as long as you are meeting those requirements, we will award credit. If you are concerned that an advanced feature you want to use falls in that second category above and might cost you points, then you should just not use it! These problems are designed to be solvable with the material we have learned so far so it's entirely not necessary to go look up a bunch of advanced material to solve them.

tl;dr; We will not be answering every question of "Can I use X" or "Will I lose points if I use Y" because the general answer is "You are not forbidden from using anything as long as it meets the spec requirements. If you're unsure if it violates a spec restriction, don't use it and just stick to what we learned before the assignment was released."

Submission

This assignment is due by Thursday, April 16 at 23:59 (PDT).

You should submit your finished hw1.py and hw1_test.py on Ed.

On very rare occasions, students have reported seeing different behavior when running on Ed vs running on their own computer (if they are developing locally). You should always default to what Ed says as this is the same environment we will use while grading your submission.

You may submit your assignment as many times as you want before the late cutoff (remember submitting after the due date will cost late days). Recall on Ed, you submit by pressing the "Mark" button. You are welcome to develop the assignment on Ed or develop locally and then upload to Ed before marking.