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

MCS 260 Fall 2021 Worksheet 6 Solutions

  • Course instructor: Emily Dumas
  • Solutions prepared by: Kylash Viswanathan and Emily Dumas

Topics

This worksheet focuses on:

  • Command line arguments
  • Opening, Reading and writing files
  • String formatting

Instructions

  • Work on the exercises below during lab. Complete them afterward if you don't finish during the lab period.
  • This worksheet will prepare you for the upcoming homework assignment.
  • Most of the exercises ask you to write Python scripts to accomplish a certain task. We recommend making a folder (with a name like "worksheet6") to store the scripts you prepare today.
  • Seek assistance from your TA or from fellow students if you run into trouble.

Collaboration

Collaboration on worksheets is strongly encouraged.

Resources

The main course materials 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. Unlimited MWDPS using command line args

A solution to Project 1 was recently posted. Starting from your own solution or the posted one, make a new program mwdps2.py that computes the MWDP sequence but has the following differences in its behavior:

  1. Instead of asking for the starting number using input(), it should expect the starting number to be given as a command line argument. (If no argument is given, it should notice this and print a usage message.)
  2. Instead of stopping after 100 iterations, this program should keep computing terms until a loop is found, no matter how long it takes.
  3. Instead of printing "looped" on the last line, it should print a more detailed message in the following format:
    Looped after 171 terms: Term #171 and term #138 are both equal to 76.

Here's what running the program in the terminal should look like:

PS C:\Users\ddumas> python3 mwdps2.py
Usage: mwdps2.py N
Computes the MWDP sequence starting with integer N.
PS C:\Users\ddumas> python3 mwdps2.py 47
47
48
56
46
41
28
46
Looped after 7 terms: Term #7 and term #4 are both equal to 46.
PS C:\Users\ddumas> python3 mwdps2.py 8 
8
8
Looped after 2 terms: Term #2 and term #1 are both equal to 8.
PS C:\Users\ddumas>

Notice that the program exits when it doesn't receive an integer as a command line argument. Also, the "Looped" message refers to terms using 1-based indices (#1 means the first term, i.e. the command line argument).

Solution

In [ ]:
# Contents of mwdps2.py
"""
Find the periodic cycle that the mean with digit power sequence
starting from a given integer ends up in.  Print terms up to the 
first duplicate, followed by a description of the duplicates.
"""
import sys

def mwdp(x):
    """For an integer x, return the integer mean
    of x and d**k where d is the largest decimal
    digit of x and k is the number of decimal digits
    of x.  This is the _mean with digit power_."""
    digits = [int(c) for c in str(x)]
    maxdigit = max(digits)
    numdigits = len(digits)
    return (x + maxdigit ** numdigits) // 2

if len(sys.argv) < 2:
    print("Usage: {} N\nComputes the MWDP sequence starting with integer N.".format(sys.argv[0]))
    exit(1)

n = int(sys.argv[1])
sequence = [n] # terms of the MWDP sequence computed so far
loop_detected = False

while True:
    print(sequence[-1])
    if sequence[-1] in sequence[:-1]:
        # The last term also appeared somewhere else
        # which means we've found a "loop"
        break
    next_term = mwdp(sequence[-1])
    sequence.append(next_term)

print("Looped after {} terms: Term #{} and term #{} are both equal to {}.".format(
    len(sequence),
    len(sequence),
    sequence.index(sequence[-1])+1, # .index always reports *first* place item is found
    sequence[-1]
))

2. protogrep

Write a program called protogrep.py that searches for all occurrences of a word in a given text file.

Specifically, protogrep.py should accept two command line arguments. The first argument (stored in sys.argv[1]) is the word that the program is going to search for (e.g. "apology"); we'll call this the search term. The second argument (sys.argv[2]) is the name of an existing text file.

The program should then open the specified file, read it line by line, and check whether each line contains the search term. Lines that contain the search term should be printed, and all other lines should be ignored.

For example, the text file version of Jane Austen's Pride and Prejudice we used as an example in lecture (available here) has 8 lines that contain apology, and the program could be asked to print them with:

PS C:\Users\ddumas> python3 protogrep.py apology pride.txt
      to Mr. Bingley for his kindness to Jane, with an apology for
      connection must plead my apology.”
      all, and saw in the motion of his lips the words “apology,”
      sometimes returned to Longbourn only in time to make an apology
      pleasure in it; she made a slight, formal apology, for not
      obeyed, and further apology would be absurd.
      to say, no other apology to offer. If I have wounded your
      understand that he believed any apology to be in his power; and
