Skip to content

Latest commit

 

History

History
1466 lines (1053 loc) · 36.7 KB

lecture6.md

File metadata and controls

1466 lines (1053 loc) · 36.7 KB

CS50x Week 6 - Python

In the previous weeks, we learned the fundamentals of programming in a low-level programming language called C. Python is a programming language that will build on top of what we learned with C.

Python is a high level interpreter language, where you need to separately compile your program. Instead, you run your program in the Python Interpreter.


Compiling

To compile our programs in C we used the make command. Which had to be predefined to tell the clang compiler which steps to follow.

make hello      -> Compile

./hello         -> Run



Manually compiling C code without make:

clang -o hello hello.c -lcs50

./hello



Compiling Python code is done by running a program called python (Python Interpreter). The interpreter reads your code and follows the instructions:

python hello.py



Hello

In C code:

#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
}

In Python code:

print("hello, world")
  • Libraries like <stdio.h> are no longer needed.
  • No need for the main() function to tell the compiler what the main part of our program is.
  • No need for extra syntax like the semicolon ;.
  • No need for the new line \n to move the cursor to the next line.



Speller

To illustrate this simplicity, let's re-write last week's Speller problem code with Python:

# Words in dictionary
words = set()

def check(word):
    """ return true if word is in dictionary else false"""
    return word.lower() in words

def load(dictionary):
    """Load dictionary into memory, returning true if successful else false"""
    with open(dictionary) as file:
        words.update(file.read().splitlines())
    return True

def size():
    """Returns number of words in dictionary if loaded else 0 if not yet loaded"""
    return len(words)

def unload():
    """Unloads dictionary from memory, returning true if successful else false"""
    return True
  • check() simply returns True if a word is in the words set.

  • load() dictionary file is opened. We split each line in that file and add it to words.

  • size() simply returns len or length of words.

  • unload() returns True because Python handles memory management on its own.



High level languages like Python exist to simplify writing code. However, speed is a tradeoff. C allows you to make decisions about memory management, and it may run faster than Python.

While C strictly follows instructions, running the code line by line, *Python, when using built-in functions, also runs additional processes in the background, simplifying usage but potentially reducing speed.



Filter

To further illustrate this simplicity, let's re-write the blur() function of the Filter problem with Python:

# Blurs an image

from PIL import Image, ImageFilter

before = Image.open("bridge.bmp")
after = before.filter(ImageFilter.BoxBlur(10))
after.save("out.bmp")
  • This program takes an input file bridge.bmp and creates modified output file out,bmp.
  • To modify the image, it uses imported modules Image and ImageFilter from a library called PIL.



Now, let's implement edges(), a function that finds the edges of an image:

# Finds edges in an image

from PIL import Image, ImageFilter

before = Image.open("bridge.bmp")
after = before.filter(ImageFilter.FIND_EDGES)
after.save("out.bmp")
  • Notice that this code is a small adjustment to the blur code, but produces a dramatically different result.



Finally, we can even write a face detection program fairly simply:

# Find faces in picture
# https://github.com/ageitgey/face_recognition/blob/master/examples/find_faces_in_picture.py

from PIL import Image
import face_recognition

# Load the jpg file into a numpy array
image = face_recognition.load_image_file("office.jpg")

# Find all the faces in the image using the default HOG-based model.
# This method is fairly accurate, but not as accurate as the CNN model and not GPU accelerated.
# See also: find_faces_in_picture_cnn.py
face_locations = face_recognition.face_locations(image)

for face_location in face_locations:

    # Print the location of each face in this image
    top, right, bottom, left = face_location

    # You can access the actual face itself like this:
    face_image = image[top:bottom, left:right]
    pil_image = Image.fromarray(face_image)
    pil_image.show()
  • This program uses a third-party library face_recognition. This is enabled by running pip install face_recognition in the terminal window.

  • With libraries we can leverage advanced functionalities with simple function calls, greatly simplifying coding efforts.


Note

Python allows you to abstract away programming that would be much more complicated within C and other lower-level programming languages.



CS50 Library in Python

In Python when importing functions from libraries, we an specify which function or group of functions to import.

