Friday, May 13, 2022

Python - Project - Pomodoro (Day 28)

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.


Goal



Use Tkinter to build a Pomodoro GUI program.


How to add images to Tkinter?



We can use canvas as a wrapper to wrap the components such as image, line, or text.


Ex: 
import tkinter as tk

# Init
root = tk.Tk()
root.title("Exp")

# Init Canvas
canvas = tk.Canvas(width=200, height=224)
tomato_img = tk.PhotoImage(file="tomato.png")
# Create an image to canvas
canvas.create_image(
    100,
    112,
    image=tomato_img,
)
canvas.pack()

# Start the Event Loop
root.mainloop()


How to schedule/cancel a task on Tkinter? 


after()
* It calls the callback function once after a delay milliseconds.

after_cancel()
* It can stop the particular scheduled task.

Ex:
import tkinter as tk

# Global Variables
scheduled_job_identifier = None


def update_label():
    """Update Lable"""
    label.config(text="Label has been updated!!", fg="blue")


def schedule():
    """Start a scheduled job with 5 seconds delay"""
    global scheduled_job_identifier
    # Update label after 5 seconds delay
    scheduled_job_identifier = root.after(5000, update_label)


def cancel():
    """Cancel the scheduled job"""
    # Terminate the scheduled job
    root.after_cancel(scheduled_job_identifier)


# Init
root = tk.Tk()
root.title("Exp")
root.minsize(width=300, height=100)

# Init Label
label = tk.Label(text="Hello World", fg="red")
label.pack()

# Init Buttons
btn_schedule = tk.Button(
    text="Schedule a task to change label text after 5 seconds",
command=schedule
)
btn_schedule.pack()
btn_cancel = tk.Button(text="cancel", command=cancel)
btn_cancel.pack()

# Start the Event Loop
root.mainloop()


Dynamic Typing


Dynamic typing means that the type of the variable is determined only during runtime.

Ex: 
value = 1
print(type(value))

value = "Hello World"
print(type(value))
Result:

<class 'int'> <class 'str'>



Project - Pomodoro




Ex:
import tkinter as tk
import math

# Constants
PINK = "#e2979c"
RED = "#e7305b"
GREEN = "#9bdeac"
YELLOW = "#f7f5dd"
FONT_NAME = "Courier"
WORK_MIN = 25
SHORT_BREAK_MIN = 5
LONG_BREAK_MIN = 20

# Global Variables
timer = None

# step 1: Work
# step 2: Short Break
# step 3: Work
# step 4: Short Break
# step 5: Work
# step 6: Short Break
# step 7: Work
# step 8: Long Break
step = 0


def reset_timer():
    """Reset App"""
    ## Cancel timer
    root.after_cancel(timer)

    ## Reset Title
    title_label.config(text="Timer")

    ## Reset countdown text
    canvas.itemconfig(count_down_text, text="00:00")

    ## Reset Marks
    marks_label.config(text="")

    ## Reset steps
    global step
    step = 0


def start_timer():
    """Trigger Count Down function"""
    global step
    step += 1

    if step % 8 == 0:
        # Long Break
        count_down(LONG_BREAK_MIN * 60)
        title_label.config(text="Break", fg=RED)
    elif step % 2 == 0:
        # Short Break
        count_down(SHORT_BREAK_MIN * 60)
        title_label.config(text="Break", fg=PINK)
    else:
        # Work
        count_down(WORK_MIN * 60)
        title_label.config(text="Work", fg=GREEN)


def get_count_down_text(counter):
    """Format count down secongs to readable text"""
    counter_min = math.floor(counter / 60)
    counter_second = counter % 60

    # Dynamic Typing
    if counter_second < 10:
        counter_second = "0" + str(counter_second)

    return f"{counter_min}:{counter_second}"


def count_down(counter):
    """Trigger root after function"""

    canvas.itemconfig(count_down_text, text=get_count_down_text(counter))

    if counter > 0:
        global timer
        timer = root.after(1000, count_down, counter - 1)
    else:
        # Go to next step
        start_timer()

        # Update marks label
        marks = ""
        for _ in range(math.floor(step / 2)):
            marks = marks + "✓"
        marks_label.config(text=marks)


# Init
root = tk.Tk()
root.title("Pomodoro")
root.configure(padx=100, pady=50, bg=YELLOW)

# Init Title Lable
title_label = tk.Label(
    text="Timer", background=YELLOW, foreground=GREEN,
font=(FONT_NAME, 30, "bold")
)
title_label.grid(column=1, row=0)

# Init Canvas
canvas = tk.Canvas(width=200, height=224, background=YELLOW,
highlightthickness=0)
tomato_img = tk.PhotoImage(file="tomato.png")
canvas.create_image(
    100,
    112,
    image=tomato_img,
)
count_down_text = canvas.create_text(
    100, 130, text="00:00", fill="white", font=(FONT_NAME, 35, "bold")
)
canvas.grid(column=1, row=1)

# Init Buttons
start_btn = tk.Button(text="Start", command=start_timer)
start_btn.grid(column=0, row=2)
reset_btn = tk.Button(text="Reset", command=reset_timer)
reset_btn.grid(column=2, row=2)

# Init Marks Label
marks_label = tk.Label(
    text="", background=YELLOW, foreground=GREEN,
font=(FONT_NAME, 10, "bold")
)
marks_label.grid(column=1, row=3)

# Start the Event Loop
root.mainloop()

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']