CS50 Week 7: Walkthroughs

Hello name in python to start it off:

print(f"Hello {input('name: ')}")

Let’s quickly run through the “tougher” Déjà vu walkthroughs ( and then go deeper into the final similarities web app. Let’s start with the Mario challenge:

mario.py

So let’s build a pyramid shape in the terminal:

height = int(input("enter a number for your mario pyramid: "))
for i in range(2, height+2):
    print(('#'*i).rjust(height+1), end=" ")
    print('#' * i)

4 lines.

Google range() and it’s arguments, then google rjust() and it’s arguments. Use print statements to figure out what I did. If you have any questions try to say what this is doing out loud with a small number (like 2 for the user input). Comment below if you have any questions.

cash.py

cash = float(input("please enter cash amount to break down into coins (example 1.25): "))
coins = {"quarters": .25,"dimes":.10,"nickels":.05,"pennies":.01}
for key, coin in coins.items():
	(coins[key], cash) = (divmod(cash,coin)[0], round(divmod(cash,coin)[1],2))
print(coins)

Short but not impossible to read. Pull apart the packed variables and google divmod(), which is a very handy function in this case. Go back to my python article that goes through packing and unpacking if you need a refresher.

Caesar.py

import sys

if len(sys.argv) != 2: 
  print("Usage: python3 caesar.py key")
  sys.exit(0)
key = int(sys.argv[1])

message = input("enter a secret message: ")

def cipher(letter, key):
	if not letter.isalpha():
		return letter
	offset = 65 if letter.isupper() else 97
	return chr(((ord(letter) - offset) + key) % 26 + offset)

print(f"userinput: {message}")
print("decoded_text: " + ''.join([cipher(letter, key * -1) for letter in message]))
print("coded_text: " + ''.join([cipher(letter, key) for letter in message]))

Similarities (from scratch)

I’m going to do one from scratch then i’ll make it again using the CS50 instructions.

Here is the example image from the CS50 website:

Here was my result. You can check out the github repo as well. I styled the pages differently and added a number input for the substring page:

application.py:

from flask import Flask, render_template, redirect, request
from compare import compare_lines, compare_sentences, compare_substrings

app = Flask(__name__)

@app.route("/", methods=["GET","POST"])
def index():
    if request.method == "POST":
        file_one = request.files["file_one"]
        file_two = request.files["file_two"]
        return render_template("index.html", results=compare_lines(file_one,file_two))
    return render_template("index.html", results=None)



@app.route("/sentences", methods=["GET","POST"])
def sentences():
    if request.method == "POST":
        file_one = request.files["file_one"]
        file_two = request.files["file_two"]
        return render_template("sentences.html", results=compare_sentences(file_one,file_two))
    return render_template("sentences.html", results=None)



@app.route("/substrings", methods=["GET","POST"])
def substrings():
    if request.method == "POST":
        file_one = request.files["file_one"]
        file_two = request.files["file_two"]
        return render_template("substrings.html", results=compare_substrings(file_one,file_two, int(request.form.get('length'))))
    return render_template("substrings.html", results=None)


if __name__ == '__main__':
    app.run

The index route goes straight to the matching lines page. All forms send a POST request to the same URL, so i’m checking for the request type using the IF statement. At the top i’ve imported my own file compare, which has the functions I need to process the files that are being uploaded. Also notice to get the files I use request.files and then pass in the form’s name value. This will return what flask calls a filestorage object. This thin wrapper allows us to call read() which is still in a format that is not decoded because it doesn’t assume the file is text.

Here is the compare.py file so you can see the function I use to get the text from each file:

from nltk.tokenize import sent_tokenize, word_tokenize
def get_file_data(file_one, file_two):
#takes filestorage type of bytes and turns file data it into a string
data = [file_one.read().decode('utf-8'), file_two.read().decode('utf-8')]
data = [file.replace('<', '&lt;').replace('>', '&gt;') for file in data]
return data
def get_similarities(file_one, file_two):
return set(file_one).intersection(file_two)
def format_html(file_one, file_two, similarities):
#adds span tags and \n re-added if identical lines otherwise just the line with \n re-added
similarities = list(similarities)
files = []
for item in similarities:
file_one = file_one.replace(item, f"<span>{item}</span>", 1)
file_two = file_two.replace(item, f"<span>{item}</span>", 1)
files.append(file_one)
files.append(file_two)
return files
def set_files_as_list_of_strings(formatted_files):
return [''.join(formatted_files[0]), ''.join(formatted_files[1])]
def compare_lines(file_one, file_two):
data = get_file_data(file_one, file_two)
#split string into list of strings wherever a linebreak is found
file_one = data[0].split('\n')
file_two = data[1].split('\n')
#set intersection returns a set of all identical lines between the lists
similarities = get_similarities(file_one, file_two)
#adds span tags and \n re-added if identical lines otherwise just the line with \n re-added
formatted_files = format_html(data[0], data[1], similarities)
#return as a list of 2 strings
return set_files_as_list_of_strings(formatted_files)
def compare_sentences(file_one, file_two):
data = get_file_data(file_one, file_two)
#TO DO splits data up into sentences
file_one = sent_tokenize(data[0])
file_two = sent_tokenize(data[1])
#set intersection returns a set of all identical sentences between the lists
similarities = get_similarities(file_one, file_two)
#adds span tags and \n re-added if identical sentence otherwise just the sentence with \n re-added
formatted_files = format_html(data[0], data[1], similarities)
#return as a list of 2 strings
return set_files_as_list_of_strings(formatted_files)
def compare_substrings(file_one, file_two, length):
data = get_file_data(file_one, file_two)
#split string into list of substrings based on user input
file_one = [char for item in word_tokenize(data[0]) for char in item]
file_two = [char for item in word_tokenize(data[1]) for char in item]
slices_one = []
for i in range(0,len(file_one), length):
if len(file_one[i:i+length]) == length and ''.join(file_one[i:i+length]).isalpha():
slices_one.append(''.join(file_one[i:i+length]))
slices_two = []
for i in range(0,len(file_two), length):
if len(file_two[i:i+length]) == length and ''.join(file_one[i:i+length]).isalpha():
slices_two.append(''.join(file_two[i:i+length]))
#set intersection returns a set of all identical substrings between the lists
similarities = get_similarities(slices_one, slices_two)
print(similarities)
list_of_substrings = ['\n'.join(similarities), '\n'.join(similarities)]
#return as a list of 2 strings
return set_files_as_list_of_strings(list_of_substrings)

On line 5 you can see where I call read() then decode(‘utf-8’) to get the files actual text.

When the user posts the data we’re not able to immediately get to the text inside the submitted files. My goal is for our data variable to just hold the file text as a string.

Using read() with no argument, I save all the form data to memory until EOF. This goes back to the days we were messing with file handlers in C. That returns a byte object. In python a byte object represents sequences of integers which are usually ascii characters. We can use the decode method to get back a string. I’ve passed ‘utf-8’ as an argument, however it actually decodes to this by default too, so decode() would work just as well.

Once I get the files as strings I can start working on formatting the data. If you look at where it ends up in html you can see i’ve wrapped the text in <pre></pre> tags. Let’s look at templates/base.html on line 110:

<!DOCTYPE html>
<html>
<head>
{% block head %}
<title>Similarities{% block title %}{% endblock %}</title>
<style>
{% block background %}
body{
background: #606c88;  /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #3f4c6b, #606c88);  /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #3f4c6b, #606c88); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
{% endblock %}
h1{
line-height: 43px;
}
table, form{
border-radius: 7px;
box-shadow: 0px 12px 20px 1px;
background: white;
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
margin-top: 47px;
font-size: 19px;
line-height: 27px;
width: 89%;
}
form{
padding: 2px 0px 21px 39px;
}
input{
font-size: 16px;
margin: -14px;
}
table, th, td {
border: 1px solid #191e2a;
}
th, td {
padding: 15px;
text-align: left;
}
span{
background-color: yellow;
}
li a{
text-decoration: none;
padding: 16px;
color: white;
font-size: 27px;
}
li{
flex: 3;
text-align: center;
}
ul{
display: flex;
list-style-type: none;
margin-top: 30px;
}
menu{
padding-left: 0px;
}
pre{
white-space: pre-wrap;
}
td{
min-width: 45vw;
}
</style>
{% endblock %}
</head>
<body>
<menu>
<ul>
<li><a href="/">Similar Lines</a></li>
<li><a href="/sentences">Similar Sentences</a></li>
<li><a href="/substrings">Similar Substrings</a></li>
</ul>
</menu>
{% block content %}
<form action={{ request.path }} method="post" enctype = "multipart/form-data">
<h1>Upload two files to highlight similarities{% block h1 %}{% endblock %}</h1>
<input type="file" name="file_one" id="file_one"><br>
<input type="file" name="file_two" id="file_two"><br>
{% block numberInput %}{% endblock %}
<input type="submit" value="Submit">
</form>
<table>
<tr>
<th>
Code Group One
</th>
<th>
Code Group Two
</th>
<tr>
<td id="groupone">
<pre>
{% block results_one %}{% endblock %}
</pre>
</td>
<td id="grouptwo">
<pre>
{% block results_two %}{% endblock %}
</pre>
</td>
</tr>
</table>
{% endblock %}
</body>
</html>

Using jinja2 templating I can create a block that holds results_one and on line 116 I have another <pre> </pre> with results_two inside. Now let’s look at index.html which is the first page of the website, where I compare lines:

{% extends "base.html" %}
{% block title %} - Lines{% endblock %}
{% block background %}
body{
background-image: linear-gradient(to right, #ab7272 0%, GRAY 280%);
}
{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block h1 %} - matching lines{% endblock %}
{% block results_one %}{{results[0]|safe}}{% endblock %}
{% block results_two %}{{results[1]|safe}}{% endblock %}
{% block content %}
{{ super() }}
{% endblock %}

This is why jinja2 is so powerful. My base.html is 123 lines of code. My index.html, sentences.html and substrings.html pages are less than 20! If I have to make a change on all the webpages, base.html will be the one place to make it.

Using {% extends “base.html” %} allows me to reuse all the code and slip in custom variables by defining their values on each individual page. Look at line 14:

{% block results_two %}{{results[1]|safe}}{% endblock %}

I pass in the results from my compare function in application.py. Let me make a diagram so it’s easier to understand the flow:

This is generally what is happening. Things are being passed to do some processing work, but always end up back in application.py so the flask framework can do the rest for us.

I’m still learning as I go, and the tough part of this is all the different languages working together…the html, css, server calls, url requests and python code. Each is a journey on it’s own and don’t be discouraged if this made little sense. CS50 doesn’t cover html, or css very much so if you’re up to it check out teamtreehouse for video courses you can take on those topics.

Finishing the actual challenges

Back to similaries less, which is a much simpler challenge. We don’t have to make the flask app from scratch. Just update the helpers.py and index.html files with functions and a form. Here is what I ended up doing:

My drop down has a nifty javascript function inside it that will hide or show the numbers field if substring is selected. Also when the user hits the back button it resets the form using the script at the bottom.

So remember, a lot of these features i’ve added is not because i’m some all powerful genius. I am just not afraid to ask myself “what do I want this to do?” followed by googling it and not being too proud to use code from online. In these cases, if it works, great! The script at the bottom is straight from a stack overflow answer. I also forgot how to inline javascript in html so a quick google and you can see onchange on line 14 that goes into my javascript for changing the style of an element (which I also googled because I forgot for the 100th time, which is OK)

There is too much to remember, and too much changing in the world of programming, be a hunter of code examples and understand fundamentals to get things done and not get stuck with half finished projects.

Moving on to week 8!

Leave a Reply

avatar
  Subscribe  
Notify of