from CS50 import get_float, get_int, get_string



Strings

get_string() in C:

// get_string and printf with %s

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    char *answer = get_string("What is you name? ");
    printf("hello, %s\n", answer);
}



get_string in Python:

# get_string and print, with concatenation

from cs50 import get_string

answer = get_string("What's your name? ")
print("hello, " + answer)
  • Notice that the variable types are no longer needed.
  • The + sign concatenates "hello, " and answer



Alternative syntax:

# get_string and print, with format strings

from cs50 import get_string

answer = get_string("What's your name? ")
print("hello,", answer)
  • Notice we did not include a space between "hello," and answer. By separating them with a , we tell python that they are two different arguments and it knows to automatically put a space between them.



Alternative syntax:

# get_string and print, with format strings

from cs50 import get_string

answer = get_string("What's your name? ")
print(f"hello, {answer}")
  • This method uses a format string. When we prefix a string in Python with an f, we can use curly braces {} to interpolate the value inside of them. {} acts as a placeholder.



In Python, especially when dealing with string concatenation and formatting, there are several ways to achieve the same output. Each method has its use cases and choosing one can also depend on convention and style. F-strings are generally preferred for their clarity and efficiency.

# print("hello, " + answer)
# print("hello,", answer)
print(f"hello, {answer}")



input()

In C programming, to simplify the process of prompting the user for a string input, we used the get_string function from the cs50.h library:

from cs50 import get_string

answer = get_string("What's your name? ")
print(f"hello, {answer}")

In Python, there is a built-in function called input():

answer = input("What's your name? ")
print(f"hello, {answer}")
  • Notice that we did not need to include any libraries.



Variables

C is a statically typed language. This means that when declaring a variable we need to include the type to inform the compiler about the amount of memory to allocate and how to interpret the data:

int counter = 0;

Python is a dynamically typed language. Variables are declared the moment we assign a value to them. It is not needed to specify the type either, since it can change as the variable is reassigned a value of a different data type:

counter = 0



Incrementing a variable

To increment a variable by 1 in C, we used the following syntax:

// counter = counter + 1;
// counter += 1;

counter++;

In Python the counter++; syntax is not used:

# counter = counter + 1

counter += 1



Types

In C, variables require explicit type declarations. Commonly used data types include:

bool    // Boolean values (true or false)

char    // Single character

double  // Double-precision floating-point number 

float   // Single-precision floating-point number

int     // Integer

long    // Large integer

string  // Array of characters



Data Types in Python do not need to be explicitly declared. Commonly used data types include:

bool    # Boolean values (true or false)

float   # Floating-point number (covers both *float* and *double*)

int     # Integer (automatically scales to *long* if needed)

str     # String of characters

Note

Notice that long and double are missing. Python automatically handles adjustments during runtime. including what data type should be used for larger and smaller numbers.



Some other data types in Python include:

range   # Generates a sequence of number

list    # A mutable, ordered sequence of elements

tuple   # An immutable, ordered sequence of elements

dict    # A collection of key-value pairs, with unique keys

set     # An unordered collection of unique elements



Calculator

Let's implement two versions of a simple calculator. The first one in C, the second in Python.

Addition with int in C:

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // Prompt user for x and y
    int x = get_int("x: ");
    int y = get_int("y: ");

    // Print addition
    printf("%i\n", x + y);
}



Addition with int (using get_int) in Python:

from cs50 import get_int

# Prompt user for x and y
x = get_int("x: ")
y = get_int("y: ")

# print addition
print(x + y)
  • Notice that the data type int, semicolons ; and main function main() are gone.



Using input() instead of CS50's get_int:

# Prompt user for x and y 
x = input("x: ") # suppose 1
y = input("y: ") # suppose 2

# Print addition
print(x + y)
12

Caution

Notice how executing the code above returns 12 instead of 3. The interpreter understood x and y to be strings and it is performing concatenation instead of addition.



int() function:

x = int(input("x: "))
y = int(input("x: "))

print(x + y)
  • Notice how the input x and y is passed to the int() function which converts it into an integer. Now, instead of concatenating the interpreter will know to add both integers.



