A document from MCS 275 Spring 2024, instructor Emily Dumas. You can also get the notebook file.

MCS 275 Spring 2024 Worksheet 1 Solutions

  • Course instructor: Emily Dumas
  • Contributors to this document: Emily Dumas, Patrick Ward

Topics

This worksheet covers some initial setup and installation of software, followed by some coding exercises that use material from prerequisite courses.

Instructions

  • This isn't collected or graded. Work on it during your lab on Jan 9 or Jan 11.
  • If you took MCS 260 recently you may have some or all of the necessary tools installed already. That's fine. Be sure to test them out as outlined below and read through the worksheet to be sure you know what is assumed from now on.

Part I: Setting up a development enviroment

No solutions are needed for this part, as it asked you to complete tasks rather than solve problems.

Part II: Python Calisthenics

This section gives a series of exercises in which you'll write programs that are based on things you are expected to have seen in a prerequisite course. We'll talk about these in Lectures 2-3 as well, and the first quiz will cover the same kind of review material.

These start very easy and build up.

8. Squares

Create a simple program squares.py that prints the squares of the first 10 positive integers, so its output should look like

1
4
9
...
100

Then, add a feature to this program where it accepts an optional command line argument which is the largest integer whose square should be printed (while 10 remains the default if no argument is given). Recall that command line arguments appear in the list sys.argv which is accessible after you import the sys module, and the items in this list are strings. So sys.argv[1] would be a string containing the first word or number on the command line after the name of the script.

Thus, for example, if you run the modified script as

python3 squares.py 3

then it would be expected to produce the output

1
4
9

Solution

In [ ]:
import sys

if len(sys.argv) == 1:
    #use default length of 10
    max_len = 10
else:
    #use first argument as length
    max_len = int(sys.argv[1])

#print the squares

for i in range(1,max_len+1):
    print(i**2)

9. Getting to know our coding standards

Now that you've written a simple program from scratch, and have a full Python setup working, it's time to get acquainted with some code style rules.

All code you submit for credit needs to follow the rules described in the

These rules enforce good coding practices and ensure your programs are readable to both humans and computers.

Read the list of rules now. If any of the rules seem unclear on a first reading, check the section later in the document that gives examples of compliant and non-compliant code for that rule.