PS C:\Users\ddumas>

Similarly, the text file of Charles Dickens Great Expectations (available here) has only one line containing the word "abundance":

PS C:\Users\ddumas> python3 protogrep.py abundance expectations.txt
The early dinner hour at Joe’s, left me abundance of time, without
PS C:\Users\ddumas>

Both of the examples shown here assume that the relevant text file has been saved in the same directory as the script protogrep.py. If it is located somewhere else, you'd need to specify the path on the command line, e.g.

PS C:\Users\ddumas\Desktop\MCS 260\worksheet 6> python3 protogrep.py abundance C:\Users\ddumas\Downloads\expectations.txt

You don't need to do any fancy word splitting in this exercise; it's fine to just test whether the search term appears in the string (so e.g. search term plan would match a line I listened carefully to his explanation.)

(Why the weird program name? There is a popular terminal utility called grep that searches for text in files. Because this exercise involves writing a simple Python script that does something similar, but is more basic, we call it "proto-grep".)

Solution

In [ ]:
# Contents of protogrep.py
"""
Search for a given string in the lines of a text file
"""
import sys

if len(sys.argv) < 3:
    print("Usage: {} SEARCHTERM FILENAME\n".format(sys.argv[0]))
    exit(1)

searchterm = sys.argv[1]
fn = sys.argv[2]

infile = open(fn,"r",encoding="UTF-8")
for line in infile:
    if searchterm in line:
        print(line,end="")
infile.close()

3. Coin flip report

Write a program that takes two command line arguments: a number N of times to flip a coin, and a filename to which a log of coin flips should be written.

The program should open the given filename for writing and then simulate flipping a coin that has an equal probability of landing on 'heads' or 'tails'. After each coin flip, the program so should write one line of text to the file, which contains both the latest result and the percentage of heads and tails in all the coin flips thus far (with two decimal points).

For example, running the program as

PS C:\Users\ddumas> python3 coinflips.py 18 flips.txt
PS C:\Users\ddumas>

should produce no visible output, but write 18 lines of text to flips.txt. The contents should look something like this:

tails (so far: 0.00% heads, 100.00% tails)
tails (so far: 0.00% heads, 100.00% tails)
heads (so far: 33.33% heads, 66.67% tails)
heads (so far: 50.00% heads, 50.00% tails)
tails (so far: 40.00% heads, 60.00% tails)
tails (so far: 33.33% heads, 66.67% tails)
heads (so far: 42.86% heads, 57.14% tails)
tails (so far: 37.50% heads, 62.50% tails)
heads (so far: 44.44% heads, 55.56% tails)
heads (so far: 50.00% heads, 50.00% tails)
tails (so far: 45.45% heads, 54.55% tails)
tails (so far: 41.67% heads, 58.33% tails)
tails (so far: 38.46% heads, 61.54% tails)
tails (so far: 35.71% heads, 64.29% tails)
tails (so far: 33.33% heads, 66.67% tails)
tails (so far: 31.25% heads, 68.75% tails)
tails (so far: 29.41% heads, 70.59% tails)
tails (so far: 27.78% heads, 72.22% tails)

Hint: You'll want to use the random module that we discussed in Lecture 11 and which also appeared in some of the problems on Worksheet 5.

Solution

In [ ]:
# Contents of coinflips.py
"""
Flip a coin multiple times and print report about results to a file
"""
import sys
import random

if len(sys.argv) < 3:
    print("Usage: {} NFLIPS OUTFILE\n".format(sys.argv[0]))
    exit(1)

N = int(sys.argv[1])
fn = sys.argv[2]

outfile = open(fn,"w",encoding="UTF-8")
nhead = 0
for i in range(N):
    flip = random.choice(["heads","tails"])
    if flip == "heads":
        nhead = nhead + 1
    pct_heads = 100*nhead/(i+1)  # i+1 is number of flips so far
    outfile.write("{} (so far: {:.2f}% heads, {:.2f}% tails)\n".format(
        flip,
        pct_heads,
        100-pct_heads  # percent tails
    ))
outfile.close()

Revision history

  • 2021-09-30 Initial release