library.function()

import cs50

x = cs50.get_int("x: ")
y = cs50.get_int("x: ")

print(x + y)
  • Imagine we imported two identically named functions from different libraries in our program. In Python we can specify the library in the function call cs50.get_int to specify which function we want to use.



Conditionals

Let's compare different conditional statements in C and Python:

// C

if (x < y)
{
    printf("x is less than y\n");
}
# Python

if x < y:
    print("x is less than y")
  • The () around the conditional (x < y) are not needed in Python (Can be used if needed, but not mandatory).

  • No need for curly braces {}. In Python, a colon : and indentation are used to define blocks.

  • The semicolon ; and the new line separator \n are also omitted.



// C

if (x < y)
{
    printf("x is less than y\n");
}
else
{
    printf("x is not less than y\n");
}
# Python

if x < y:
    print("x is less than y")
else:
    print("x is not less than y")
  • Again, for the else statement in Python, curly braces {} and semicolons ; are gone. Colons : and indentation are used to define blocks.



// C

if (x < y)
{
    printf("x is less than y\n");
}
else if (x > y)
{
    printf("x is greater than y\n");
}
else
{
    printf("x is equal to y\n");
}
# Python

if x < y:
    print("x is less than y")
elif:
    print("x is greater than y")
else:
    print("x is equal to y")
  • In Python else if is shortened to elif to save extra typing.



Now, let's see how we can integrate these conditionals in C and Python programs:

// Conditionals in C

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // Prompt user for integers
    int x = get_int("What's x? ");
    int y = get_int("What's y? ");

    // Compare integers
    if (x < y)
    {
        printf("x is less than y\n");
    }
    else if (x > y)
    {
        printf("x is greater than y\n");
    }
    else
    {
        printf("x is equal to y\n");
    }
}

# Conditionals in Python

from cs50 import get_int

x = get_int("What's x? ")
y = get_int("What's y? ")

if x < y:
    print("x is less than y")
elif x > y:
    print("x is greater than y")
else:
    print("x is equal to y")



Comparing strings str:

# Comparing strings in Python

s = input("s: ")
t = input("t: ")

if s == t:
    print("Same")
else
    print("Different")
  • Remember that in C, strings char * are pointers to the memory address holding the first character of the string.

  • Attempting to compare two strings in C with the equality operator == will always return different. This is because two different strings point to two different addresses.

  • In Python however, comparing strings using the equality operator == works like it would with integers.



Further looking at comparisons, let's consider the following C code:

// Logical operators in C

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // Prompt user to agree
    char c = get_char("Do you agree? ");

    // Check whether agreed
    if (c == 'Y' || c == 'y')
    {
        printf("Agreed.\n");
    }
    else if (c == 'N' || c == 'n')
    {
        printf("Not agreed.\n");
    }
}
  • The program above prompts the user to agree by typing Y or y or disagree by typing N or n.



# Logical operators in Python

from cs50 import get_string

# Prompt user to agree
s = get_string("Do you agree? ")

# Check whether agreed
if s == "Y" or s == "y":
    print("Agreed.")
elif s == "N" or s == "n":
    print("Not agreed.")
  • Notice that the two vertical bars || in C are replaced with or.



Imagine the user typing Yes instead of Y or y. In Python we can use a list to express multiple keywords:

s = input("Do you agree? ")

if s in ["y", "yes"]:
    print("Agreed")
elif s in ["n", "no"]:
    print("Not Agreed")
  • If the string s is included in the list ["y", "yes"], the program prints Agreed.

  • If the string s is included in the list ["n", "no"], the program prints Not Agreed.



We introduced a list as a potential solution to include different user inputs into our conditionals, but this is still not the most efficient solution. To attempt to include every potential user input we will have to create extensive lists ["y", "Y", "yes", "YES", "Yes", "YeS", ...].



Object-Oriented Programming

OOP allows variables to not only have values with certain properties or attributes, but also have functions built into them. In Python they are known as objects.

In C we had the ability to create a struct where we could associate multiple variables inside a single self-created data type. In Python we can include functions in a self-created data type called methods.