Then, take the program below (which works!) and fix it so that it does the same thing but complies with the MCS 275 rules. (Note: The rules say you need to add a declaration stating that the program is your own work, or that it is derived solely from a template provided by the instructor. That's because all graded work in MCS 275 is done individually. Since this is a worksheet, collaboration is allowed and if you work with others you should instead list your collaborators.)

# I am a program that doesn't follow MCS 275 coding guidelines
# Please fix me.
def thing(x):
 return "{} is one greater than {}".format(x,x-1)

def thing2(x):
  return "{} is one less than {}".format(x,x+1)

s = str(input("Please enter your name"))
print("Hello",s,", this is a sample Python program.")

ss = list(range(5))
sss = [ x+5 for x in ss ]
ssss = [ 10*x for x in sss ]

for i in range(len(ssss)):
      sssss = 'Some things that are true:\n' + thing(ssss[i]) + "\n" + thing2(ssss[i]) + '\n\n'
      print(sssss)

Solution

The following modifications were made to update the code to course standards

  • A header was added
  • Explanations for each of the functions was added
  • Comments were added
  • Variables and function names were changed to become more clear
  • Removed pointless conversation str() for the name variable
  • Removed manual indexing in the last for loop
  • Condensed the numbers list into one list comprehension (optional; the alternative is renaming all three lists)
In [ ]:
#MCS 275 Spring 2024 Worksheet 1
#Patrick Ward
#Code copied from worksheet and modified



def onegreater(x):
    "given an integer, output a sentence which says x is one greater than x-1"
    return "{} is one greater than {}".format(x,x-1)

def oneless(x):
    "given an integer, output a sentence which says x is one less than x+1"
    return "{} is one less than {}".format(x,x+1)

#gather the user's name and print a sentence which their name 
name = input("Please enter your name")
print("Hello",name,", this is a sample Python program.")

#create a list of numbers
numbers = [10*(x+5) for x in range(5)]

for i in numbers:
    sentence = 'Some things that are true:\n' + onegreater(i) + "\n" + oneless(i) + '\n\n'
    print(sentence)

10. Thinking inside the box

One of the nice things about Python is that strings can contain any Unicode character (as opposed to strings in some other programming languages where the set of available characters is much more limited).

Some Unicode characters are made for drawing "text graphics" boxes, like this:

╔══════════╗ 
║  Look,   ║
║  a box!  ║
╚══════════╝

The box drawing characters used above are:

  • \u2550 or
  • \u2551 or
  • \u2554 or
  • \u2557 or
  • \u255a or
  • \u255d or

Write a program box.py that prints a unicode text box of specified width and height. It should expect at least two command line arguments, giving the width and height of the box. If only those are given, the box should be printed to the screen. If a third argument is given, it should be a filename and the box should be written to that text file instead of being printed to the screen.

So, for example, the command

python box.py 3 4

should result the following text being printed, exactly:

╔═╗
║ ║
║ ║
╚═╝

(This box has a width of 3 characters and a height of 4 lines). Similarly, the command

python box.py 12 2 box.txt

should not print anything to the screen, but should result in a file box.txt being created that contains the following text, exactly:

╔══════════╗
╚══════════╝

Recommended structure of your program:

There are at most three different lines of text that appear in a box: The top of the box, the line that repeats some number of times to make the vertical sides of the box, and the bottom of the box.

I suggest you generate these three lines and store them in strings, and then assemble them into one bigger string that has all the lines and line breaks (\n characters) that you need.

To generate the strings, you can use the fact that multiplication of a string by an integer will repeat the string a certain number of times. For example, "foo"*3 evaluates to "foofoofoo".

It would be a good idea to put the part of the program that writes the box to a file into a function and have the rest of the program create a string that contains the box. Then you can decide whether to print the string or call the function to write it to a file based on whether there is a third command line argument.

Solution

In [ ]:
import sys

#read the inputs

length = int(sys.argv[1])
height = int(sys.argv[2])

#create the top, middle and bottom lines of the box

top = "\u2554"+"\u2550"*(length-2)+"\u2557"
mid = "\u2551"+" "*(length-2)+"\u2551"
bottom = "\u255a"+"\u2550"*(length-2)+"\u255d"

#create the box

box = top + ("\n" + mid)*(height-2) + "\n" + bottom


if len(sys.argv) == 3:
    #no filename is given, print box
    print(box)
else:
    #write box to the filename given as third argument
    f = open(sys.argv[3], "w", encoding="UTF-8") #encoding allows writing the unicode to the text file
    f.write(box+"\n")
    f.close()

Bonus round

Work on this more challenging exercise if you finish the rest of the worksheet before discussion ends. The solutions to the worksheet won't include solutions for the bonus problem, but it's still good practice!

11. Digits to word lengths

Consider this process: Start with a positive integer like

2781

Write out each digit as an english word:

two, seven, eight, one

Then replace each word with its number of letters (e.g. "two" has 3 letters, "seven" as 5, etc.):

3 5 5 3

Use these numbers as digits of a new integer

3553

Finally, take the average of the new integer and the old one, and if the result is not an integer, round it down to the nearest integer:

(2781 + 3553) // 2 = 3167 (no rounding happened in this case)

(Why are there two slashes there? That's how you perform integer division.)

Write a Python function dwl(n) that takes an integer n and returns the integer that results from this process. So, for example we have:

In [3]:
dwl(2781)
Out[3]:
3167

Now, consider what happens when you apply this function over and over again, e.g. start with n, compute dwl(n), then dwl(dwl(n)), then dwl(dwl(dwl(n))), etc., to get a sequence. For 2781 this sequence looks like:

2781, 3167, 4251, 4297, 4321, 4427, 4431, 4442, 4442, 4442, 4442, ...

It continues with 4442 repeating forever.

That's what happened when starting with 2781, but what happens when you do the same thing but start with a different value of n, like 7847? Does it always seem to end up repeating some number over and over again?

Write a program to explore this more systematically, for example by searching for a repeating pattern (or lack thereof) for every 4-digit starting number.

Solution

In [15]:
#define a dictionary to convert a digit to its spelling

num2words1 = {0: 'Zero', 1: 'One', 2: 'Two', 3: 'Three', 4: 'Four', 5: 'Five', 6: 'Six', 7: 'Seven', 8: 'Eight', 9: 'Nine'}

def dwl(n):
    "takes and integer and outputs the average of the original integer and an integer based on word lengths"

    #write out digits and replace each word with the length of the word
    numlist = [int(d) for d in str(n)]
    #create a new number with the lengths
    newnum = int("".join([str(len(num2words1[d])) for d in numlist]))
    
    # alternative way to compute newnum, same output
    #newnum = sum(len(num2words1[d]) * 10**i for i, d in enumerate(numlist[::-1]))
    
    avg = (newnum + n) // 2
    return avg

def findpattern(n):
    "takes an integer and returns the pattern from applying dwl() many times"
    
    visited = [n]
    next = dwl(n)
    while next not in visited:
        visited.append(next)
        next = dwl(next)
    visited.append(next)
    return visited

#to avoid too much text in the solutions document, let's only check multiples of 500
check_start = 1000
check_end = 9999
check_step = 500

#uncomment these to check all 4-digit numbers
#check_start = 1000
#check_end = 9999
#check_step = 1

for k in range(check_start,check_end+1,check_step):
    print("Applying dwl to {} reaches a repeating pattern after {} steps".format(k,len(findpattern(k))))
Applying dwl to 1000 reaches a repeating pattern after 12 steps
Applying dwl to 1500 reaches a repeating pattern after 11 steps
Applying dwl to 2000 reaches a repeating pattern after 11 steps
Applying dwl to 2500 reaches a repeating pattern after 10 steps
Applying dwl to 3000 reaches a repeating pattern after 9 steps
Applying dwl to 3500 reaches a repeating pattern after 7 steps
Applying dwl to 4000 reaches a repeating pattern after 9 steps
Applying dwl to 4500 reaches a repeating pattern after 7 steps
Applying dwl to 5000 reaches a repeating pattern after 10 steps
Applying dwl to 5500 reaches a repeating pattern after 10 steps
Applying dwl to 6000 reaches a repeating pattern after 10 steps
Applying dwl to 6500 reaches a repeating pattern after 10 steps
Applying dwl to 7000 reaches a repeating pattern after 11 steps
Applying dwl to 7500 reaches a repeating pattern after 11 steps
Applying dwl to 8000 reaches a repeating pattern after 12 steps
Applying dwl to 8500 reaches a repeating pattern after 12 steps
Applying dwl to 9000 reaches a repeating pattern after 12 steps
Applying dwl to 9500 reaches a repeating pattern after 12 steps