Sunday, May 8, 2022

Python - Default and Unlimited Arguments (Day 27)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Argument with Default Value



If some arguments are always the same in your custom function, then we can set those values as default.

Ex: Regular
# Define a function
def my_function(a, b, c):
    """Print input arguments"""
    print(a, b, c)

# Most of the cases, argument c is set to 0
my_function(a=1, b=2, c=0)
my_function(a=2, b=3, c=0)

Result:
1 2 0 2 3 0

Ex: Use Default Value
# Define a function
def my_function(a, b, c=0):
    """Print input arguments"""
    print(a, b, c)

# Most of the cases, argument c is set to 0
my_function(a=1, b=2)
my_function(a=2, b=3)

# If the argument c is not the default value, we can pass it
my_function(a=3, b=4, c=99)

Result:
1 2 0 2 3 0 3 4 99


Unlimited Positional Arguments



We define a function below.

Ex: 
# Define a function
def add(a, b):
    """Return the sum of input a and b"""
    return a + b

# Call custom function to get the sum of input
print(add(1, 2))

Result:
3

Then the add(a, b) function suits our needs if we only want to get the sum of two input numbers.

Later, imagine that if we need to get the sum of multiple input numbers (such as input a, b, c, and d), then we need to change this function definition.

In python, we can use unlimited positional arguments (*args) to allow you to pass multiple arguments.

Ex: 
# Define a function with *args
def my_function(*args):
    """Exp"""
    # Checking its type
    print("type of args", type(args))

    # Print this tuple value
    print("args: ", args)

    # Access tuple by index
    print("args[1]: ", args[1])

    # Loop through it
    print("Loop through all items")
    for num in args:
        print(num)

# Call custom function with 5 arguments
my_function(1, 2, 3, 4, 5)

# Call custom function with 5 arguments
my_function(2, 1, 3, 4, 5)


Result:
type of args <class 'tuple'> args: (1, 2, 3, 4, 5) args[1]: 2 Loop through all items 1 2 3 4 5
type of args <class 'tuple'> args: (2, 1, 3, 4, 5) args[1]: 1 Loop through all items 2 1 3 4 5

The type of *args is tuple, and we can use for loop to access its element.
And if we change the order of input arguments, then the result got changed since it is positional arguments.

Therefore, to utilize *args, our previous get sum example can be adjusted as the following.

Ex: 
# Define a function with *args
def add(*args):
    """Return the summary of multiple input"""
    sum_of_args = 0

    # Loop through input args
    for num in args:
        sum_of_args += num

    return sum_of_args

# Call custom function with 2 arguments
print(add(1, 2))

# Call custom function with 5 arguments
print(add(1, 2, 3, 4, 5))

Result:
3
15


Unlimited Keyword Arguments



In some cases, using index to access the unlimited positional arguments is not that convenient.

Then we can use Unlimited Keyword Arguments (**kwargs)

Ex: 
# Define a function with **kwargs
def my_function(**kwargs):
    """Exp"""
    # Checking its type
    print("type of kwargs", type(kwargs))

    # Print this dictionary value
    print("kwargs", kwargs)

    # Access dictionary by its key (key exists)
    print(f"key1: {kwargs['key1']}")

    # Access dictionary by its key with get function (key exists)
    print(f"key1: {kwargs.get('key1')}")

    # Access dictionary by its key with get function (key does not exist)
    # We can setup the default value if this key does not exist
    print(f"key3: {kwargs.get('key3', 0)}")

    # Loop through
    print("Loop through all items")
    for key, value in kwargs.items():
        print(key, value)

# Call custom function with 2 arguments
my_function(key1="value1", key2="value2")

Result:
type of kwargs <class 'dict'> kwargs {'key1': 'value1', 'key2': 'value2'} key1: value1 key1: value1 key3: 0 Loop through all items key1 value1 key2 value2

The type of **kwargs is dictionary, and we can use key instead of magic index number to access its value.
Also we can use for loop with items() to access all its key-value pair.

This is an example how we can use it in our calculation program.

Ex: 
# Define a function with **kwargs
def calculate(num, **kwargs):
    """Return the calculation of multiple input"""
    # Use get function and default value to access the dictionary element
    num += kwargs.get("add", 0)
    num -= kwargs.get("sub", 0)
    num *= kwargs.get("mul", 1)
    num /= kwargs.get("div", 1)

    return num

# Call custom function with 3 arguments
print("(1 + 1) * 2 = ", calculate(1, add=1, mul=2))