Strings str in Python have built-in methods:

# Prompt user to agree
s = input("Do you agree? ").lower()

# Check whether agreed
if s in ["y", "yes"]:
    print("Agreed.")
elif s in ["n", "no"]:
    print("Not agreed.")
  • The code takes the user input and immediately applies the .lower() method to convert it to lowercase.

  • This ensures that no matter what is the capitalization of the user input, it will be treated the same.



Loops

Loops in Python are very similar to C, but require less setup and manual control of iteration variables and conditions.


While Loops

// While loop in C

int i = 0;
while (i < 3)
{
    printf("meow\n");
    i++;
}

# While loop in Python

i = 0
while i < 3:
    print("meow")
    i += 1
  • Notice that the Python while loop is more concise.



For Loops

// For loop in C

for (int i = 0; i < 3; i++)
{
    printf("meow\n");
}

# For loop in Python

for i in [0, 1, 2]:
    print("meow")
  • The code above works but it's not efficient, particularly as the list increases.

# For loop in Python - Alternative syntax

for i in range(3):
    print("meow")
  • This for loop is mode efficient. The range() function returns a range of values as they are needed. To change the number of values we can simply change the argument to range().

Important

Notice that i is never explicitly used. In Python we do not have to manage the iteration variable i, it is incremented automatically. It is common practice to use placeholder underscore _ as an iteration variable, signaling that the focus is in the code's operation, enhancing clarity and maintainability.

# For loop in Python - Alternative syntax

for _ in range(3):
    print("meow")



Forever Loops

// Forever loop in C

while (true)
{
    printf("meow\n");
}

# Forever loop in Python

while True
    print("meow")
  • In Python True and False are capitalized.



uppercase.py

To further our understanding of loops and iteration in Python, let's implement a program to uppercase characters:

# Uppercase string one character at a time

before = input("Before: ")
print("After: ")
for c in before:
    print(c.upper())
Before: cat
After:
C
A
T
  • Notice that the capitalization is achieved, but there are a couple of problems. The After: string prints in a separate line followed by the capitalized characters are printed one per line.

  • Unlike C, Python handles creating a new line automatically, there is no need to add the \n. However, sometimes, like our example, it could not be desirable.



To fix the this code we have to introduce named parameters:

# Uppercase string one character at a time

before = input("Before: ")
print("After: ", end="")
for c in before:
    print(c.upper(), end="")
Before: cat
After: CAT$
  • Python uses an abstracted process to automatically move to the next line at the end of every line using the default character "\n" (end="\n").

  • To override that and prevent the program from jumping to the next line, we can modify the named character end=, and set it equal to nothing end="".

  • Also notice that the cursor $ was not moved to the next line.



We can move the cursor to the next line by adding an extra empty print() line:

# Uppercase string one character at a time

before = input("Before: ")
print("After: ", end="")
for c in before:
    print(c.upper(), end="")
print()
Before: cat
After: CAT
$
  • With the version above, we achieved the desired outcome of the program, but some of the steps we implemented are not necessary.



In Python, strings str come with built-in methods, one of them is upper(). We do not need to loop through and call upper() on every character of the string. Instead we can call it on the entire string before.upper():


# Uppercase string all at once

before = input("Before: ")
print("After: ", end="")
print(before.upper())



We can optimize the design and logic of the code by using a formatted string f-string:

# Alternative syntax
before = input("Before: ")
after = before.upper()
print(f"After: {after}")
  • We created a variable called after, which is the uppercase version of the before variable.

  • In the print statement, we created a f-string composed of a string and an embedded variable {after}.



We can achieve the same result and make the code even more concise by removing the need to create the after variable and instead embedding the expression before.upper() between the formatted string's curly braces:

# Alternative syntax
before = input("Before: ")
print(f"After: {before.upper()}")



Abstraction

Like in C we can define our own functions using the def keyword. This wil allow us to abstract away the code when calling the functions.

# Abstraction

def main():
    for i in range(3):
        print("meow")

# Meow once
def meow():
    print("meow")


