University of London / MSc Computer Science: Principles of programming(後半)
July 9, 2023
ロンドン大学で MSc Computer Science: Principles of programming モジュールを履修中。
講義内容に関して記録した個人的なスタディノートです。
全 12 週のうち 6〜12 週目の内容を記録します。(6 週目開始:2023 年 5 月 22 日 / 12 週目終了:2023 年 7 月 9 日)
Week 6: Software development principles #
pytest #
main.py
def delete_every_2nd(s: str, term: str)->str:
# Raises ValueError if term is an empty string.
if not term:
raise ValueError
# If term is not appear in s then no process is needed
if not term in s:
return s
words = s.split(term)
# Remove empty string if exists
words = [w for w in words if w != ""]
print(words)
result: list[str]= []
for i in range(0, len(words)):
print(result)
if i % 2 == 0:
result.append(term)
result.append(words[i])
return "".join(result)
test_delete_every_2nd.py
import pytest
from main import *
def test_1():
with pytest.raises(ValueError):
delete_every_2nd("xxxx", "")
def test_2():
assert delete_every_2nd("aabcdaaaxyaa11aa2", "aa") == "aabcdaxyaa112"
def test_3():
assert delete_every_2nd("abcdefg", "x") == "abcdefg"
def test_4():
assert delete_every_2nd("", "x") == ""
def test_5():
assert delete_every_2nd("aaaa", "aa") == ""
def test_6():
assert delete_every_2nd("aaaaa", "aa") == "aaa"
Further videos material #
- Python unit testing - pytest introduction - Codebasics | YouTube
- Python unit testing - skip/selectively run tests in pytest - Codebasics | YouTube
- Python unit testing - pytest parameters - Codebasics | YouTube
Week 7: More on object-oriented programming #
Dunder methods #
Dunder = “D"ouble “under"score
Object-oriented programming #
from typing import Union
class Counter:
def __init__(self, id: str) -> None:
'''creates a new counter with a given ID'''
self.id = id
self._items: dict[str, list[Union[int, float]]] = {}
def add(self, item_name, amount, price_of_unit):
'''Adds amount of items with item_name and specifies price_of_unit.price_of_unit.'''
if item_name in self._items:
self._items[item_name][0] += amount
else:
self._items[item_name] = [amount, price_of_unit]
def remove(self, item_name: str, amount: int) -> None:
'''Removes the given amount of items with the given item name.'''
item = self._items.get(item_name)
if item:
current_amount = item[0]
if current_amount < amount:
self._items.pop(item_name)
else:
self._items[item_name][0] = current_amount - amount
def reset(self) -> None:
'''Removes all the records of items previously added.'''
self._items = {}
def get_total(self) -> float:
'''Returns the total sum rounded to two digits after decimal point .'''
total_price = 0.0
for item in self._items.values():
total_price += item[0] * item[1]
return round(total_price, 2)
def status(self) -> str:
'''
Returns string of form "Id N M", where Id is id of counter, N is total amount of all items and M total price of them rounded to two digits after decimal point (with both digits printed).
'''
total_amount = 0
total_price = 0.0
for item in self._items.values():
total_amount += item[0]
total_price += item[0] * item[1]
return f"{self.id} {total_amount} {total_price:.2f}"
# Test
c = Counter("C001")
assert c.id == "C001"
c.add("Spaghetti", 5, 1.8)
assert c.status() == "C001 5 9.00"
c.add("Ice Cream", 2, 3.4)
assert c.status() == "C001 7 15.80"
assert abs(c.get_total() - 15.8) < 0.0001
c.add("Spaghetti", 3, 1.8)
assert c.status() == "C001 10 21.20"
c.remove("Ice Cream", 1)
assert c.status() == "C001 9 17.80"
c.reset()
c.add("Coke", 5, 1.45)
assert c.status() == "C001 5 7.25"
Week 8: More on object-oriented programming #
Inheritance and Overriding #
Here’s a design suggestion: when you override a method, the interface of the new method should be the same as the old. It should take the same parameters, return the same type, and obey the same preconditions and postconditions. If you follow this rule, you will find that any function designed to work with an instance of a parent class will also work with instances of child classes. If you violate this rule, which is called the ‘Liskov substitution principle’, your code will collapse like (sorry) a house of cards.
class Person:
def __init__(self, first_name: str, last_name: str) -> None:
self._first_name = first_name
self._last_name = last_name
def get_info(self) -> str:
return f"{self._first_name} {self._last_name}"
def get_name(self) -> tuple[str, str]:
return (self._first_name, self._last_name)
class Adult(Person):
def __init__(self, first_name: str, last_name: str, phone_number: str) -> None:
super().__init__(first_name, last_name)
self._phone_number = phone_number
def get_phone(self) -> str:
return self._phone_number
class Child(Person):
def __init__(self, first_name: str, last_name: str, parent1: Person, parent2: Person) -> None:
super().__init__(first_name, last_name)
self._parent1 = parent1
self._parent2 = parent2
def get_info(self) -> str:
return f"{super().get_info()} {self._parent1.get_info()} {self._parent2.get_info()}"
def get_parents(self) -> tuple[Person, Person]:
return (self._parent1, self._parent2)
# Test
p = Person("Mary", "Ann")
a = Adult("John", "Doe", "1234567")
c = Child("Richard", "Doe", p, a)
assert a.get_info()== "John Doe"
assert a.get_phone()== "1234567"
assert c.get_name()==("Richard", "Doe")
assert c.get_info()== "Richard Doe Mary Ann John Doe"
assert c.get_parents() == (p, a)
Polymorphism #
Polymorphism can facilitate code reuse. Functions that work with several types are called polymorphic. For example, the built-in function sum, which adds the elements of a sequence, works as long as the elements of the sequence support addition.
Week 9: Functional programming #
Functional programming #
There various of programming paradigms. Example:
- Imperative programming paradigm(命令型プログラミング)
- Procedural programming paradigm(手続き型プログラミング)
- Object-oriented programming paradigm(オブジェクト指向プログラミング)
- Declarative programming(宣言型プログラミング)
- Functional programming(関数型プログラミング)
Functional Programming is one of the programming paradigm. In functional programming:
- Output of functions depend only on input (no global variables)
- Minimize use of loops (use recursion instead)
- Minimize use of variables
The motivations of using Functional Programming are:
- Functional programs are easier verified for correctness
- Functional programs can be more scalable
- Functional programs are much better parallelizable
list comprehension #
list comprehension = リスト内包表記
num_tuple = (1, 2, 3)
num_list = [x for x in num_tuple] # [1, 2, 3]
num_set = {x for x in num_tuple} # {1, 2, 3}
collection_a = (1, 2, 3)
collection_b = ['a', 'b', 'c']
list_c = [(x, y) for x in collection_a for y in collection_b]
# [(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]
collection = [1, 2, 3, 4]
list_a = [x for x in num_tuple >= 2] # [2, 3, 4]
list_b = [x ** 2 for x in collection] # [1, 4, 9, 16]
even_numbers = [x for x in collection if x % 2 == 0] # [2, 4]
def double(x):
return x * 2
def is_even(x):
return x % 2 == 0
collection = [1, 2, 3, 4]
even_numbers = [double(x) for x in collection if is_even(x)] # [4, 8]
Z = []
for x in X:
for y in Y:
if x + y < 5:
Z.append((x, y))
# is equal to
Z = [(x, y) for x in X for y in Y if x + y < 5]
Z = []
for y in Y: if y < 5:
Z.append(y * y)
# is equal to
Z = [x * x for x in [y for y in Y if y < 5]]
cities = ['mumbai', 'new york', 'paris']
countries = ['india', 'usa', 'france']
d = { city: country for city, country in zip(cities, countries) }
# {
# 'new york': 'usa',
# 'mumbai': 'india',
# 'paris': 'france'
# }
def create_matrix(m: int, n: int)-> list[list[int]]:
return [[i + j for j in range(n)] for i in range(m)]
create_matrix(2, 3)
# [
# [0, 1, 2, 3],
# [1, 2, 3, 4],
# [2, 3, 4, 5]
# ]
Higher-order functions #
Higher order function is a function that takes a function as an input parameter or as the return value.
def double(x):
return x * 2
def square(x):
return x * x
def vectorize(f):
def new_func(vector):
result = []
for x in vector:
result.append(f(x))
return result
return new_func
vec_double = vectorize(double)
vec_square = vectorize(square)
vec_double([1, 2, 3]) # [2, 4, 6]
vec_square([1, 2, 3]) # [1, 4, 9]
Commonly used built-in higher-order functions #
def plus(x, y):
return x + y
'''map'''
a, b = [1, 2, 3], [5, 6, 7]
it = map(plus, a, b)
print(list(it)) # [6, 8, 10]
def is_even(x):
return x % 2 == 0
'''filter'''
a = [1, 2, 3, 4, 5]
it = filter(is_even, a)
print(list(it)) # [2, 4]
'''enumerate'''
x = ['a', 'b', 'c']
it = enumerate(x)
print(list(it)) # [(0, 'a'), (1, 'b'), (2, 'c')]
'''zip'''
x, y = ['a', 'b', 'c'], [5, 6, 7]
it = zip(x, y)
print(list(it)) # [('a', 5), ('b', 6), ('c', 7)]
'''sorted'''
x = [5, 7, 3, 8, 1]
l = sorted(x)
print(l) # [1, 3, 5, 7, 8]
Iterators #
https://www.youtube.com/watch?v=Fc1fLEk_Kr0
Week 10: Recursion #
Recursion #
We have to pay a price for recursion:
- calling a function consumes more time and memory than adjusting a loop counter.
- high performance applications (graphic action games, real-time scientific simulations) hardly ever use recursion.
In less demanding applications recursion is an attractive alternative for iteration (for the right problems!)
- many search and sort problems
- combinatorial problems
'''
Factorial function(階乗)
Example:
6! = 6 * 5 * 4 * 3 * 2 * 1
We could write:
6! = 6 * 5!
Rule:
n! = 1 (if n is equal to 1) --- if n is equal to 1
n! = n * (n-1) --- if n is larger than 1
'''
def fac(n):
if n == 1:
return 1
else:
return n * fac(n - 1)
fac(1) # 1
fac(2) # 2
fac(3) # 6
fac(4) # 24
fac(5) # 120
fac(6) # 720
'''
Fibonacci numbers recursive algorithm
Rule:
0th Fibonacci number is 0 --- fib(0) = 0
1st Fibonacci number is 1 --- fib(1) = 1
Nth Fibonacci number is the sum of (n-1)th and (n-2)th --- fib(n) = fib(n-1) + fib(n-2)
Example:
2nd Fibonacci number is the sum of the 0th and 1st --- fib(2) = fib(0) + fib(1)
3rd Fibonacci number is the sum of the 1st and 2nd --- fib(3) = fib(1) + fib(2)
'''
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
fib(0) # 0
fib(1) # 1
fib(2) # 1
fib(3) # 2
fib(4) # 3
fib(5) # 5
fib(6) # 8
fib(7) # 13
fib(8) # 21
Week 11, 12 #
最終課題を作成する期間。課題は「Bishop and King Chess Puzzle(ビショップとキングのチェスゲーム)」を開発するというもの。要件をずらっと指定されるので、それを満たすように作成のうえ提出する。