# Call custom function with 4 arguments
print("(1 + 6 - 4) * 8 / 3 = ", calculate(1, add=6, sub=4, mul=8, div=3))

Result:
(1 + 1) * 2 = 4.0 (1 + 6 - 4) * 8 / 3 = 8.0


Tkinter - The GUI Program




Ex: Hello World 
import tkinter as tk

# Callback function for tk.Button
def on_button_clicked():
    """Copy the content from my_entry to my_label"""
    my_label.config(text=my_entry.get())

# Initializes Tk and creates its associated Tcl interpreter
# It also creates a toplevel window, known as the root window,
# which serves as the main window of the application.
root = tk.Tk()

root.title("My First GUI Program")
root.minsize(width=500, height=300)

# Label
my_label = tk.Label(text="Hello Wordl!", font=("Arial", 24, "bold"))
my_label.pack()

# Entry
my_entry = tk.Entry()
my_entry.pack()

# Button
my_button = tk.Button(text="Click me", command=on_button_clicked)
my_button.pack()

# Start the Event Loop
root.mainloop()


Project - Miles to Kilometers Converter



Ex:
import tkinter as tk

# Callback function for tk.Button
def on_button_clicked():
    """Convert miles to kilometers and show it to km_value_label"""
    miles = float(miles_entry.get())
    km = miles * 1.609
    km_value_label.config(text=f"{km}")

# Initializes Tk and creates its associated Tcl interpreter
# It also creates a toplevel window, known as the root window,
# which serves as the main window of the application.
root = tk.Tk()

root.title("Converter")
root.config(padx=20, pady=20)
root.minsize(width=200, height=100)

# Entry
miles_entry = tk.Entry(width=20)
miles_entry.grid(column=1, row=0)

# Label
miles_label = tk.Label(text="Miles")
miles_label.grid(column=2, row=0)

desc_label = tk.Label(text="is equal to")
desc_label.grid(column=0, row=1)

km_value_label = tk.Label(text="0")
km_value_label.grid(column=1, row=1)

km_label = tk.Label(text="Km")
km_label.grid(column=2, row=1)

# Button
calculate_button = tk.Button(text="Calculate", command=on_button_clicked)
calculate_button.grid(column=1, row=2)

# Start the Event Loop
root.mainloop()

Wednesday, May 4, 2022

Python - List and Dictionary Comprehension (Day 26)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


The regular way to create a new list from an existing list



The example below is the regular way to create a new list calculated from an existing list.

Ex:
list = [1, 2, 3]
new_list = []

# Go through each item of the existing list
for num in list:
    # With some logic to get the new value
    new_value = num + 1
    # Append the new value to new_list
    new_list.append(new_value)

print(f"old list: {list}")
print(f"new list: {new_list}")

Result:
old list: [1, 2, 3]
new list: [2, 3, 4]


Using List Comprehension



List comprehensions provides a concise way to create lists.

* new_list = [new_item for item in list]

Ex:
list = [1, 2, 3]
new_list = [item + 1 for item in list]

print(f"old list: {list}")
print(f"new list: {new_list}")

Result:
old list: [1, 2, 3]
new list: [2, 3, 4]


List Comprehension is not only for List



It can be used with python sequence (an ordered set)
* list
* range
* string
* tuple

Ex: range
# get sequence 1, 2, 3, 4 from range(1, 5)
# and double the value
new_list = [number * 2 for number in range(1, 5)]

print(f"new_list: {new_list}")

Result:
new_list: [2, 4, 6, 8]

Ex: string
name = "Frank"

letter_list = [letter for letter in name]

print(f"letter_list: {letter_list}")

Result:
letter_list: ['F', 'r', 'a', 'n', 'k']


Conditional List Comprehension



We can even add some conditions in List Comprehension for filtering.

* new_list = [new_item for item in list if condition]

Ex:
number_list = [1, 2, 3, 4, 5, 6]

even_number_list = [num for num in number_list if num % 2 == 0]

print(f"even_number_list: {even_number_list}")

Result:
even_number_list: [2, 4, 6]


Exp - Get the common numbers from two files



Ex:
# file1.txt and file2.txt contain a bunch of numbers,
# each number on a new line.
# Create a list called result which contains the numbers
# that are common in both files.

with open("file1.txt", encoding="utf-8") as file:
    file_1 = file.readlines()

with open("file2.txt", encoding="utf-8") as file:
    file_2 = file.readlines()

result = [int(f1) for f1 in file_1 if f1 in file_2]

print(result)


Exp - Refactor the previous US State game