main()
  • At the top of the file main() function is defined. It calls the meow() function 3 times (It is not mandatory to create a main() function in Python, but by convention it's expected).

  • Then we defined the meow() function to print the string "meow".


Important

Notice at the last line, we are calling main(). In Python we have to call the main() function for it to run. If we ony define the functions, they will not execute.



We can optimize further our code for readability and efficiency by passing variables between our functions:

# Abstraction with parameterization 

def main():
    meow(3)

# Meow n times
def meow(n):
    for i in range(n):
    print("meow")


main()
  • Notice how we modified meow() to accept a parameter n.

  • In the main() function, meow() is called with the argument 3.

  • When meow() is called it uses the value passed to it (3) to control the number of iteration in the for loop.


We successfully abstracted away the complexities of the functions by defining them. Now, if we needed to call meow() elsewhere in our program, we can simply use one line of code meow(n), to execute it a specified number of times.



Truncation and Floating Point Imprecision

Truncation is a process where excess digits or data beyond certain point are cut off. Dividing one *integer by another could result in an imprecise result.

In C we had to deal with truncation when working with integers and floats.

// Division with integers in C

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int x = get_int("x: ");
    int y = get_int("y: ");

    printf("%i\n", x / y);
}
x: 1
y: 3
0
  • In C When two integers are divided, the fractional part of the result is truncated, leaving only the integer portion. 1 / 3 results in 0 instead of 0.3333333.



Now, let's see how Python handles such division:

# Division with integers in Python - Lack of truncation 

x = int(input("x: "))
y = int(input("y: "))

z = x / y
print(z)
x: 1
y: 3
0.3333333

Notice that truncation did not happen. Executing an integer to integer division in Python that ends up giving a fractional component will result in a floating-point number.



Floating Point Imprecision

Floating point imprecision arises due to the inherent limitation in representing real numbers in binary. Not all decimal numbers can be precisely represented in binary.

We can reveal this imprecision using a special syntax in Python that allows us to show a set number of digits.

# Floating point imprecision

x = int(input("x: "))
y = int(input("y: "))

z = x / y
print(f"{z:.50f}")
x: 1
y: 3
0.33333333333333331482961625624739099293947219848633
  • f"{z:.50f}" We print out a format stringf"" and configure it to print out a specific number of digits of the value z.

  • z:.50f Means print out z with 50 digits after the decimal point specifying it's a float f.


Printing 50 digits after the decimal point in this division should result in: 0.33333333333333333333333333333333333333333333333333, which cannot be represented precisely in a binary floating-point system.

Since the computer can only use a finite number of bits to store a number, the system will truncate this repeating decimal at some point and print out the closest possible approximation possible within the limits of the number of bits used.



Integer Overflow

Integer overflow happens when an operation attempts to create a numeric value that is outside the range that can be represented with a given number of bits.

// Integer overflow in C

#include <stdio.h>

int main()
{
    // Maximum value for an unsigned char
    unsigned char a = 255;

    // Incrementing a by 1
    a = a + 1;

    printf("%d\n", a);

    return 0;
}
0
  • In this code, we declare an unsigned char a and assign to it the value of 255 (In C, unsigned chars can hold values from 0 to 255).

  • Adding 1 when it is already at the maximum value of 255 causes it to overflow, and instead of printing 256, it wraps around back to 0.



In Python integer overflow does not exist. Python integers int have arbitrary precision, meaning they can grow beyond bounds and allocate more and more memory to fit the integer.

# Simulating maximum value of an unsigned char
a = 255

# Incrementing a by 1
a = a + 1

print(a)
256
  • Notice that Python correctly calculates the result as 256. It handles this by automatically resizing the integer to fit the new value.



Exceptions

Let's define our own version of get_int():

# Implements get_int

def get_int(prompt):
    return int(input(prompt))

def main():
    x = get_int("x: ")
    y = get_int("y: ")
    print(X + Y)


main()
  • The code above works fine if the user cooperates and inputs an integer as a value. If any other data type is entered, we will get a Traceback error.



Error Handling in C

Remember that to handle errors in C we had to write the functions to return a special value if something went wrong (e.g., return 1; or return: NULL;).