Ex: Previous version
# Define a dictionary
  output_disc = {"state": []}

  for state in states_list:
      if state not in correct_guess_states_list:
          output_disc["state"].append(state)


Ex: Using List Comprehension
  output_disc = {
  "state": [
        state for state in states_list if
state not in correct_guess_states_list
  ]
  }



Dictionary Comprehension




* new_dict = {new_key:new_value for item in list}
* new_dict = {new_key:new_value for (key, value) in dict.items()}
* new_dict = {new_key:new_value for (key, value) in dict.items() if condition}

Ex: Generate a dictionary based on a student list to generate student score randomly
import random

students = ["Andy", "Ben", "Calvin"]

# Generate a dictionary from a list
student_scores = {student: random.randint(50, 80) for student in students}

print(f"student_scores: {student_scores}")


Result:
student_scores: {'Andy': 79, 'Ben': 62, 'Calvin': 71}

Ex: Based on the previous example, generate a dictionary to filter out the students whose score is under 60
import random

students = ["Andy", "Ben", "Calvin"]

# Generate a dictionary from a list
student_scores = {student: random.randint(50, 80) for student in students}

# Generate a dictionary from an existing dict with condition
passed_students = {
    name: score for (name, score) in student_scores.items() if score >= 60
}

print(f"student_scores: {student_scores}")
print(f"passed_students: {passed_students}")


Result:
student_scores: {'Andy': 62, 'Ben': 52, 'Calvin': 62} passed_students: {'Andy': 62, 'Calvin': 62}


Project - NATO Alphabet



Ex: 
import pandas

# Using pandas to read csv file
nato_phonetic_alphabet_data_frame =
pandas.read_csv("nato_phonetic_alphabet.csv")

# Generate a dictionary to use alphabet as a key and
# relating word as a value
# {"A": "Alfa", "B": "Bravo"}
nato_phonetic_alphabet_dict = {
    row["letter"]: row["code"]
    for (index, row) in nato_phonetic_alphabet_data_frame.iterrows()
}

# Ask Users to enter a word
user_input = input("Enter a word? ")

# Use List Comprehension to create a result list
# Loop through all letters of user input and
# get the mapping word by dictionary
result = [nato_phonetic_alphabet_dict[letter] for letter in
user_input.upper()]

print(result)


Result:
Enter a word? Frank ['Foxtrot', 'Romeo', 'Alfa', 'November', 'Kilo']

Tuesday, April 26, 2022

Python - Handle CSV file and pandas (Day 25)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Read a CSV file



Using readlines of file object

Ex:
# Open file
with open("./weather_data.csv", encoding="utf-8") as csv_file:
    # Using readlines function of file object
    # to read all the lines of a file in a list
    data = csv_file.readlines()
    print(data)

Result:
 [
'day,temp,condition\n',
'Monday,12,Sunny\n',
'Tuesday,14,Rain\n',
'Wednesday,15,Rain\n',
'Thursday,14,Cloudy\n',
'Friday,21,Sunny\n',
'Saturday,22,Sunny\n',
'Sunday,24,Sunny'
]

According to the result, we need to spend lots of efforts to clean up the data (comma or newline character) before analyzing it.


Utilize csv module



The csv module implements classes to read and write tabular data in CSV format.

Ex:
import csv

# Open a file
with open("./weather_data.csv", encoding="utf-8") as csv_file:
    # csv.reader() will return a reader object
# which will iterate over lines
    data = csv.reader(csv_file)

    # Go through each line
    for row in data:
        print(row)

Result:
 ['day', 'temp', 'condition']
   ['Monday', '12', 'Sunny']
   ['Tuesday', '14', 'Rain']
   ['Wednesday', '15', 'Rain']
   ['Thursday', '14', 'Cloudy']
   ['Friday', '21', 'Sunny']
   ['Saturday', '22', 'Sunny']
   ['Sunday', '24', 'Sunny']

Now the result looks much better. Using csv module save lots of our time.

But if we want to get all temperatures from the previous result and save them into a list. Is there any better way to handle it?

Ex:
import csv

# Open a file
with open("./weather_data.csv", encoding="utf-8") as csv_file:
    # csv.reader() will return a reader object
# which will iterate over lines
    data = csv.reader(csv_file)

    # Define a list
    temperature = []

    # Go through each line
    for row in data:
        # Skip csv header row
        if row[1] != "temp":
            # 1 is a magic number
            temperature.append(row[1])

print(temperature)

Result:
 ['12', '14', '15', '14', '21', '22', '24']


pandas Library



The code above looks long and tedious.

There are some libraries can save our time, and pandas module is popular.

Ex:
import pandas