This approach can lead to complications when a valid return value might naturally coincide with an error indicator. We wouldn't be able to write our function to return one of those values as part of its normal functionality. The value used for error handling is now reserved, meaning it cannot be returned by our function for any other purpose.



Error Handling in Python

Python has a built-in mechanism for raising and handling errors. When an error occurs, Python raises an exception that interrupts the normal flow of execution unless it is handled.

# Implements get_int + Error handling

def get_int(prompt):
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print("Not an integer")

def main():
    x = get_int("x: ")
    y = get_int("y: ")
    print(x + y)


main()
  • In the code above we used a Try and Except block to handle the possibility of the user entering an unexpected value.

  • We create a forever loop while True that will re-prompt the user if the input is not in the expected format.

  • Inside the loop we will try to run the function expect if we get a ValueError. In that case we will print the error message "Not an integer" and prompt the user again.

  • We could also replace the line print("Not an integer") error message with pass. This will silently re-prompt the user.



Mario

Let's rewrite a few lines of the Mario code in Python to represent a column of 3 bricks:

# Print a column of 3 bricks with a loop

for i in range(3)
print("#")
#
#
#
  • Notice the simplicity of the Python code.



// Mario in C
// Prints a column of height n

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // Get height of column
    int n;
    do
    {
        n = get_int("Height: ");
    }
    while (n < 1);

    // Print column of bricks
    for (int i = 0; i < n; i++)
    {
        printf("#\n");
    }
}
  • In C we had the advantage of a do-while loop.



# Mario in Python
# Prints a column of n bricks with a loop

from cs50 import get_int

while True:
    n = get_int("Height: ")
    if n > 0:
        break

for i in range(n):
    print("#")
  • Python does not have a do-while loop and the convention is to use a while loop.

  • The idea of implementing an infinite loop while True to get the desired outcome and breaking out of it break once it is done, is very common in Python.

  • The code ignores non-integer types because of the error checking in the get_int function. Also because we are only breaking from the while loop if user enters a positive integer if n > 0 negative numbers and 0 are ignored.



Now, let's implement a row of 4 question marks:

# Prints a row of 4 question marks with a loop

for i in range(4):
    print("?", end="")
print()
????
  • Remember we used the named parameter end="" to override its default \n value and print in the same line.
  • Notice the last print() line that enables us the place the cursor on the next line.



We can simplify this code further with an alternative syntax:

# Prints a row of 4 question marks without a loop

print("?" * 4)
????
  • We can use the * to multiply the print statement to repeat 4 times.



Now, let's implement a large block of bricks:

# Prints a 3-by-3 grid of bricks with a nested loop

for i in range(3):
    for j in range(3):
        print("#", end="")
    print()
###
###
###
  • Notice that we used a first for loop to represent the columns, and a nested for loop to represent the rows.

  • The named parameter end="" ensures the bricks are printed in line in each row.

  • The print() statement is used to add a new line at the end of each row.



# Alternative Syntax

for i in range(3):
    print("#" * 3)
###
###
###
  • We can also use the * to multiply the print statement to repeat 3 times in each row. Removing the need for a nested loop.



Lists - list

In Python, lists are a data structure and have built-in methods or functions within them. The memory is also automatically managed.

# Averages three numbers using a list

scores = [72, 73, 33]

average = sum(scores) / len(scores)
print(f"Average: {average}")
  • First we created a list scores and assigned to it 3 values.

  • To calculate the average we used built-in methods sum() to add the values, and divided the sum by the length of the list using len().

  • In the print() statement we used a format string.



We can also utilize the following syntax to get values from the user:

# Averages three numbers using a list and a loop

from cs50 import get_int

# Get scores
scores = []
for i in range(3):
    score = get_int("Score: ")
    scores.append(score)

# Print average
average = sum(scores) / len(scores)
print(f"Average: {average}")
  • In Python we can create an empty list scores = [] and add values later.

  • scores.append(score) We prompt the user for a score and we use the append() method to add it to the end of the scores list.



# Alternative syntax

from cs50 import get_int

scores = []

for i in range(3):
    get_int("Score: ")
    scores = scores + [score]

average = sum(scores) / len(scores)
print(f"Average: {average}")
  • We can also concatenate scores list with a modified version of itself.

  • scores = scores + [score] We take the list scores and modify its value scores by adding + to it the new score score.



Searching a List

Let's implement linear search to find an element in a List:

# Implements linear search for names using loop

names = ["Carter", "David", "John"]

name = input("Name: ")

for n in names:
    if name == n:
        print("Found")
        break
else:
    print("Not found")
  • Notice how the code implements linear search looping through every element in names to see if it matches the user input name.

  • If it's a match, it breaks out of the loop, if it's not, it prints not found.



The code above is functional, but in Python we can execute linear search with a better and simpler design:

# Implements linear search for names using `in`

names = ["Carter", "David", "John"]

name = input("Name: ")

if name in names:
    print("Found")
else:
    print("Not found")
  • The loop was replaced by the use of a conditional and the membership operator in, used to check if the value is present in a sequence. Python automatically performs the linear search.



Dictionaries - dict

A dictionary dict is an unordered collection of key-value pairs. In contrast to C, where creating a hash table from scratch was necessary to be able to have values associated to keys (e.g., mapping names to numbers in a phonebook), Python simplifies this process with its built-in dict.


Key Value
name Carter
number +1-617-495-1000

# Implements a phone book as a list of dictionaries

from cs50 import get_string

people = [
    {"name": "Carter", "number": "+1-617-495-1000"},
    {"name": "David", "number": "+1-617-495-1000"},
    {"name": "John", "number": "+1-949-468-2750"},
]

# Search for a name
name = get_string("Name: ")
for person in people:
    if person["name"] == name:
        print(f"Found {person['number']}")
        break
else:
    print("Not found")
  • We created a list of dictionaries people, where each dictionary holds two keys (name & number) with two associated values (e.g., Carter & +1-617-495-1000).

  • Notice that in the search, we did not have to define a structure called person before using it.

  • The for loop iterates through the dictionaries indexing into the name key person["name"] and compare it to the user input name.

  • If input name matches a key in a dictionary, The associated number is printed.


Notice in the print statement f-string, we are using single quotes '' around number person['number'], to differentiate from the surrounding double quotes "".



# Alternative simplified syntax

# Implements a phone book using a dictionary

from cs50 import get_string

people = {
    "Carter": "+1-617-495-1000",
    "David": "+1-617-495-1000",
    "John": "+1-949-468-2750",
}

# Search for name
name = input("Name: ")
if name in people:
    print(f"Number: {people[name]}")
else:
    print("Not found")
  • Notice that people is now one dictionary instead of a list of dictionaries. In this version we can only have one key associated with one value.

  • The statement if name in people searches for a match in the keys of the dictionary.

  • In the print statement, indexing into the key people[name] will print associated value number.


Key Value
Carter +1-617-495-1000



Command-Line Arguments

In Python, the sys module allows access to variable and functions used by the interpreter, as well as system-specific parameters and functions, including command-line arguments.


from sys import argv

if len(argv) == 2:
    print(f"hello, {argv[1]}")
else:
    print("hello, world")
  • First we import argv from the sys module.

  • If two arguments are entered in the command line len(argv) == 2, we print hello followed by the second argument argv[1].

  • If no argument is provided, we simply print hello, world


Important

The command python used to run the interpreter is ignored. Entering python greet.py Lazer will count as two command-line arguments: argv[0] is greet.py and argv[1] is Lazer.



Exit Status

The sys library also has built-in methods. We can use sys.exit(i) to exit the program with a specific exit code like it is typically done in C:

import sys

if len(sys.argv) != 2:
    print("Missing command-line argument")
    sys.exit(1)

print(f"hello, {sys.argv[1]}")
sys.exit(0)
  • Notice that since we imported the whole sys library, we have to specify sys.argv when using the parameter.

  • If the number of command-line arguments argv is not 2, we use built-in function sys.exit(1) to exit the program with a specific exit code of 1.

  • If sys.argv is equal to 2, we print the second argument argv[1] and exit the program with exit code 0.