# Read a comma-separated values (csv) file into DataFrame.
data = pandas.read_csv("weather_data.csv")

# Read temp column
print(data["temp"])

Result:
  0    12
  1    14
  2    15
  3    14
  4    21
  5    22
  6    24
  Name: temp, dtype: int64

Now the code looks cleaner.

That is why python developers always use pandas module (or other library) to deal with csv file no matter how easy the tasks.


Exp 1 - Get the average temperature



Ex:
import pandas

# Read a comma-separated values (csv) file into DataFrame.
data = pandas.read_csv("weather_data.csv")

# Transform Series to List first
temp_list = data["temp"].to_list()
# Then using built-in function to finish the calculation
average = sum(temp_list) / len(temp_list)
print(average)

# Or Using Series.mean()
print(data["temp"].mean())


Exp 2 - Get rows with conditions



Ex:
import pandas

# Read a comma-separated values (csv) file into DataFrame.
data = pandas.read_csv("weather_data.csv")

# To select rows based on a conditional expression,
# use a condition inside the selection brackets [].
# Condition: data["day"] == "Monday"
monday_data = data[data["day"] == "Monday"]
print(monday_data)


Project - US State Game






* Load US Map image to Turtle Graphic
* Read all states and their relating x and y position from csv file
* Ask for user input (screen.textinput)
* Use title of screen.textinput to keep tracking the scores of this game
* Show correct guesses in specific position (which is read from csv)
* Use loop to allow users to keep guessing
* Track the correct guesses and output the missing states in csv when game is over

main.py
from turtle import Screen, Turtle
import pandas

# Constants
BG_IMAGE_PATH = "blank_states_img.gif"
INPUT_CSV_PATH = "50_states.csv"
OUTPUT_CSV_PATH = "states_to_learn.csv"

# Init Screen
screen = Screen()
screen.title("US State Game")
screen.bgpic(BG_IMAGE_PATH)

# Read state info from csv
state_data_frame = pandas.read_csv(INPUT_CSV_PATH)
states_list = state_data_frame["state"].to_list()
# Track the correct guesses
correct_guess_states_list = []

def write_state_to_screen(name, pos_x, pos_y):
    """Write state text in map with input position"""
    tim = Turtle()
    tim.penup()
    tim.color("black")
    tim.hideturtle()
    tim.setpos(pos_x, pos_y)
    tim.write(name)

def write_message_to_screen(text):
    """Show Message with input text"""
    tim = Turtle()
    tim.penup()
    tim.color("red")
    tim.hideturtle()
    tim.setpos(0, 0)
    tim.write(text, False, align="center", font=("Courier", 24, "normal"))

def get_user_input():
    """Generate a textinput and return the user_input to the caller"""
    textinput_title = "Guess the state"
    # Update textinput title if needed
    if len(correct_guess_states_list) > 0:
        textinput_title = f"{len(correct_guess_states_list)}/50 State
Correct"

    # Ask user input
    user_input = screen.textinput(
        title=textinput_title, prompt="What's another state's name?"
    )

    # If user clicked 'cancel'
    if user_input is None:
        return None

    # Uisng title() to get the title case of user input
    return user_input.title()

def write_the_missing_states_to_csv():
    """Write missing states to csv"""
    # Define a dictionary
    output_disc = {"state": []}

    for state in states_list:
        if state not in correct_guess_states_list:
            output_disc["state"].append(state)

    # Use a dictionary to initialize a DataFrame Object
    output_data_frame = pandas.DataFrame(output_disc)
    # Use 'to_csv' function of the DataFrame Object to output a csv file
    output_data_frame.to_csv(OUTPUT_CSV_PATH)


while len(correct_guess_states_list) < 50:
    # Get User Input
    answer = get_user_input()

    # Exit this game
    # 1. if users clicked cancel button of the input box
    # 2. if users typed 'exit'
    if answer is None or answer == "Exit":
        break

    if answer in states_list:
        # Select the state info from csv source
        answer_state_series = state_data_frame[
            state_data_frame["state"] == answer
        ].iloc[0]

        # Show the state
        write_state_to_screen(
            answer_state_series["state"],
            answer_state_series["x"],
            answer_state_series["y"],
        )

        # Append the correct guess to the tracking list
        correct_guess_states_list.append(answer)

# Determine the game result
if len(correct_guess_states_list) == 50:
    write_message_to_screen("You Win")
else:
    write_message_to_screen("You Lose")

    # Generate a csv file for the missing states for player to learn
    write_the_missing_states_to_csv()

screen.exitonclick()