School Commit Init

This commit is contained in:
2024-08-31 12:07:21 +03:00
commit 0b130ee18c
2801 changed files with 4720552 additions and 0 deletions
@@ -0,0 +1,32 @@
# :computer: Assignment 01
## Requirements
- Solve one problem statement from each set
- Write the solution to each problem statement in its corresponding Python module (`p1.py`, `p2.py` and `p3.py` respectively).
- Use functions, input parameters and return values
- Each function only does one thing!
- Do not use global variables
- Provide the user relevant messages regarding expected input and the meaning of the programs output.
- Assignment should be **completed by week 2, hard deadline is week 3**.
## Problem Statements
### First Set
1. Generate the first prime number larger than a given natural number `n`.
2. Given natural number `n`, determine the prime numbers `p1` and `p2` such that `n = p1 + p2` (check the Goldbach hypothesis).
3. For a given natural number `n` find the minimal natural number `m` formed with the same digits. (e.g. `n=3658, m=3568`).
4. For a given natural number `n` find the largest natural number written with the same digits. (e.g. `n=3658, m=8653`).
5. Generate the largest prime number smaller than a given natural number `n`. If such a number does not exist, a message should be displayed.
### Second Set
6. Determine a calendar date (as year, month, day) starting from two integer numbers representing the year and the day number inside that year (e.g. day number 32 is February 1st). Take into account leap years. Do not use Python's inbuilt date/time functions.
7. Determine the twin prime numbers `p1` and `p2` immediately larger than the given non-null natural number `n`. Two prime numbers `p` and `q` are called twin if `q - p = 2`.
8. Find the smallest number `m` from the Fibonacci sequence, defined by `f[0]=f[1]=1`, `f[n]=f[n-1] + f[n-2]`, for `n > 2`, larger than the given natural number `n`. (e.g. `for n = 6, m = 8`).
9. Consider a given natural number `n`. Determine the product `p` of all the proper factors of `n`.
10. The palindrome of a number is the number obtained by reversing the order of its digits (e.g. the `palindrome of 237 is 732`). For a given natural number `n`, determine its palindrome.
11. The numbers `n1` and `n2` have the property `P` if their writing in base 10 uses the same digits (e.g. `2113 and 323121`). Determine whether two given natural numbers have property `P`.
### Third Set
12. Determine the age of a person, in number of days. Take into account leap years, as well as the date of birth and current date `(year, month, day)`. Do not use Python's inbuilt date/time functions.
13. Determine the `n-th` element of the sequence `1,2,3,2,5,2,3,7,2,3,2,5,...` obtained from the sequence of natural numbers by replacing composed numbers with their prime divisors, without memorizing the elements of the sequence.
14. Determine the `n-th` element of the sequence `1,2,3,2,2,5,2,2,3,3,3,7,2,2,3,3,3,...` obtained from the sequence of natural numbers by replacing composed numbers with their prime divisors, each divisor `d` being written `d` times, without memorizing the elements of the sequence.
15. Generate the largest perfect number smaller than a given natural number `n`. If such a number does not exist, a message should be displayed. A number is perfect if it is equal to the sum of its divisors, except itself. (e.g. `6 is a perfect number, as 6=1+2+3`).
@@ -0,0 +1,32 @@
# Assignment 02
## Requirements
Implement a menu-driven console application to help visualize the way sorting algorithms work. You will be given two of the algorithms from the list below to implement (one from each set). When started, the program will print a menu with the following options:
- Generate a list of `n` random natural numbers. Generated numbers must be between `0` and `100`.
- Sort the list using the first algorithm. (see **NB!** below)
- Sort the list using the second algorithm. (see **NB!** below)
- Exit the program
## NB!
Before starting each sort, the program will ask for the value of parameter `step`. During sorting, the program will display the partially sorted list on the console each time it makes `step` operations or passes, depending on the algorithm (e.g., if `step=2`, display the partially sorted list after each 2 element swaps in bubble sort, after each 2 element insertions in insert sort, after every 2nd generation of a permutation in permutation sort etc.).
## Implementation requirements
- Write a function for each sorting algorithm; each function should take as parameter the list to be sorted and the value of parameter `step` that was read from the console.
- Functions communicate using input parameter(s) and return values (**DO NOT use global, or module-level variables**)
- Provide the user with a menu-driven console-based user interface. Input data should be read from the console and the results printed to the console. At each step, the program must provide the user the context of the operation (never display an empty prompt).
- You may use Internet resources to research the sorting algorithm, but you must be able to explain **how** and **why** they work in detail
## Sorting algorithms
### Basic set
- Bubble Sort
- Cocktail Sort
- Insert Sort
- Permutation Sort
- Selection Sort
### Advanced set
- Shell Sort
- Comb Sort
- Gnome Sort
- Stooge Sort
- Strand Sort
@@ -0,0 +1,40 @@
"""
Created on Sep 23, 2016
@author: Arthur
"""
"""
Print a message to the console
"""
print("Hello world!")
"""
Read from the console
"""
a = input("Enter the first number: ")
b = input("Enter the second number: ")
"""
NB!
What is printed out and why?
"""
print(a + b)
# This is a line comment
"""
NB!
What does this do?
"""
x = int(a)
y = int(b)
print(x + y)
"""
NB!
This is all very confusing... how do I know what is what?
"""
print("this should be a string - ", type(a))
print("and this is an integer -", type(x))
@@ -0,0 +1,72 @@
"""
Created on Sep 26, 2016
@author: Arthur
"""
"""
List
"""
myList = [1, 2, 3]
print(myList)
print(myList[1])
print('The list has', len(myList), 'elements')
print('Tha first element is', myList[0], 'and the last one is', myList[len(myList) - 1])
x = myList
print(myList, x)
"""
What happens here?
"""
x[1] = '?'
print(myList, x)
"""
List slicing
"""
myList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(myList[:2])
print(myList[2:])
myList[5:] = ['a', 'b', 'c']
print(myList)
myList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
myList[1:9] = 'x'
print(myList)
"""
Tuple
"""
tup = 1, 2, 'a'
print(tup)
print(tup[1])
for e in tup:
print(e)
"""
What happens if we uncomment this line?
"""
# tup[1] = 'x'
"""
Dictionary
"""
d = {'num': 1, 'den': 2}
print(d)
print(d['num'])
d['num'] = 99
print(d['num'])
if 'num' in d:
print('We have num!')
del d['num']
if 'num' in d:
print('We have num!')
@@ -0,0 +1,45 @@
"""
Created on Sep 23, 2016
@author: Arthur
"""
"""
Read a number and check whether it is prime
"""
x = input("Give the number: ")
x = int(x)
isPrime = True
for i in range(2, x // 2):
if x % i == 0:
isPrime = False
if isPrime:
print("Number is prime!")
else:
print("Number is not prime!")
"""
NB!
Let's break program flow as soon as we know it's not a prime
"""
x = input("Give the number: ")
x = int(x)
isPrime = True
i = 2
while isPrime and i <= x // 2:
i += 1
if x % i == 0:
isPrime = False
if isPrime:
print("Number is prime!")
else:
print("Number is not prime!")
"""
Open question!
How can you check the functions above do what they are supposed to?
"""
@@ -0,0 +1,153 @@
"""
Created on Dec 6, 2016
@author: Arthur
"""
'''
1. Compute the factorial for a given positive integer
'''
def factorial(n):
"""
Determine the factorial for the given positive integer
input:
n - input parameter
output:
n!
"""
'''
This is the best case, no recursion
'''
if n == 0:
return 1
'''
Recursive step progresses toward the simple case
'''
return n * factorial(n - 1)
def test_factorial():
for n in range(0, 10):
fact1 = factorial(n)
fact2 = 1
for i in range(1, n + 1):
fact2 *= i
assert fact1 == fact2
test_factorial()
'''
2. Compute the sum of a list of numbers
'''
def sum_list(lst):
"""
Calculate the sum of the elements in the list
input:
lst - the list
output:
The sum of the elements
"""
'''
This is the best case, no recursion
'''
if len(lst) == 0:
return 0
'''
Recursive step progresses toward the simple case
'''
return lst[0] + sum_list(lst[1:])
def test_sum_list():
assert sum_list([]) == 0
assert sum_list([0]) == 0
assert sum_list([1, 2, 6]) == 9
assert sum_list([-1, 4, -100, 50]) == -47
assert sum_list([1, 2, 3, 4, 5, 6]) == 21
test_sum_list()
'''
3. Cumpute the n-th term of the Fiboanacci sequence:
'''
def fibo(n):
"""
Computes the n-th term of the Fibonacci sequence
input:
n - the index of the desired term
output:
The value of the desired term
"""
'''
This is the best case, no recursion
'''
if n == 0 or n == 1:
return 1
'''
Recursive step progresses toward the simple case
'''
return fibo(n - 2) + fibo(n - 1)
def test_fibo():
fib = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
for index in range(0, len(fib)):
assert fibo(index) == fib[index]
test_fibo()
'''
4. Determine whether a given string is a palindrome
'''
def palindrome(s):
"""
Determine if the given string is a palindrome
input:
s - the string
output:
True if s is palindrome, False otherwise
"""
'''
This is the best case, no recursion
'''
if len(s) < 2:
return True
'''
Recursive step progresses toward the simple case
'''
return s[0] == s[-1] and palindrome(s[1:-1])
def test_palindrome():
assert palindrome("") is True
assert palindrome("a") is True
assert palindrome("axa") is True
assert palindrome("axdf") is False
assert palindrome("axdfdxa") is True
assert palindrome("abcddcba") is True
assert palindrome("abcddca") is False
test_palindrome()
@@ -0,0 +1,100 @@
"""
Created on Dec 6, 2016
@author: Arthur
"""
import timeit
from texttable import Texttable
'''
1. Here we have two implementation for the Fibonacci sequence
'''
def fibonacci_iterative(n):
"""
Iterative implementation for computing n-th term of the Fibonacci sequence
"""
if n == 0:
return 0
x = 0
y = 1
for i in range(1, n):
z = x + y
x = y
y = z
return y
def fibonacci_recursive(n):
"""
Recursive implementation for computing n-th term of the Fibonacci sequence
"""
if n < 2:
return n
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
'''
2. We test them to see they work correctly
'''
def test_fibonacci():
fib = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
for i in range(0, len(fib)):
assert fibonacci_iterative(i) == fib[i], (i, fibonacci_iterative(i))
assert fibonacci_recursive(i) == fib[i]
test_fibonacci()
'''
NB!
To run the function below, you must have installed the texttable component from:
https://github.com/foutaise/texttable
'''
def build_result_table():
table = Texttable()
table.add_row(['Term', 'Iterative', 'Recursive'])
for term in [10, 20, 30, 32, 34, 36]:
# Iterative
start_iter = timeit.default_timer()
row = fibonacci_iterative(term)
end_iter = timeit.default_timer()
# Recursive
start_rec = timeit.default_timer()
row = fibonacci_recursive(term)
end_rec = timeit.default_timer()
table.add_row([term, end_iter - start_iter, end_rec - start_rec])
return table
if __name__ == "__main__":
print(build_result_table().draw())
'''
In case you cannot run the example, this is what it is supposed to look like:
+------+-----------+-----------+
| Term | Iterative | Recursive |
+------+-----------+-----------+
| 10 | 0 | 0 |
+------+-----------+-----------+
| 20 | 0 | 3 |
+------+-----------+-----------+
| 30 | 0 | 357 |
+------+-----------+-----------+
| 32 | 0 | 937 |
+------+-----------+-----------+
| 34 | 0 | 2440 |
+------+-----------+-----------+
| 36 | 0 | 6437 |
+------+-----------+-----------+
NB!
0 milliseconds is not really 0, it's just too short to measure accurately
'''
@@ -0,0 +1,78 @@
"""
Created on Dec 6, 2016
@author: Arthur
"""
from texttable import Texttable
import timeit
from lecture.examples.ex05_complexity import fibonacci_recursive, fibonacci_iterative
'''
4. To speed up the recursive implementation, we use memoization to store interim results
'''
results = {0: 0, 1: 1}
def fibonacci_memoization(n):
if n not in results:
results[n] = fibonacci_memoization(n - 1) + fibonacci_memoization(n - 2)
return results[n]
dataList = []
'''
NB!
To run the function below, you must have installed the texttable component from:
https://github.com/foutaise/texttable
'''
def build_result_table():
table = Texttable()
table.add_row(['Term', 'Iterative', 'Recursive', 'Memoization'])
for term in [10, 20, 30, 32, 34, 36]:
# Iterative
start_iter = timeit.default_timer()
row = fibonacci_iterative(term)
end_iter = timeit.default_timer()
# Recursive
start_rec = timeit.default_timer()
row = fibonacci_recursive(term)
end_rec = timeit.default_timer()
# Recursive with memoization
start_mem = timeit.default_timer()
row = fibonacci_memoization(term)
end_mem = timeit.default_timer()
table.add_row([term, end_iter - start_iter, end_rec - start_rec, end_mem - start_mem])
return table
if __name__ == "__main__":
print(build_result_table().draw())
'''
In case you cannot run the example, this is what it is supposed to look like:
+------+-----------+-----------+-------------+
| Term | Iterative | Recursive | Memoization |
+------+-----------+-----------+-------------+
| 10 | 0 | 0 | 0 |
+------+-----------+-----------+-------------+
| 20 | 0 | 3 | 0 |
+------+-----------+-----------+-------------+
| 30 | 0 | 345 | 0 |
+------+-----------+-----------+-------------+
| 32 | 0 | 912 | 0 |
+------+-----------+-----------+-------------+
| 34 | 0 | 2381 | 0 |
+------+-----------+-----------+-------------+
| 36 | 0 | 6215 | 0 |
+------+-----------+-----------+-------------+
NB!
0 milliseconds is not really 0, it's just too short to measure accurately
'''
@@ -0,0 +1,99 @@
"""
Created on Dec 7, 2016
@author: Arthur
"""
import timeit
from texttable import Texttable
def hanoi(n, x, y, z):
"""
n - number of disks on the x stick
x - source Stick
y - destination stick
z - intermediate stick
"""
if n == 1:
return
hanoi(n - 1, x, z, y)
hanoi(n - 1, z, y, x)
def hanoi_verbose(n, x, y, z):
"""
n - number of disks on the x stick
x - source Stick
y - destination stick
z - intermediate stick
"""
if n == 1:
print("Disk 1 from ", x, " to ", y)
return
hanoi(n - 1, x, z, y)
print("Disk ", n, " from ", x, " to ", y)
hanoi(n - 1, z, y, x)
'''
NB!
To run the function below, you must have installed the texttable component from:
https://github.com/foutaise/texttable
'''
def build_result_table():
table = Texttable()
table.add_row(['disks', 'seconds'])
for term in range(10, 26):
t1 = timeit.default_timer()
hanoi(term, "X", "Y", "Z")
t2 = timeit.default_timer()
table.add_row([term, t2 - t1])
return table
print(build_result_table().draw())
'''
In case you cannot run the example, this is what it is supposed to look like:
+-------+-------------+
| Disks | Miliseconds |
+-------+-------------+
| 10 | 0 |
+-------+-------------+
| 11 | 0 |
+-------+-------------+
| 12 | 1 |
+-------+-------------+
| 13 | 1 |
+-------+-------------+
| 14 | 3 |
+-------+-------------+
| 15 | 5 |
+-------+-------------+
| 16 | 10 |
+-------+-------------+
| 17 | 19 |
+-------+-------------+
| 18 | 39 |
+-------+-------------+
| 19 | 76 |
+-------+-------------+
| 20 | 154 |
+-------+-------------+
| 21 | 312 |
+-------+-------------+
| 22 | 614 |
+-------+-------------+
| 23 | 1223 |
+-------+-------------+
| 24 | 2440 |
+-------+-------------+
| 25 | 4891 |
+-------+-------------+
NB!
0 miliseconds is not really 0, it's just too short to measure accurately
'''
@@ -0,0 +1,61 @@
"""
Examples for sequential searching
"""
import random
import timeit
from texttable import Texttable
def search_iter(data: list, key):
for i in range(len(data)):
if data[i] == key:
return i
return -1
def search_rec(data: list, key, pos: int = 0):
if 0 > pos or pos >= len(data):
return -1
if data[pos] == key:
return key
return search_rec(key, data, pos + 1)
def generate_list(length: int):
"""
Generate a list of given length with elements [0, ... , n-1]
:return: The newly generated list
"""
return list(range(length))
'''
NB!
To run the function below, you must have installed the texttable component from:
https://github.com/foutaise/texttable
'''
def build_result_table(algorithms: list, list_lengths: list):
table = Texttable()
table.add_row(['algorithm'] + list_lengths)
for algorithm in algorithms:
table_row = [algorithm.__name__]
for list_length in list_lengths:
data = generate_list(list_length)
t1 = timeit.default_timer()
# -1 is not in the list, so worst case complexity
algorithm(data, -1)
t2 = timeit.default_timer()
table_row.append(t2 - t1)
table.add_row(table_row)
return table
if __name__ == "__main__":
list_lengths = [1_000_000, 2_000_000, 4_000_000, 8_000_000, 16_000_000]
# Adding search_rec here will crash with recursion depth exceeded error
# TODO How do we add the binary search implementations here?
algorithms = [search_iter]
print(build_result_table(algorithms, list_lengths).draw())
@@ -0,0 +1,53 @@
def binary_search_rec(data: list, key):
"""
Binary search, recursive implementation
:param data: List in which search is performed in
:param key: Search key
:return: Position of element, -1 if element was not found
"""
return _binary_search_impl(data, key, 0, len(data) - 1)
def _binary_search_impl(data: list, key, left: int, right: int):
"""
This is an implementation method. _ means that the method should not be called from other modules.
"""
if right < left:
return -1
m = (left + right) // 2
if data[m] > key:
return _binary_search_impl(data, key, left, m - 1)
if data[m] < key:
return _binary_search_impl(data, key, m + 1, right)
if data[m] == key:
return m
def binary_search_iter(data: list, key):
left = 0
right = len(data) - 1
while left <= right:
middle = (left + right) // 2
if data[middle] > key:
right = middle - 1
if data[middle] < key:
left = middle + 1
if data[middle] == key:
return middle
return -1
# TODO Take a look at this method
def test_binary_search():
binary_search_alg = [binary_search_iter, binary_search_rec]
for bs_alg in binary_search_alg:
data = list(range(1000))
for i in range(0, 1000):
assert i == bs_alg(data, i)
assert -1 == bs_alg(list(range(100)), 101)
assert -1 == bs_alg(list(range(100)), -1)
test_binary_search()
@@ -0,0 +1,50 @@
from random import choice
def create_person(name: str, age: int):
return {"name": name, "age": age}
def get_name(person: dict):
return person["name"]
def get_age(person: dict):
return person["age"]
def generate():
"""
Generate some persons
"""
result = []
family_name = ['Popescu', 'Marian', 'Pop', 'Lazarescu', 'Dincu']
given_name = ['Anca', 'Emilia', 'Liviu', 'Marius']
age = [17, 18, 19, 20]
for i in range(20):
result.append(create_person(choice(family_name) + " " + choice(given_name), choice(age)))
return result
'''
1. Generate people
'''
result = generate()
'''
2. First we sort the list by name (ascending)
'''
result.sort(key=lambda person: person["name"])
'''
3. Then we sort by age (descending) - the sorts are STABLE
'''
result.sort(key=lambda person: person["age"], reverse=True)
'''
4. People of the same age are ordered by name
'''
for p in result:
print(p)
@@ -0,0 +1,52 @@
"""
Insertion sort. An O(n^2) complexity algorithm
"""
def insertion_sort(data: list):
for i in range(1, len(data)):
val = data[i]
j = i - 1
while (j >= 0) and (data[j] > val):
data[j + 1] = data[j]
j = j - 1
data[j + 1] = val
return data
"""
Binary insertion sort
Source: https://www.geeksforgeeks.org/binary-insertion-sort/ (code contributed by Mohit Gupta_OMG)
"""
def binary_search(arr, val, start, end):
# we need to distinguish whether we should insert before or after the left boundary. imagine [0] is the last
# step of the binary search and we need to decide where to insert -1
if start == end:
if arr[start] > val:
return start
else:
return start + 1
# this occurs if we are moving beyond left's boundary meaning the left boundary is the least position to find a
# number greater than val
if start > end:
return start
mid = (start + end) // 2
if arr[mid] < val:
return binary_search(arr, val, mid + 1, end)
elif arr[mid] > val:
return binary_search(arr, val, start, mid - 1)
else:
return mid
def binary_insertion_sort(data: list):
for i in range(1, len(data)):
val = data[i]
j = binary_search(data, val, 0, i - 1)
# This is O(n) space complexity, but it can be simplified by moving elements one by one
data = data[:j] + [val] + data[j:i] + data[i + 1:]
return data
@@ -0,0 +1,36 @@
"""
Merge Sort implementation
"""
def merge_sort(array):
if len(array) < 2:
return array
mid = len(array) // 2
left_half = array[:mid]
right_half = array[mid:]
merge_sort(left_half)
merge_sort(right_half)
merge(left_half, right_half, array)
def merge(l1, l2, lrez):
i = 0
j = 0
l = []
while i < len(l1) and j < len(l2):
if l1[i] < l2[j]:
l.append(l1[i])
i = i + 1
else:
l.append(l2[j])
j = j + 1
while i < len(l1):
l.append(l1[i])
i = i + 1
while j < len(l2):
l.append(l2[j])
j = j + 1
lrez.clear()
lrez.extend(l)
@@ -0,0 +1,649 @@
"""
TimSort implementation from https://github.com/reingart/pypy/blob/master/rpython/rlib/listsort.py
"""
"""
NB! Overflow checks removed so code is no longer production ready!
"""
# from rpython.rlib.rarithmetic import ovfcheck
## ------------------------------------------------------------------------
## Lots of code for an adaptive, stable, natural mergesort. There are many
## pieces to this algorithm; read listsort.txt for overviews and details.
## ------------------------------------------------------------------------
## Adapted from CPython, original code and algorithms by Tim Peters
def make_timsort_class(getitem=None, setitem=None, length=None,
getitem_slice=None, lt=None):
if getitem is None:
def getitem(list, item):
return list[item]
if setitem is None:
def setitem(list, item, value):
list[item] = value
if length is None:
def length(list):
return len(list)
if getitem_slice is None:
def getitem_slice(list, start, stop):
return list[start:stop]
if lt is None:
def lt(a, b):
return a < b
class TimSort(object):
"""TimSort(list).sort()
Sorts the list in-place, using the overridable method lt() for comparison.
"""
def __init__(self, list, listlength=None):
self.list = list
if listlength is None:
listlength = length(list)
self.listlength = listlength
def setitem(self, item, val):
setitem(self.list, item, val)
def lt(self, a, b):
return lt(a, b)
def le(self, a, b):
return not self.lt(b, a) # always use self.lt() as the primitive
# binarysort is the best method for sorting small arrays: it does
# few compares, but can do data movement quadratic in the number of
# elements.
# "a" is a contiguous slice of a list, and is sorted via binary insertion.
# This sort is stable.
# On entry, the first "sorted" elements are already sorted.
# Even in case of error, the output slice will be some permutation of
# the input (nothing is lost or duplicated).
def binarysort(self, a, sorted=1):
for start in range(a.base + sorted, a.base + a.len):
# set l to where list[start] belongs
l = a.base
r = start
pivot = a.getitem(r)
# Invariants:
# pivot >= all in [base, l).
# pivot < all in [r, start).
# The second is vacuously true at the start.
while l < r:
p = l + ((r - l) >> 1)
if self.lt(pivot, a.getitem(p)):
r = p
else:
l = p + 1
assert l == r
# The invariants still hold, so pivot >= all in [base, l) and
# pivot < all in [l, start), so pivot belongs at l. Note
# that if there are elements equal to pivot, l points to the
# first slot after them -- that's why this sort is stable.
# Slide over to make room.
for p in range(start, l, -1):
a.setitem(p, a.getitem(p - 1))
a.setitem(l, pivot)
# Compute the length of the run in the slice "a".
# "A run" is the longest ascending sequence, with
#
# a[0] <= a[1] <= a[2] <= ...
#
# or the longest descending sequence, with
#
# a[0] > a[1] > a[2] > ...
#
# Return (run, descending) where descending is False in the former case,
# or True in the latter.
# For its intended use in a stable mergesort, the strictness of the defn of
# "descending" is needed so that the caller can safely reverse a descending
# sequence without violating stability (strict > ensures there are no equal
# elements to get out of order).
def count_run(self, a):
if a.len <= 1:
n = a.len
descending = False
else:
n = 2
if self.lt(a.getitem(a.base + 1), a.getitem(a.base)):
descending = True
for p in range(a.base + 2, a.base + a.len):
if self.lt(a.getitem(p), a.getitem(p - 1)):
n += 1
else:
break
else:
descending = False
for p in range(a.base + 2, a.base + a.len):
if self.lt(a.getitem(p), a.getitem(p - 1)):
break
else:
n += 1
return ListSlice(a.list, a.base, n), descending
# Locate the proper position of key in a sorted vector; if the vector
# contains an element equal to key, return the position immediately to the
# left of the leftmost equal element -- or to the right of the rightmost
# equal element if the flag "rightmost" is set.
#
# "hint" is an index at which to begin the search, 0 <= hint < a.len.
# The closer hint is to the final result, the faster this runs.
#
# The return value is the index 0 <= k <= a.len such that
#
# a[k-1] < key <= a[k] (if rightmost is False)
# a[k-1] <= key < a[k] (if rightmost is True)
#
# as long as the indices are in bound. IOW, key belongs at index k;
# or, IOW, the first k elements of a should precede key, and the last
# n-k should follow key.
def gallop(self, key, a, hint, rightmost):
assert 0 <= hint < a.len
if rightmost:
lower = self.le # search for the largest k for which a[k] <= key
else:
lower = self.lt # search for the largest k for which a[k] < key
p = a.base + hint
lastofs = 0
ofs = 1
if lower(a.getitem(p), key):
# a[hint] < key -- gallop right, until
# a[hint + lastofs] < key <= a[hint + ofs]
maxofs = a.len - hint # a[a.len-1] is highest
while ofs < maxofs:
if lower(a.getitem(p + ofs), key):
lastofs = ofs
try:
pass
# ofs = ovfcheck(ofs << 1)
except OverflowError:
ofs = maxofs
else:
ofs = ofs + 1
else: # key <= a[hint + ofs]
break
if ofs > maxofs:
ofs = maxofs
# Translate back to offsets relative to a.
lastofs += hint
ofs += hint
else:
# key <= a[hint] -- gallop left, until
# a[hint - ofs] < key <= a[hint - lastofs]
maxofs = hint + 1 # a[0] is lowest
while ofs < maxofs:
if lower(a.getitem(p - ofs), key):
break
else:
# key <= a[hint - ofs]
lastofs = ofs
try:
pass
# ofs = ovfcheck(ofs << 1)
except OverflowError:
ofs = maxofs
else:
ofs = ofs + 1
if ofs > maxofs:
ofs = maxofs
# Translate back to positive offsets relative to a.
lastofs, ofs = hint - ofs, hint - lastofs
assert -1 <= lastofs < ofs <= a.len
# Now a[lastofs] < key <= a[ofs], so key belongs somewhere to the
# right of lastofs but no farther right than ofs. Do a binary
# search, with invariant a[lastofs-1] < key <= a[ofs].
lastofs += 1
while lastofs < ofs:
m = lastofs + ((ofs - lastofs) >> 1)
if lower(a.getitem(a.base + m), key):
lastofs = m + 1 # a[m] < key
else:
ofs = m # key <= a[m]
assert lastofs == ofs # so a[ofs-1] < key <= a[ofs]
return ofs
# hint for the annotator: the argument 'rightmost' is always passed in as
# a constant (either True or False), so we can specialize the function for
# the two cases. (This is actually needed for technical reasons: the
# variable 'lower' must contain a known method, which is the case in each
# specialized version but not in the unspecialized one.)
gallop._annspecialcase_ = "specialize:arg(4)"
# ____________________________________________________________
# When we get into galloping mode, we stay there until both runs win less
# often than MIN_GALLOP consecutive times. See listsort.txt for more info.
MIN_GALLOP = 7
def merge_init(self):
# This controls when we get *into* galloping mode. It's initialized
# to MIN_GALLOP. merge_lo and merge_hi tend to nudge it higher for
# random data, and lower for highly structured data.
self.min_gallop = self.MIN_GALLOP
# A stack of n pending runs yet to be merged. Run #i starts at
# address pending[i].base and extends for pending[i].len elements.
# It's always true (so long as the indices are in bounds) that
#
# pending[i].base + pending[i].len == pending[i+1].base
#
# so we could cut the storage for this, but it's a minor amount,
# and keeping all the info explicit simplifies the code.
self.pending = []
# Merge the slice "a" with the slice "b" in a stable way, in-place.
# a.len and b.len must be > 0, and a.base + a.len == b.base.
# Must also have that b.list[b.base] < a.list[a.base], that
# a.list[a.base+a.len-1] belongs at the end of the merge, and should have
# a.len <= b.len. See listsort.txt for more info.
def merge_lo(self, a, b):
assert a.len > 0 and b.len > 0 and a.base + a.len == b.base
min_gallop = self.min_gallop
dest = a.base
a = a.copyitems()
# Invariant: elements in "a" are waiting to be reinserted into the list
# at "dest". They should be merged with the elements of "b".
# b.base == dest + a.len.
# We use a finally block to ensure that the elements remaining in
# the copy "a" are reinserted back into self.list in all cases.
try:
self.setitem(dest, b.popleft())
dest += 1
if a.len == 1 or b.len == 0:
return
while True:
acount = 0 # number of times A won in a row
bcount = 0 # number of times B won in a row
# Do the straightforward thing until (if ever) one run
# appears to win consistently.
while True:
if self.lt(b.getitem(b.base), a.getitem(a.base)):
self.setitem(dest, b.popleft())
dest += 1
if b.len == 0:
return
bcount += 1
acount = 0
if bcount >= min_gallop:
break
else:
self.setitem(dest, a.popleft())
dest += 1
if a.len == 1:
return
acount += 1
bcount = 0
if acount >= min_gallop:
break
# One run is winning so consistently that galloping may
# be a huge win. So try that, and continue galloping until
# (if ever) neither run appears to be winning consistently
# anymore.
min_gallop += 1
while True:
min_gallop -= min_gallop > 1
self.min_gallop = min_gallop
acount = self.gallop(b.getitem(b.base), a, hint=0,
rightmost=True)
for p in range(a.base, a.base + acount):
self.setitem(dest, a.getitem(p))
dest += 1
a.advance(acount)
# a.len==0 is impossible now if the comparison
# function is consistent, but we can't assume
# that it is.
if a.len <= 1:
return
self.setitem(dest, b.popleft())
dest += 1
if b.len == 0:
return
bcount = self.gallop(a.getitem(a.base), b, hint=0,
rightmost=False)
for p in range(b.base, b.base + bcount):
self.setitem(dest, b.getitem(p))
dest += 1
b.advance(bcount)
if b.len == 0:
return
self.setitem(dest, a.popleft())
dest += 1
if a.len == 1:
return
if acount < self.MIN_GALLOP and bcount < self.MIN_GALLOP:
break
min_gallop += 1 # penalize it for leaving galloping mode
self.min_gallop = min_gallop
finally:
# The last element of a belongs at the end of the merge, so we copy
# the remaining elements of b before the remaining elements of a.
assert a.len >= 0 and b.len >= 0
for p in range(b.base, b.base + b.len):
self.setitem(dest, b.getitem(p))
dest += 1
for p in range(a.base, a.base + a.len):
self.setitem(dest, a.getitem(p))
dest += 1
# Same as merge_lo(), but should have a.len >= b.len.
def merge_hi(self, a, b):
assert a.len > 0 and b.len > 0 and a.base + a.len == b.base
min_gallop = self.min_gallop
dest = b.base + b.len
b = b.copyitems()
# Invariant: elements in "b" are waiting to be reinserted into the list
# before "dest". They should be merged with the elements of "a".
# a.base + a.len == dest - b.len.
# We use a finally block to ensure that the elements remaining in
# the copy "b" are reinserted back into self.list in all cases.
try:
dest -= 1
self.setitem(dest, a.popright())
if a.len == 0 or b.len == 1:
return
while True:
acount = 0 # number of times A won in a row
bcount = 0 # number of times B won in a row
# Do the straightforward thing until (if ever) one run
# appears to win consistently.
while True:
nexta = a.getitem(a.base + a.len - 1)
nextb = b.getitem(b.base + b.len - 1)
if self.lt(nextb, nexta):
dest -= 1
self.setitem(dest, nexta)
a.len -= 1
if a.len == 0:
return
acount += 1
bcount = 0
if acount >= min_gallop:
break
else:
dest -= 1
self.setitem(dest, nextb)
b.len -= 1
if b.len == 1:
return
bcount += 1
acount = 0
if bcount >= min_gallop:
break
# One run is winning so consistently that galloping may
# be a huge win. So try that, and continue galloping until
# (if ever) neither run appears to be winning consistently
# anymore.
min_gallop += 1
while True:
min_gallop -= min_gallop > 1
self.min_gallop = min_gallop
nextb = b.getitem(b.base + b.len - 1)
k = self.gallop(nextb, a, hint=a.len - 1, rightmost=True)
acount = a.len - k
for p in range(a.base + a.len - 1, a.base + k - 1, -1):
dest -= 1
self.setitem(dest, a.getitem(p))
a.len -= acount
if a.len == 0:
return
dest -= 1
self.setitem(dest, b.popright())
if b.len == 1:
return
nexta = a.getitem(a.base + a.len - 1)
k = self.gallop(nexta, b, hint=b.len - 1, rightmost=False)
bcount = b.len - k
for p in range(b.base + b.len - 1, b.base + k - 1, -1):
dest -= 1
self.setitem(dest, b.getitem(p))
b.len -= bcount
# b.len==0 is impossible now if the comparison
# function is consistent, but we can't assume
# that it is.
if b.len <= 1:
return
dest -= 1
self.setitem(dest, a.popright())
if a.len == 0:
return
if acount < self.MIN_GALLOP and bcount < self.MIN_GALLOP:
break
min_gallop += 1 # penalize it for leaving galloping mode
self.min_gallop = min_gallop
finally:
# The last element of a belongs at the end of the merge, so we copy
# the remaining elements of a and then the remaining elements of b.
assert a.len >= 0 and b.len >= 0
for p in range(a.base + a.len - 1, a.base - 1, -1):
dest -= 1
self.setitem(dest, a.getitem(p))
for p in range(b.base + b.len - 1, b.base - 1, -1):
dest -= 1
self.setitem(dest, b.getitem(p))
# Merge the two runs at stack indices i and i+1.
def merge_at(self, i):
a = self.pending[i]
b = self.pending[i + 1]
assert a.len > 0 and b.len > 0
assert a.base + a.len == b.base
# Record the length of the combined runs and remove the run b
self.pending[i] = ListSlice(self.list, a.base, a.len + b.len)
del self.pending[i + 1]
# Where does b start in a? Elements in a before that can be
# ignored (already in place).
k = self.gallop(b.getitem(b.base), a, hint=0, rightmost=True)
a.advance(k)
if a.len == 0:
return
# Where does a end in b? Elements in b after that can be
# ignored (already in place).
b.len = self.gallop(a.getitem(a.base + a.len - 1), b, hint=b.len - 1,
rightmost=False)
if b.len == 0:
return
# Merge what remains of the runs. The direction is chosen to
# minimize the temporary storage needed.
if a.len <= b.len:
self.merge_lo(a, b)
else:
self.merge_hi(a, b)
# Examine the stack of runs waiting to be merged, merging adjacent runs
# until the stack invariants are re-established:
#
# 1. len[-3] > len[-2] + len[-1]
# 2. len[-2] > len[-1]
#
# Note these invariants will not hold for the entire pending array even
# after this function completes. [1] This does not affect the
# correctness of the overall algorithm.
#
# [1] http://envisage-project.eu/proving-android-java-and-python-sorting-algorithm-is-broken-and-how-to-fix-it/
#
# See listsort.txt for more info.
def merge_collapse(self):
p = self.pending
while len(p) > 1:
if len(p) >= 3 and p[-3].len <= p[-2].len + p[-1].len:
if p[-3].len < p[-1].len:
self.merge_at(-3)
else:
self.merge_at(-2)
elif p[-2].len <= p[-1].len:
self.merge_at(-2)
else:
break
# Regardless of invariants, merge all runs on the stack until only one
# remains. This is used at the end of the mergesort.
def merge_force_collapse(self):
p = self.pending
while len(p) > 1:
if len(p) >= 3 and p[-3].len < p[-1].len:
self.merge_at(-3)
else:
self.merge_at(-2)
# Compute a good value for the minimum run length; natural runs shorter
# than this are boosted artificially via binary insertion.
#
# If n < 64, return n (it's too small to bother with fancy stuff).
# Else if n is an exact power of 2, return 32.
# Else return an int k, 32 <= k <= 64, such that n/k is close to, but
# strictly less than, an exact power of 2.
#
# See listsort.txt for more info.
def merge_compute_minrun(self, n):
r = 0 # becomes 1 if any 1 bits are shifted off
while n >= 64:
r |= n & 1
n >>= 1
return n + r
# ____________________________________________________________
# Entry point.
def sort(self):
remaining = ListSlice(self.list, 0, self.listlength)
if remaining.len < 2:
return
# March over the array once, left to right, finding natural runs,
# and extending short natural runs to minrun elements.
self.merge_init()
minrun = self.merge_compute_minrun(remaining.len)
while remaining.len > 0:
# Identify next run.
run, descending = self.count_run(remaining)
if descending:
run.reverse()
# If short, extend to min(minrun, nremaining).
if run.len < minrun:
sorted = run.len
run.len = min(minrun, remaining.len)
self.binarysort(run, sorted)
# Advance remaining past this run.
remaining.advance(run.len)
# Push run onto pending-runs stack, and maybe merge.
self.pending.append(run)
self.merge_collapse()
assert remaining.base == self.listlength
self.merge_force_collapse()
assert len(self.pending) == 1
assert self.pending[0].base == 0
assert self.pending[0].len == self.listlength
class ListSlice:
"A sublist of a list."
def __init__(self, list, base, len):
self.list = list
self.base = base
self.len = len
def copyitems(self):
"Make a copy of the slice of the original list."
start = self.base
stop = self.base + self.len
assert 0 <= start <= stop # annotator hint
return ListSlice(getitem_slice(self.list, start, stop), 0, self.len)
def advance(self, n):
self.base += n
self.len -= n
def getitem(self, item):
return getitem(self.list, item)
def setitem(self, item, value):
setitem(self.list, item, value)
def popleft(self):
result = getitem(self.list, self.base)
self.base += 1
self.len -= 1
return result
def popright(self):
self.len -= 1
return getitem(self.list, self.base + self.len)
def reverse(self):
"Reverse the slice in-place."
list = self.list
lo = self.base
hi = lo + self.len - 1
while lo < hi:
list_hi = getitem(list, hi)
list_lo = getitem(list, lo)
setitem(list, lo, list_hi)
setitem(list, hi, list_lo)
lo += 1
hi -= 1
return TimSort
ts = make_timsort_class()
def tim_sort_rpython(array: list):
ts(array).sort()
@@ -0,0 +1,356 @@
"""
Source code from https://gist.github.com/vladris/13bf84513e76b75a60b0eb761207541e
Python Timsort implementation based on the OpenJDK Java implementation
"""
MIN_MERGE = 32
MIN_GALLOP = 7
minGallop = MIN_GALLOP
def minRunLength(n):
r = 0
while n >= MIN_MERGE:
r |= n & 1
n >>= 1
return n + r
def binarySort(arr, lo, hi, start):
if start == lo:
start += 1
while start < hi:
pivot = arr[start]
left, right = lo, start
while left < right:
mid = (left + right) // 2
if pivot < arr[mid]:
right = mid
else:
left = mid + 1
arr.pop(start)
arr.insert(left, pivot)
start += 1
def reverseRange(arr, lo, hi):
hi -= 1
while lo < hi:
arr[lo], arr[hi] = arr[hi], arr[lo]
lo += 1
hi -= 1
def countRunAndMakeAscending(arr, lo, hi):
runHi = lo + 1
if runHi == hi:
return 1
if arr[lo] > arr[runHi]: # Descending run
while runHi < hi and arr[runHi] < arr[runHi - 1]:
runHi += 1
reverseRange(arr, lo, runHi)
else: # Ascending run
while runHi < hi and arr[runHi] >= arr[runHi - 1]:
runHi += 1
return runHi - lo
def gallopLeft(key, arr, base, len, hint):
lastOfs, ofs = 0, 1
if key > arr[base + hint]:
maxOfs = len - hint
while ofs < maxOfs and key > arr[base + hint + ofs]:
lastOfs = ofs
ofs = (ofs << 1) + 1
if ofs > maxOfs:
ofs = maxOfs
lastOfs += hint
ofs += hint
else:
maxOfs = hint + 1
while ofs < maxOfs and key <= arr[base + hint - ofs]:
lastOfs = ofs
ofs = (ofs << 1) + 1
if ofs > maxOfs:
ofs = maxOfs
lastOfs, ofs = hint - ofs, hint - lastOfs
lastOfs += 1
while lastOfs < ofs:
mid = lastOfs + (ofs - lastOfs) // 2
if key > arr[base + mid]:
lastOfs = mid + 1
else:
ofs = mid
return ofs
def gallopRight(key, arr, base, len, hint):
ofs, lastOfs = 1, 0
if key < arr[base + hint]:
maxOfs = hint + 1
while ofs < maxOfs and key < arr[base + hint - ofs]:
lastOfs = ofs
ofs = (ofs << 1) + 1
if ofs > maxOfs:
ofs = maxOfs
lastOfs, ofs = hint - ofs, hint - lastOfs
else:
maxOfs = len - hint
while ofs < maxOfs and key >= arr[base + hint + ofs]:
lastOfs = ofs
ofs = (ofs << 1) + 1
if ofs > maxOfs:
ofs = maxOfs
lastOfs += hint;
ofs += hint;
lastOfs += 1
while lastOfs < ofs:
mid = lastOfs + ((ofs - lastOfs) // 2)
if key < arr[base + mid]:
ofs = mid
else:
lastOfs = mid + 1
return ofs
def mergeLo(arr, lo, mid, hi):
t = arr[lo:mid]
i, j, k = lo, mid, 0
global minGallop
done = False
while not done:
count1, count2 = 0, 0
while (count1 | count2) < minGallop:
if t[k] < arr[j]:
arr[i] = t[k]
count1 += 1
count2 = 0
k += 1
else:
arr[i] = arr[j]
count1 = 0
count2 += 1
j += 1
i += 1
if k == mid - lo or j == hi:
done = True
break
if done:
break
while count1 >= MIN_GALLOP or count2 >= MIN_GALLOP:
count1 = gallopRight(arr[j], t, k, mid - lo - k, 0)
if count1 != 0:
arr[i:i + count1] = t[k:k + count1]
i += count1
k += count1
if k == mid - lo:
done = True
break
arr[i] = arr[j]
i += 1
j += 1
if j == hi:
done = True
break
count2 = gallopLeft(t[k], arr, j, hi - j, 0)
if count2 != 0:
arr[i:i + count2] = arr[j:j + count2]
i += count2
j += count2
if j == hi:
done = True
break
arr[i] = t[k]
i += 1
k += 1
if k == mid - lo:
done = True
break
minGallop -= 1
if minGallop < 0:
minGallop = 0
minGallop += 2
if k < mid - lo:
arr[i:hi] = t[k:mid - lo]
def mergeHi(arr, lo, mid, hi):
t = arr[mid:hi]
i, j, k = hi - 1, mid - 1, hi - mid - 1
global minGallop
done = False
while not done:
count1, count2 = 0, 0
while (count1 | count2) < minGallop:
if t[k] > arr[j]:
arr[i] = t[k]
count1 += 1
count2 = 0
k -= 1
else:
arr[i] = arr[j]
count1 = 0
count2 += 1
j -= 1
i -= 1
if k == -1 or j == lo - 1:
done = True
break
if done:
break
while count1 >= MIN_GALLOP or count2 >= MIN_GALLOP:
count1 = j - lo + 1 - gallopRight(t[k], arr, lo, j - lo + 1, j - lo)
if count1 != 0:
arr[i - count1 + 1:i + 1] = arr[j - count1 + 1:j + 1]
i -= count1
j -= count1
if j == lo - 1:
done = True
break
arr[i] = t[k]
i -= 1
k -= 1
if k == -1:
done = True
break
count2 = k + 1 - gallopLeft(arr[j], t, 0, k + 1, k)
if count2 != 0:
arr[i - count2 + 1:i + 1] = t[k - count2 + 1:k + 1]
i -= count2
k -= count2
if k == -1:
done = True
break
arr[i] = arr[j]
i -= 1
j -= 1
if j == lo - 1:
done = True
break
minGallop -= 1
if minGallop < 0:
minGallop = 0
minGallop += 2
if k >= 0:
arr[lo:i + 1] = t[0:k + 1]
def mergeAt(arr, stack, i):
assert i == len(stack) - 2 or i == len(stack) - 3
base1, len1 = stack[i]
base2, len2 = stack[i + 1]
stack[i] = (base1, len1 + len2)
if i == len(stack) - 3:
stack[i + 1] = stack[i + 2]
stack.pop()
k = gallopRight(arr[base2], arr, base1, len1, 0)
base1 += k
len1 -= k
if len1 == 0:
return
len2 = gallopLeft(arr[base1 + len1 - 1], arr, base2, len2, len2 - 1)
if len2 == 0:
return
if len1 > len2:
mergeLo(arr, base1, base2, base2 + len2)
else:
mergeHi(arr, base1, base2, base2 + len2)
def mergeCollapse(arr, stack):
while len(stack) > 1:
n = len(stack) - 2
if (n > 0 and stack[n - 1][1] <= stack[n][1] + stack[n + 1][1]) or \
(n > 1 and stack[n - 2][1] <= stack[n - 1][1] + stack[n][1]):
if stack[n - 1][1] < stack[n + 1][1]:
n -= 1
elif n < 0 or stack[n][1] > stack[n + 1][1]:
break
mergeAt(arr, stack, n)
def mergeForceCollapse(arr, stack):
while len(stack) > 1:
n = len(stack) - 2
if n > 0 and stack[n - 1][1] < stack[n + 1][1]:
n -= 1
mergeAt(arr, stack, n)
def tim_sort_vladris(arr):
lo, hi = 0, len(arr)
stack = []
nRemaining = hi
global minGallop
minGallop = MIN_GALLOP
if nRemaining < MIN_MERGE:
initRunLen = countRunAndMakeAscending(arr, lo, hi)
binarySort(arr, lo, hi, lo + initRunLen)
return
minRun = minRunLength(len(arr))
while nRemaining > 0:
runLen = countRunAndMakeAscending(arr, lo, hi)
if runLen < minRun:
force = min(nRemaining, minRun)
binarySort(arr, lo, lo + force, lo + runLen)
runLen = force
stack.append((lo, runLen))
mergeCollapse(arr, stack)
lo += runLen
nRemaining -= runLen
mergeForceCollapse(arr, stack)
@@ -0,0 +1,102 @@
"""
QuickSort example
Source code from https://www.geeksforgeeks.org/iterative-quick-sort/ (code is contributed by Mohit Kumra)
"""
def partition(array: list, low: int, high: int):
i = (low - 1)
x = array[high]
for j in range(low, high):
if array[j] <= x:
# increment index of smaller element
i = i + 1
array[i], array[j] = array[j], array[i]
array[i + 1], array[high] = array[high], array[i + 1]
return i + 1
# Function to do Quick sort
# arr[] --> Array to be sorted,
# l --> Starting index,
# h --> Ending index
def quick_sort_but_slow(array: list):
# Create an auxiliary stack
low = 0
high = len(array) - 1
size = high - low + 1
stack = [0] * size
# initialize top of stack
top = -1
# push initial values of l and h to stack
top = top + 1
stack[top] = low
top = top + 1
stack[top] = high
# Keep popping from stack while is not empty
while top >= 0:
# Pop h and l
high = stack[top]
top = top - 1
low = stack[top]
top = top - 1
# Set pivot element at its correct position in
# sorted array
p = partition(array, low, high)
# If there are elements on left side of pivot,
# then push left side to stack
if p - 1 > low:
top = top + 1
stack[top] = low
top = top + 1
stack[top] = p - 1
# If there are elements on right side of pivot,
# then push right side to stack
if p + 1 < high:
top = top + 1
stack[top] = p + 1
top = top + 1
stack[top] = high
"""
Iterative implementation of quick_sort
Source code from https://stackoverflow.com/questions/66546476/non-recursive-quicksort
User https://stackoverflow.com/users/3282056/rcgldr
"""
def quick_sort(array):
if len(array) < 2: # if nothing to sort, return
return
stack = [[0, len(array) - 1]] # initialize stack
while len(stack) > 0: # loop till stack empty
lo, hi = stack.pop() # pop lo, hi indexes
p = array[(lo + hi) // 2] # pivot, any a[] except a[hi]
i = lo - 1 # Hoare partition
j = hi + 1
while 1:
while 1: # while(a[++i] < p)
i += 1
if array[i] >= p:
break
while 1: # while(a[--j] < p)
j -= 1
if array[j] <= p:
break
if i >= j: # if indexes met or crossed, break
break
array[i], array[j] = array[j], array[i] # else swap elements
if j > lo: # push indexes onto stack
stack.append([lo, j])
j += 1
if hi > j:
stack.append([j, hi])
@@ -0,0 +1,119 @@
"""
Created on Dec 20, 2016
@author: Arthur
"""
from random import *
from datetime import *
from texttable import *
from ex11_insertion_sort import insertion_sort, binary_insertion_sort
from ex12_merge_sort import merge_sort
from ex13_tim_sort import tim_sort_rpython
from ex14_tim_sort import tim_sort_vladris
from ex15_quick_sort import quick_sort_but_slow, quick_sort
def already_sorted_data(data_size):
result = list(range(0, data_size))
return result
def sorted_in_reverse_data(data_size):
result = list(range(data_size, 0, -1))
return result
def random_data(data_size):
result = list(range(0, data_size))
shuffle(result)
return result
def test_sorts():
array = list(range(100, 0, -1))
insertion_sort(array)
assert array == list(range(1, 101))
array.reverse()
array = binary_insertion_sort(array)
assert array == list(range(1, 101))
array.reverse()
merge_sort(array)
assert array == list(range(1, 101))
array.reverse()
quick_sort_but_slow(array)
assert array == list(range(1, 101))
array.reverse()
tim_sort_rpython(array)
assert array == list(range(1, 101))
array.reverse()
tim_sort_vladris(array)
assert array == list(range(1, 101))
test_sorts()
'''
Utility function to convert a timedelta into a number of milliseconds
'''
def millis_interval(start, end):
diff = end - start
millis = diff.days * 24 * 60 * 60 * 1000
millis += diff.seconds * 1000
millis += diff.microseconds / 1000
return int(millis)
'''
And here we build our experiment
data_generators - Change the functions that build the data set to be sorted
sort_functions - What functions will do the actual sort
data_sizes - What are the data sizes to be sorted
'''
def sort_test():
"""
Generator functions for best case, average case and worst case
"""
data_generators = [already_sorted_data, random_data, sorted_in_reverse_data]
'''
Sorting functions to employ
'''
sort_functions = [insertion_sort, binary_insertion_sort, merge_sort, quick_sort_but_slow, quick_sort,
tim_sort_rpython, tim_sort_vladris, sorted]
'''
Data sizes that will be sorted
'''
data_sizes = [64, 128, 256, 512, 1024, 2048, 4096, 8192]
'''
Do the sort and build the result table dynamically
'''
for generator in data_generators:
print("Current data: " + generator.__name__)
t = Texttable()
t.add_row(['Functions/size'] + data_sizes)
for sort_function in sort_functions:
row = [sort_function.__name__]
for size in data_sizes:
data = generator(size)
t1 = datetime.now()
sort_function(data)
t2 = datetime.now()
row = row + [millis_interval(t1, t2)]
t.add_row(row)
print(t.draw())
sort_test()
@@ -0,0 +1,67 @@
"""
Created on Jan 10, 2017
@author: Arthur
"""
import time
from texttable import *
def generate_test(array, dim):
if len(array) == dim:
# print (array)
pass
if len(array) > dim:
return
array.append(0)
for i in range(0, dim):
array[-1] = i
generate_test(array[:], dim)
def backtracking_iter(dim: int):
array = [-1] # candidate solution
while len(array) > 0:
chosen = False
while not chosen and array[-1] < dim - 1:
array[-1] = array[-1] + 1 # increase the last component
chosen = len(set(array)) == len(array)
if chosen:
if len(array) == dim:
print(array)
array.append(-1) # expand candidate solution
else:
array = array[:-1] # go back one component
def backtracking_rec(array, dim):
if len(array) == dim:
print(array)
if len(array) > dim:
return
array.append(0)
for i in range(0, dim):
array[-1] = i
if len(set(array)) == len(array):
backtracking_rec(array, dim)
array.pop()
'''
And here we build our experiment
'''
functions = [generate_test, backtracking_rec]
data_sizes = [3, 4, 5, 6, 7]
t = Texttable()
t.add_row(['Functions'] + data_sizes)
for function in functions:
row = [function.__name__]
for size in data_sizes:
t1 = time.perf_counter()
function([], size)
t2 = time.perf_counter()
row = row + [t2 - t1]
t.add_row(row)
print(t.draw())
@@ -0,0 +1,86 @@
"""
Created on Jan 10, 2017
@author: Arthur
"""
def compute_sum(b: list, sum_total: int):
"""
Compute the paid amount with the current candidate
"""
amount = 0
for coin in b:
nr_coins = (sum_total - amount) // coin
# If this is a candidate solution,
# we need to use at least 1 coin
if nr_coins == 0:
nr_coins = 1
amount += nr_coins * coin
return amount
def select_most_promising(c: list):
"""
Select the largest coin from the remaining
input:
c - candidate coins
Return the largest coin
"""
return max(c)
def acceptable(b: list, sum_total: int):
"""
Verify if a candidate solution is valid (we are not over amount)
"""
amount = compute_sum(b, sum_total)
return amount <= sum_total
def solution(b: list, sum_total: int):
"""
Verify if a candidate solution is an actual solution
(we are at the required amount)
"""
amount = compute_sum(b, sum_total)
return amount == sum_total
def build_solution_string(b: list, sum_total: int):
"""
Pretty print the solution
"""
sol_str = ''
amount = 0
for coin in b:
nr_coins = (sum_total - amount) // coin
sol_str += str(nr_coins) + '*' + str(coin)
amount += nr_coins * coin
if sum_total - amount > 0:
sol_str += " + "
return sol_str
def greedy(c: list, sum_total: int):
"""
Main function
"""
# The empty set is the candidate solution
b = []
while not solution(b, sum_total) and c != []:
# Select best candidate (local optimum)
candidate = select_most_promising(c)
c.remove(candidate)
# If the candidate is acceptable, add it
if acceptable(b + [candidate], sum_total):
b.append(candidate)
if solution(b, sum_total):
return build_solution_string(b, sum_total)
'''
Let's see how it works
'''
for money in range(1, 55):
print('Amount ' + str(money) + "=" + greedy([1, 5, 10], money))
@@ -0,0 +1,54 @@
"""
Created on Jan 11, 2017
@author: Arthur
"""
def longest_increasing_subsequence(A):
"""
Maximum length of subsequence recorded so far, as well as its end index
"""
max_length = 1
best_end = 0
indices_array = [1]
previous_indices = [-1]
for i in range(1, len(A)):
'''
The maximum length of the increasing subsequence ending at index i
'''
indices_array.append(1)
previous_indices.append(-1)
'''
The maximum length is increased by 1 if 'j' exists, so that:
A[j] < A[i] and the length of the subsequence would increase
'''
for j in range(i - 1, -1, -1):
if (indices_array[j] + 1 > indices_array[i]) and (A[j] < A[i]):
indices_array[i] = indices_array[j] + 1
previous_indices[i] = j
'''
Record the end index in the same go
'''
if indices_array[i] > max_length:
best_end = i
max_length = indices_array[i]
'''
Build the solution using the previous_indices list
'''
solution = [A[best_end]]
while previous_indices[best_end] != -1:
solution.append(A[previous_indices[best_end]])
best_end = previous_indices[best_end]
solution.reverse()
return solution
A = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
print(longest_increasing_subsequence(A))
@@ -0,0 +1,121 @@
"""
Created on Jan 6, 2017
@author: Arthur
"""
'''
Calculate the maximum sum of consecutive elements within an array
e.g. for array [-2, -5, 6, -2, -3, 1, 5, -6] the maximum sum is 7 (6-2-3+1+5, as the numbers must be consecutive)
'''
arr = [-2, -5, 6, -2, -3, 1, 5, -6]
'''
1. 1st naive implementation. What is the complexity?
'''
def max_subarray_sum_very_slow(array: list):
maximum = array[0]
for i in range(0, len(array)):
for j in range(i, len(array)):
s = 0
for k in range(i, j + 1):
s += array[k]
if s > maximum:
maximum = s
return maximum
'''
2. 2nd naive implementation. What is the complexity?
'''
def max_subarray_sum_slow(array: list):
maximum = array[0]
for i in range(0, len(array)):
s = 0
for j in range(i, len(array)):
s += array[j]
if s > maximum:
maximum = s
return maximum
'''
3. Divide & conquer implementation
'''
def max_crossing_sum(array: list, low, middle, high: int):
"""
Find the maximum possible temp sum in array such that array[middle] is part of it
input:
low, high - Low and high bounds, respectively
middle - the midpoint to consider
output:
The value of the maximum crossing temp_sum
"""
# Include elements on left of middle
temp_sum = 0
i = middle
left_sum = -10 ** 10
while i >= low:
temp_sum = temp_sum + array[i]
i -= 1
if temp_sum > left_sum:
left_sum = temp_sum
# Include elements on right of middle
temp_sum = 0
i = middle + 1
right_sum = -10 ** 10
while i <= high:
temp_sum = temp_sum + array[i]
i += 1
if temp_sum > right_sum:
right_sum = temp_sum
return left_sum + right_sum
def max_subarray_sum(array: list, low, high: int):
"""
Calculate the maximum subarray sum
input:
array - The input array
low, high - Low and high bounds, respectively
output:
The resulting sum value
"""
if low == high:
return array[low]
m = (low + high) // 2
return max(max_subarray_sum(array, low, m), max_subarray_sum(array, m + 1, high),
max_crossing_sum(array, low, m, high))
'''
4. Dynamic programming implementation.
'''
def max_subarray(array: list):
"""
We traverse the array once. For each index i in the array, we calculate the maximum subarray sum ending at that index.
If that sum is larger than the one previously recorded, we remember it (in the max_so_far variable)
"""
max_ending_here = max_so_far = array[0]
for x in array[1:]:
max_ending_here = max(x, max_ending_here + x)
max_so_far = max(max_so_far, max_ending_here)
return max_so_far
data = [-2, -5, 6, -2, -3, 1, 5, -6]
print(max_subarray(data))
@@ -0,0 +1,62 @@
#
# 0-1 Knapsack problem
#
W = 10
values_data = [5, 3, 10, 4, 2]
weights_data = [2, 3, 4, 3, 6]
#
# Version 1 - non dynamic programming
#
def knapsack(W: int, values, weights: list, index: int):
# Basic case - no more room or we've gone through all items
if W == 0 or index < 0:
return 0
# Item we are currently does not fit in knapsack
if weights[index] > W:
return knapsack(W, values, weights, index - 1)
else:
# Maximum between the decision of including it or not
return max(values[index] + knapsack(W - weights[index], values, weights, index - 1),
knapsack(W, values, weights, index - 1))
#
# Version 2 - dynamic programming
#
# Pretty-print auxiliary table, this should help follow the algorithm's steps
from texttable import Texttable
def pretty_print(v: list):
t = Texttable()
t.header(['X'] + list(range(W + 1)))
for i in range(len(v)):
t.add_row([i] + v[i])
print(t.draw())
def knapsack_dp(W: int, values, weights: list):
V = [[0 for x in range(W + 1)] for x in range(len(values) + 1)]
# Go over every item
for i in range(len(values) + 1):
# Go over every possible weight
for j in range(W + 1):
if i == 0 or j == 0:
V[i][j] = 0
elif weights[i - 1] > j:
# Current item does not fit, do not include it
V[i][j] = V[i - 1][j]
else:
# Maximum between skipping the item and including it
V[i][j] = max(V[i - 1][j], values[i - 1] + V[i - 1][j - weights[i - 1]])
pretty_print(V)
return V[len(values)][W]
print(knapsack_dp(W, values_data, weights_data))
@@ -0,0 +1,67 @@
"""
Created on Jan 11, 2017
@author: Arthur
"""
import math
'''
Function to get minimum number of trails needed in worst # case with n eggs and k floors
input:
n - Number of eggs remaining
k - Number of floors
output:
The number of minimum drops, if following optimal solution
credit - This code is contributed by Bhavya Jain
http://www.geeksforgeeks.org/dynamic-programming-set-11-egg-dropping-puzzle/
'''
def egg_drop(n, k):
"""
A 2D table where every egg_floor[i][j] will represent minimum
number of trials needed for i eggs and j floors.
"""
egg_floor = [[0 for x in range(k + 1)] for x in range(n + 1)]
'''
We need one trial for one floor and0 trials for 0 floors
'''
for i in range(1, n + 1):
egg_floor[i][1] = 1
egg_floor[i][0] = 0
'''
We always need j trials for one egg and j floors.
'''
for j in range(1, k + 1):
egg_floor[1][j] = j
'''
Fill rest of the entries in table using optimal substructure property
'''
for i in range(2, n + 1):
for j in range(2, k + 1):
egg_floor[i][j] = math.inf
for x in range(1, j + 1):
res = 1 + max(egg_floor[i - 1][x - 1], egg_floor[i][j - x])
if res < egg_floor[i][j]:
egg_floor[i][j] = res
'''
eggFloor[n][k] holds the result
'''
return egg_floor[n][k]
def test_egg_drop():
n = 2
k = 100
print("Minimum number of drops in the worst case with " + str(n) + " eggs and " + str(k) + " floors is " + str(
egg_drop(n, k)))
test_egg_drop()
@@ -0,0 +1,96 @@
"""
Created on Sep 29, 2016
@author: http://www.python-course.eu/global_vs_local_variables.php
"""
"""
NB!
Uncomment each of the sections separated by ### below,
one at a time and run them
"""
# def f():
# print(s)
#
#
# s = 'I hate spam'
# f()
#############################
# def f():
# # PyCharm complains that 's' shadows the name from the outer scope
# # Of course it does, that's the whole point :)
# s = "Me too."
# print(s)
#
#
# s = "I hate spam."
# f()
# print(s)
#############################
# def f():
# """
# Variables created or changed inside functions
# are local, unless declared global
# """
# print(s)
# s = "Me too."
# print(s)
#
#
# s = "I hate spam."
# f()
# print(s)
#############################
# def f():
# global s
# print(s)
# s = "That's clear."
# print(s)
#
#
# s = "Python is great again!"
# f()
# print(s)
#############################
# def f():
# s = "I am globally not known"
# print(s)
#
#
# f()
# print(s)
#############################
"""
Exercise:
What happens in the example below?
"""
# def foo(x, y):
# global a
# a = 42
# x,y = y,x
# b = 33
# b = 17
# c = 100
# print(a,b,x,y)
# '''
# NB!
# Learn to use the locals() and globals() functions to figure out what's what
#
# print(locals())
# print(globals())
# '''
#
# a,b,x,y = 1,15,3,4
# foo(17,4)
# print(a,b,x,y)
@@ -0,0 +1,13 @@
"""
Add the code below to https://pythontutor.com/visualize.html
Check the evolution of the call stack step by step
"""
def fib(n: int):
if n < 2:
return n
return fib(n - 2) + fib(n - 1)
print(fib(8))
@@ -0,0 +1,24 @@
"""
Created on Sep 29, 2016
@author: http://www.python-course.eu/passing_arguments.php
"""
"""
Example for parameter passing.
Use the locals() and globals() functions to better
understand what goes on
"""
def references_demo(x):
print("2. x=", x, " id=", id(x))
x = 42
print("3. x=", x, " id=", id(x))
x = 10
print("1. x=", x, " id=", id(x))
references_demo(x)
print("4. x=", x, " id=", id(x))
@@ -0,0 +1,30 @@
"""
Created on Sep 29, 2016
@author: http://www.python-course.eu/passing_arguments.php
"""
"""
Example for function side effects. Run the code below calling each of
the functions in turn. Examine the result
"""
def no_side_effect(lst):
print(2, id(lst), lst)
lst = [0, 1, 2, 3]
print(3, id(lst), lst)
def side_effect(lst):
print(5, id(lst), lst)
lst.append(999)
print(6, id(lst), lst)
my_list = [0, 1, 2]
print(1, id(my_list), my_list)
no_side_effect(my_list)
print(4, id(my_list), my_list)
side_effect(my_list)
print(7, id(my_list), my_list)
@@ -0,0 +1,157 @@
"""
Created on Sep 30, 2016
@author: Istvan Czibula / Arthur Molnar
"""
from math import gcd
"""
Create a calculator program for rational numbers with the following functionalities:
+ add a rational number to the calculator
u undo the last operation
x exit
"""
# ----------------------#
# Non-UI functions here #
# ----------------------#
# Functions to handle rational numbers
def create_rational(num, denom):
if denom == 0:
raise ValueError("Fraction denominator 0")
d = gcd(num, denom)
num = num // d
denom = denom // d
return [num, denom]
def get_numerator(q):
return q[0]
def get_denominator(q):
return q[1]
def add(q1, q2):
"""
Return the sum of two rational numbers.
input: q1, q2 - rational numbers operands
return the result
"""
return create_rational(get_numerator(q1) * get_denominator(q2) +
get_numerator(q2) * get_denominator(q1),
get_denominator(q1) * get_denominator(q2))
def to_str(q):
return str(get_numerator(q)) + "/" + str(get_denominator(q))
# Functions to handle the calculator
def create_calculator():
"""
Create a new calculator
post: the current total equal 0/1
return calculator status
"""
return {'val': create_rational(0, 1), 'history': []}
def get_total(calc):
return calc['val']
def set_total(calc, q):
calc['val'] = q
def add_calculator(calc, q):
"""
add a rational number to the current total
input: calc - calculator
q - rational number
output: q is added to the calculator
"""
calc_total = get_total(calc)
# Record the operation for undo
history = calc['history']
history.append(calc_total)
# Set current calculator value
set_total(calc, add(calc_total, q))
def undo_calc(calc):
"""
:param calc: The calculator to undo
:return: 0 if successful, -1 if nothing to undo
"""
history = calc['history']
if len(history) == 0:
# Return statement signals operation failure
return -1
calc['val'] = history[-1]
history.pop()
# Return statement signals operation success
return 0
# -----------------------#
# Only UI functions here #
# -----------------------#
def print_menu():
print("Calculator menu:")
print(" + add a rational number")
print(" u undo last operation")
print(" x close the calculator")
def add_calc_ui(calc):
"""
Read a rational number and add to the current total
calc - calculator
"""
m = input("Give nominator:")
n = input("Give denominator:")
add_calculator(calc, create_rational(int(m), int(n)))
def undo_calc_ui(calc):
success = undo_calc(calc)
if success < 0:
print('Nothing to undo!')
def run():
"""
Implement the user interface
"""
commands = {'+': add_calc_ui, 'u': undo_calc_ui}
calculator = create_calculator()
finish = False
while not finish:
print('Total= ' + to_str(get_total(calculator)))
print_menu()
m = input().strip()
if m in commands.keys():
commands[m](calculator)
elif m == 'x':
return
else:
print("Invalid command")
"""
Program entry point
"""
run()
@@ -0,0 +1,63 @@
"""
Created on Oct 21, 2018
@author: Arthur
"""
# FIXME Import statements should be at the top
'''
Switch the order of the import statements below. What happens?
'''
# import src.lecture.examples.ex28_modules.rational.rational_dict as rational
# import src.lecture.examples.ex28_modules.rational.rational_list as rational
# print(rational.create_rational(1,3))
'''
Code below works by directly referencing the create_rational function
'''
from src.lecture.examples.ex28_modules.rational import rational_dict
from src.lecture.examples.ex28_modules.rational import rational_list
print('Rational number as dict')
print(rational_dict.create_rational(1, 3))
print('Rational number as list')
print(rational_list.create_rational(1, 3))
"""
Switch the commented line below and check what happens
"""
from src.lecture.examples.ex28_modules.rational.rational_dict import create_rational, get_numerator, get_denominator
# from src.lecture.examples.ex28_modules.rational.rational_list import create_rational, get_numerator, get_denominator
def add(q1, q2):
"""
Function to add rational numbers that works with both list and dict representations
:param q1:
:param q2:
:return:
"""
return create_rational(get_numerator(q1) * get_denominator(q2) + get_numerator(q2) * get_denominator(q1),
get_denominator(q1) * get_denominator(q2))
q1 = create_rational(1, 2)
q2 = create_rational(3, 4)
print(add(q1, q2))
"""
Let's see what the dir(...) and help(...) functions do
Switch between the commented lines and check the output
"""
print('-' * 50)
print(dir(rational_dict))
print('-' * 50)
print(help(rational_dict))
# print('-' * 50)
# print(dir(rational_list))
# print('-' * 50)
# print(help(rational_list))
@@ -0,0 +1,23 @@
"""
Docstring for Rational number represented using a dict
Created on Oct 21, 2018
@author: Arthur
"""
import math
def create_rational(nom, den=1):
if den == 0:
raise ValueError("Denominator cannot be 0")
d = math.gcd(nom, den)
return {"nom": nom // d, "denom": den // d}
def get_numerator(q):
return q["nom"]
def get_denominator(q):
return q["denom"]
@@ -0,0 +1,22 @@
"""
Docstring for Rational number represented using a list
Created on Oct 21, 2018
@author: Arthur
"""
from math import gcd
def create_rational(nom, den=1):
if den == 0:
raise ValueError("Denominator cannot be 0")
d = gcd(nom, den)
return [nom // d, den // d]
def get_numerator(q):
return q[0]
def get_denominator(q):
return q[1]
@@ -0,0 +1,83 @@
"""
Calculator module, contains functions related to the calculator
"""
from src.lecture.examples.ex29_modular_calc.domain.rational import add, create_rational
'''
Calculator state
'''
def get_calculator_total(calc):
return calc[0]
def set_calculator_total(calc, total):
calc[0] = total
def get_undo_list(calc):
return calc[1]
def reset_calc():
return [create_rational(0), []]
"""
Other calculator operations
"""
def undo(calc):
"""
Undo the last user operation
post: restore the previous current total
"""
if len(get_undo_list(calc)) == 0:
raise ValueError("No more undos!")
set_calculator_total(calc, get_undo_list(calc)[-1])
get_undo_list(calc).pop()
def calculator_add(calc, q):
"""
add a rational number to the current total
a, b integer number, b<>0
post: add a/b to the current total
"""
# add the current total to the undo list
get_undo_list(calc).append(get_calculator_total(calc))
set_calculator_total(calc, add(get_calculator_total(calc), q))
def test_calculator_add():
"""
Test function for calculator_add
"""
c = reset_calc()
assert get_calculator_total(c) == create_rational(0)
calculator_add(c, create_rational(1, 2))
assert get_calculator_total(c) == create_rational(1, 2)
calculator_add(c, create_rational(1, 3))
assert get_calculator_total(c) == create_rational(5, 6)
calculator_add(c, create_rational(1, 6))
assert get_calculator_total(c) == create_rational(1)
def test_undo():
"""
Test function for undo
"""
c = reset_calc()
calculator_add(c, create_rational(1, 3))
undo(c)
assert get_calculator_total(c) == create_rational(0)
c = reset_calc()
calculator_add(c, create_rational(1, 3))
calculator_add(c, create_rational(1, 3))
calculator_add(c, create_rational(1, 3))
undo(c)
assert get_calculator_total(c) == create_rational(2, 3)
@@ -0,0 +1,36 @@
"""
Utility module to work with rational numbers
"""
from math import gcd
def create_rational(nom, den=1):
if den == 0:
raise ValueError("Denominator cannot be 0")
d = gcd(nom, den)
return [nom // d, den // d]
def get_numerator(q):
return q[0]
def get_denominator(q):
return q[1]
def add(q1, q2):
"""
Return the sum of two rational numbers.
q1, q2 the rational numbers
return the sum
"""
return create_rational(get_numerator(q1) * get_denominator(q2) + get_numerator(q2) * get_denominator(q1), get_denominator(q1) * get_denominator(q2))
def test_add():
"""
Test function for rational_add
"""
assert add(create_rational(1, 2), create_rational(1, 3)) == create_rational(5, 6)
assert add(create_rational(1, 2), create_rational(1, 2)) == create_rational(1)
@@ -0,0 +1,10 @@
"""
Start the calculator application
"""
from src.lecture.examples.ex29_modular_calc.ui.console import run
# invoke the run method from the module ui.console
run()
@@ -0,0 +1,58 @@
"""
The user interface of the calculator
Contains functions related to the user interaction (console)
"""
from src.lecture.examples.ex29_modular_calc.domain.calculator import \
calculator_add, undo, get_calculator_total, get_undo_list, set_calculator_total, reset_calc
from src.lecture.examples.ex29_modular_calc.domain.rational import create_rational
def print_menu():
"""
Print out the main menu of the calculator
"""
print("Calculator menu:")
print(" + for adding a rational number")
print(" c to clear the calculator")
print(" u to undo the last operation")
print(" x to close the calculator")
def print_current(calc):
"""
Print the current total
"""
print("Total:", get_calculator_total(calc))
def run():
"""
Implement the user interface
"""
calc = reset_calc()
finish = False
print_current(calc)
while not finish:
print_menu()
m = input().strip()
if m == 'x':
finish = True
elif m == '+':
m = input("Give numerator:")
n = input("Give denominator:")
try:
calculator_add(calc, create_rational(int(m), int(n)))
print_current(calc)
except ValueError:
print("Enter integers for m, n, with not null n")
elif m == 'c':
calc = reset_calc()
print_current(calc)
elif m == 'u':
try:
undo(calc)
print_current(calc)
except ValueError as ve:
print(ve)
else:
print("Invalid command")
@@ -0,0 +1,48 @@
"""
Created on Oct 25, 2016
@author: Arthur
Part of this code is taken from:
https://docs.python.org/3/tutorial/errors.html
"""
"""
Try to enter various (non-integer) values at the prompt given by the code snippet below
"""
# while True:
# try:
# x = int(input("Please enter a number: "))
# break
# except ValueError:
# print("Oops! That was no valid number. Try again...")
'''
Try running the program below, and see what happens on different inputs
NB!
This is not a correct way to implement error handling, it's just example code :)
'''
end = False
while not end:
try:
val = input("T - TypeError, V - ValueErorr, K - KeyError, 0 - Exit")
if val.lower() == 't':
raise TypeError("Well this is a type error!")
elif val.lower() == 'k':
raise KeyError("Your program just raised a key error!")
elif val.lower() == 'v':
raise ValueError("value error!")
else:
end = True
except KeyError as ke:
print("There was a " + str(type(ke)) + " " + str(ke))
except ValueError as ve:
print("There was a ValueError")
except:
print("There was an exception that was not handled above!")
else:
print("There was no error")
@@ -0,0 +1,60 @@
"""
Created on Nov 1, 2016
@author: Arthur
"""
class FirstClass:
pass
class SecondClass:
def __init__(self):
self.test_one = "Test One"
self._test_two = "Test Two"
self.__test_three = "Test Three"
x = FirstClass()
'''
What is the type of x ?
'''
print(type(x))
'''
An empty class, such as FirstClass can be used as a Pascal record or a C struct
'''
x.name = "Alice"
x.salary = 100
x.salary += 20
print("Name of x=", x.name)
print("Salary of x=", x.salary)
y = FirstClass()
y.name = "Bob"
y.salary = 1000
print("Name of y=", y.name)
print("Salary of y=", y.salary)
'''
What happens if we add another field on-the-fly?
'''
y.hello = "Say hello"
print(y.hello)
# Field 'hello' is not a part of instance 'x'
# print(x.hello)
'''
How about the more complex, SecondClass example?
'''
obj = SecondClass()
print(obj.test_one)
# print(obj._test_two)
'''
This is Python's name mangling at work
'''
# print(obj.__test_three)
# print(obj._SecondClass__test_three)
@@ -0,0 +1,71 @@
"""
Created on Nov 1, 2016
@author: Arthur
"""
from math import gcd
class rational:
"""
Abstract data type rational number
Domain: {a/b where a,b integer numbers, b!=0, greatest common divisor a, b =1}
"""
def __init__(self, a, b=1):
"""
Initialise a rational number
a,b integer numbers
"""
if b == 0:
raise ValueError("Denominator cannot be 0!")
d = gcd(a, b)
self.__nominator = a // d
self.__denominator = b // d
def get_numerator(self):
"""
Getter method
return the denominator of the rational number
"""
return self.__denominator
def get_denominator(self):
""""
Getter method
return the nominator of the method
"""
return self.__nominator
def add(self, a):
"""
add 2 rational numbers
a is a rational number
Return the sum of two rational numbers as an instance of rational number.
Raise ValueError if the denominators are zero.
"""
return rational(self.get_denominator() * a.get_numerator() + self.get_numerator() * a.get_denominator(),
self.get_numerator() * a.get_numerator())
def test_rational_add():
r1 = rational(1, 2)
r2 = rational(1, 3)
r3 = r1.add(r2)
assert r3.get_denominator() == 5
assert r3.get_numerator() == 6
def test_create():
r1 = rational(1, 3) # create the rational number 1/3
assert r1.get_denominator() == 1
assert r1.get_numerator() == 3
r1 = rational(4, 3) # create the rational number 4/3
assert r1.get_denominator() == 4
assert r1.get_numerator() == 3
if __name__ == "__main__":
test_create()
test_rational_add()
@@ -0,0 +1,169 @@
"""
Created on Nov 1, 2016
@author: Arthur
"""
from math import gcd
class rational:
# Class field is shared by all instances
number_of_instances = 0
"""
Abstract data type rational numbers
Domain: {a/b where a,b integer numbers, b!=0, greatest common divisor a, b =1}
"""
def __init__(self, a, b=1):
"""
Initialise a rational number
a,b int numbers
"""
if b == 0:
raise ValueError("Denominator cannot be 0!")
rational.number_of_instances += 1
d = gcd(a, b)
self.__nominator = a // d
self.__denominator = b // d
def get_denominator(self):
"""
Getter method
return the denominator of the rational number
"""
return self.__denominator
def get_numerator(self):
""""
Getter method
return the nominator of the method
"""
return self.__nominator
@staticmethod
def get_total_number_of_instances():
"""
Get the number of created rational number instances
return integer
"""
return rational.number_of_instances
def add(self, a):
"""
add 2 rational numbers
a is a rational number
Return the sum of two rational numbers as an instance of rational number.
Raise ValueError if the denominators are zero.
"""
if self.get_denominator() == 0 or a.get_denominator() == 0:
raise ValueError("0 denominator not allowed")
return rational(self.get_numerator() * a.get_denominator() + self.get_denominator() * a.get_numerator(),
self.get_denominator() * a.get_denominator())
def __add__(self, other):
"""
Overload + operator
other - rational number
return a rational number, the sum of self and other
"""
return self.add(other)
def get_float(self):
"""
Get the real value for the rational number
return a float
"""
return float(self.get_numerator()) / self.get_denominator()
def __lt__(self, ot):
"""
Compare 2 rational numbers (less than)
self the current instance
ot a rational number
return True if self<ot, False otherwise
"""
return self.get_float() < ot.get_float()
def __str__(self):
"""
provide a string representation for the rational number
return a string
"""
return str(self.__nominator) + "/" + str(self.__denominator)
def __eq__(self, other):
"""
Verify if 2 rational numbers are equals
other - a rational number
return True if the instance is equal with other
"""
return self.__nominator == other.__nominator and self.__denominator == other.__denominator
def test_rational_add():
r1 = rational(1, 2)
r2 = rational(1, 3)
r3 = r1.add(r2)
assert r3.get_numerator() == 5
assert r3.get_denominator() == 6
assert r3 == rational(5, 6)
def test_equal():
"""
test function for testing == for 2 rational numbers
"""
r1 = rational(1, 3)
assert r1 == r1
r2 = rational(1, 3)
assert r1 == r2
r1 = rational(1, 3)
r1 = r1.add(rational(2, 3))
r2 = rational(1, 1)
assert r1 == r2
def test_compare_operator():
"""
Test function for < >
"""
r1 = rational(1, 3)
r2 = rational(2, 3)
assert r2 > r1
assert r1 < r2
def test_add_operator():
"""
Test function for the + operator
"""
r1 = rational(1, 3)
r2 = rational(1, 3)
r3 = r1 + r2
assert r3 == rational(2, 3)
def test_create():
"""
Test function for creating rational numbers
"""
r1 = rational(1, 3) # create the rational number 1/3
assert r1.get_numerator() == 1
assert r1.get_denominator() == 3
r1 = rational(4, 3) # create the rational number 4/3
assert r1.get_numerator() == 4
assert r1.get_denominator() == 3
if __name__ == "__main__":
test_create()
test_equal()
test_rational_add()
test_add_operator()
test_compare_operator()
'''
How many actual rational numbers have we created?
'''
print("Total numer of instances ", rational.get_total_number_of_instances())
@@ -0,0 +1,52 @@
"""
Created on Nov 2, 2016
@author: Arthur
"""
class student:
# Class field is shared by all instances
__studentCount = 0
def __init__(self, name="Anonymous"):
self.__name = name
'''
NB!
Make sure to prefix the attribute name with:
- class name, or
- type(self)
'''
student.__studentCount += 1
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
@staticmethod
def get_student_count():
return student.__studentCount
if __name__ == "__main__":
'''
We create some students
'''
s1 = student()
s2 = student()
s1.set_name("Anna")
s2.set_name("Carla")
print(s1.get_name())
print(s2.get_name())
print(student.get_student_count())
'''
What happens here?
'''
# print(student.__studentCount)
@@ -0,0 +1,37 @@
from lecture.examples.ex35_rational_calc.domain import Rational
class Calculator:
def __init__(self):
self._total = Rational(0)
self._history = []
@property
def get_total(self):
# Total is a read-only property
return self._total
def add(self, r):
"""
Add a number to the calculator. Adding 0 will not create an undo point
param:
r - the number to add
"""
if r == Rational(0):
return
# Record for undo
self._history.append(self._total)
# Add the value to calculator total
self._total += r
def undo(self):
if len(self._history) == 0:
raise ValueError('No more undo steps!')
self._total = self._history.pop()
@staticmethod
def get_number_count():
return Rational.get_total_number_of_instances()
def reset(self):
self._total = Rational(0)
@@ -0,0 +1,165 @@
"""
Created on Oct 30, 2019
@author: Arthur
"""
from math import gcd
class Rational:
"""
Abstract data type rational numbers
Domain: {a/b where a,b integer numbers, b!=0, greatest common divisor a, b =1}
"""
# Class field is shared by all instances
_number_of_instances = 0
def __init__(self, numerator, denominator=1):
"""
Initialise a rational number
numerator, denominator integers, denominator non-zero
"""
if denominator == 0:
raise ValueError("Denominator cannot be 0!")
d = gcd(numerator, denominator)
self.num = numerator // d
self.denom = denominator // d
Rational._number_of_instances += 1
@property
def num(self):
return self._numerator
@num.setter
def num(self, value):
self._numerator = value
@property
def denom(self):
return self._denominator
@denom.setter
def denom(self, value):
if value == 0:
raise ValueError("Denominator cannot be 0!")
self._denominator = value
@staticmethod
def get_total_number_of_instances():
"""
Get the number of created rational number instances
return integer
"""
return Rational._number_of_instances
def add(self, a):
"""
add 2 rational numbers
a is a rational number
Return the sum of two rational numbers as an instance of rational number.
Raise ValueError if the denominators are zero.
"""
return Rational(self.num * a.denom + self.denom * a.num, self.denom * a.denom)
def __add__(self, other):
"""
Overload + operator
other - rational number
return a rational number, the sum of self and other
"""
return self.add(other)
def get_float(self):
"""
Get the real value for the rational number
return a float
"""
return float(self.num) / self.denom
def __lt__(self, ot):
"""
Compare 2 rational numbers (less than)
self the current instance
ot a rational number
return True if self<ot, False otherwise
"""
return self.get_float() < ot.get_float()
def __str__(self):
"""
provide a string representation for the rational number
return a string
"""
return str(self.num) + "/" + str(self.denom)
def __eq__(self, other):
"""
Verify if 2 rational numbers are equals
other - a rational number
return True if the instance is equal with other
"""
return self.num == other.num and self.denom == other.denom
def test_rational_add():
r1 = Rational(1, 2)
r2 = Rational(1, 3)
r3 = r1.add(r2)
assert r3.num == 5
assert r3.denom == 6
assert r3 == Rational(5, 6)
def test_equal():
"""
test function for testing == for 2 rational numbers
"""
r1 = Rational(1, 3)
assert r1 == r1
r2 = Rational(1, 3)
assert r1 == r2
r1 = Rational(1, 3)
r1 = r1.add(Rational(2, 3))
r2 = Rational(1, 1)
assert r1 == r2
def test_compare_operator():
"""
Test function for < >
"""
r1 = Rational(1, 3)
r2 = Rational(2, 3)
assert r2 > r1
assert r1 < r2
def test_add_operator():
"""
Test function for the + operator
"""
r1 = Rational(1, 3)
r2 = Rational(1, 3)
r3 = r1 + r2
assert r3 == Rational(2, 3)
def test_create():
"""
Test function for creating rational numbers
"""
r1 = Rational(1, 3) # create the rational number 1/3
assert r1.num == 1
assert r1.denom == 3
r1 = Rational(4, 3) # create the rational number 4/3
assert r1.num == 4
assert r1.denom == 3
test_create()
test_equal()
test_rational_add()
test_add_operator()
test_compare_operator()
@@ -0,0 +1,62 @@
from lecture.examples.ex35_rational_calc.calculator import Calculator
from lecture.examples.ex35_rational_calc.domain import Rational
class UI:
def __init__(self, calculator):
self._commands = {'+': self.add_number, 'c': self.clear, 'u': self.undo, '?': self.count}
self._calculator = calculator
def _print_menu(self):
"""
Print out the calculator menu
"""
print("Calculator:")
print(" + for adding a rational number")
print(" c to clear the calculator")
print(" u to undo the last operation")
print(" ? to count the rational numbers created")
print(" x to close the calculator")
def add_number(self):
a = int(input("Give numerator:"))
b = int(input("Give denominator:"))
q = Rational(a, b)
self._calculator.add(q)
def undo(self):
self._calculator.undo()
def clear(self):
self._calculator.reset()
def count(self):
print("Number of rational instances created so far: " + str(self._calculator.get_number_count()))
def start(self):
self._calculator.reset()
while True:
self._print_menu()
print("Total: " + str(self._calculator.get_total))
_command = input().strip().lower()
# Exit program
if _command == 'x':
return
# Invalid option
if _command not in self._commands:
print("Bad command")
# Run user option
try:
self._commands[_command]()
except ValueError as ve:
print("error - " + str(ve))
if __name__ == "__main__":
# Initialize the calculator class
calc = Calculator()
# This allows us to use the UI class with any implementation of the Calculator
ui = UI(calc)
# Start the UI
ui.start()
@@ -0,0 +1,76 @@
"""
Created on Nov 22, 2016
@author: Arthur
"""
class Person:
def __init__(self, person_id, family_name, given_name):
self._personId = person_id
self._familyName = family_name
self._givenName = given_name
@property
def id(self):
return self._personId
@property
def family_name(self):
return self._familyName
@property
def given_name(self):
return self._givenName
def __str__(self):
return str(self._personId) + " - " + self._familyName + " " + self._givenName
def write_text_file(file_name, persons):
f = open(file_name, "w")
try:
for p in persons:
person_str = str(p.id) + ";" + p.family_name + ";" + p.given_name + "\n"
f.write(person_str)
f.close()
except Exception as e:
print("An error occurred -" + str(e))
def read_text_file(file_name):
result = []
try:
f = open(file_name, "r")
line = f.readline().strip()
while len(line) > 0:
line = line.split(";")
result.append(Person(int(line[0]), line[1], line[2]))
line = f.readline().strip()
f.close()
except IOError as e:
"""
Here we 'log' the error, and throw it to the outer layers
"""
print("An error occured - " + str(e))
raise e
return result
if __name__ == "__main__":
"""
Initialize a list of objects
"""
persons = [Person(1, "Pop", "Anca"), Person(2, "Morariu", "Sergiu"), Person(3, "Moldovean", "Iuliu")]
"""
Write it to a text file
"""
write_text_file("persons.txt", persons)
"""
Read it back and see what we have
"""
for p in read_text_file("persons.txt"):
print(p)
@@ -0,0 +1,69 @@
"""
Created on Nov 22, 2016
@author: Arthur
"""
import pickle
class Person:
def __init__(self, person_id, family_name, given_name):
self._personId = person_id
self._familyName = family_name
self._givenName = given_name
@property
def id(self):
return self._personId
@property
def family_name(self):
return self._familyName
@property
def given_name(self):
return self._givenName
def __str__(self):
return str(self._personId) + " - " + self._familyName + " " + self._givenName
def write_binary_file(file_name, persons):
f = open(file_name, "wb")
pickle.dump(persons, f)
f.close()
def read_binary_file(file_name):
try:
f = open(file_name, "rb")
return pickle.load(f)
except EOFError:
"""
This is raised if input file is empty
"""
return []
except IOError as e:
"""
Here we 'log' the error, and throw it to the outer layers
"""
print("An error occured - " + str(e))
raise e
if __name__ == "__main__":
"""
Initialize a list of objects
"""
persons = [Person(1, "Pop", "Anca"), Person(2, "Morariu", "Sergiu"), Person(3, "Moldovean", "Iuliu")]
"""
Write it to a text file
"""
write_binary_file("persons.pickle", persons)
"""
Read it back and see what we have
"""
for p in read_binary_file("persons.pickle"):
print(p)
@@ -0,0 +1,128 @@
"""
Created on Nov 23, 2016
@author: Arthur
"""
class Shape:
def __init__(self, color):
# print("building a shape")
self._color = color
@property
def color(self):
return self._color
@color.setter
def color(self, new_color):
self._color = new_color
@property
def area(self):
return 0
def __str__(self):
return "a " + self.color + " shape"
class Rectangle(Shape):
def __init__(self, width, height, color):
Shape.__init__(self, color)
# print("building a rectangle")
self._width = width
self._height = height
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def area(self):
return self._width * self._height
def __str__(self):
return "a " + self.color + " rectangle"
class Square(Rectangle):
def __init__(self, side, color):
# print("building a square")
Rectangle.__init__(self, side, side, color)
@property
def side(self):
return self._width
def __str__(self):
return "a " + self.color + " square"
class Ellipse(Shape):
def __init__(self, major, minor, color):
Shape.__init__(self, color)
# print("building an ellipse")
self._major = major
self._minor = minor
@property
def major(self):
return self._major
@property
def minor(self):
return self._minor
@property
def area(self):
return 3.14 * self._minor * self._major
def __str__(self):
return "a " + self.color + " ellipse"
class Circle(Ellipse):
def __init__(self, radius, color):
Ellipse.__init__(self, radius, radius, color)
# print("building a circle")
@property
def radius(self):
return self.major
@property
def area(self):
return 3.14 * self.radius ** 2
def __str__(self):
return "a " + self.color + " circle"
if __name__ == "__main__":
shape = Shape("red")
print(str(shape) + ", area is = " + str(shape.area))
"""
The rectangle 'is a' shape
"""
rectangle = Rectangle(5, 2, "blue")
print(str(rectangle) + ", area is = " + str(rectangle.area))
"""
The square 'is a' particular rectangle
"""
square = Square(3, "green")
print(str(square) + ", area is =" + str(square.area))
ellipse = Ellipse(10, 6, "pink")
print(str(ellipse) + ", area is =" + str(ellipse.area))
"""
The circle 'is a' particular ellipse
"""
circle = Circle(8, "magenta")
print(str(circle) + ", area is =" + str(circle.area))
@@ -0,0 +1,73 @@
"""
Created on Nov 14, 2016
@author: Arthur
"""
"""
A function that we want tested
"""
def is_prime(nr):
"""
Verify if a number is prime
return True if nr is prime, False otherwise
raise ValueError if nr <= 0
"""
if nr <= 0:
raise ValueError("nr needs to be positive")
if nr == 1:
return False
if nr <= 3:
return True
for i in range(2, nr):
if nr % i == 0:
return False
return True
"""
Black-box testing assumes we only have its specification
"""
def test_is_prime_black_box():
for i in range(-100, 1):
try:
is_prime(i)
assert False
except ValueError:
pass
primes = [2, 3, 5, 7, 11, 13, 17, 19]
for i in range(2, 20):
assert is_prime(i) == (i in primes), "this is the value where it fails: " + str(i)
"""
White-box testing - we can see the source code, so we only write the required test cases
"""
def test_is_prime_white_box():
try:
is_prime(-5)
assert False
except ValueError:
pass
assert is_prime(1) is False, 1
assert is_prime(2) is True, 2
assert is_prime(3) is True, 3
assert is_prime(6) is False, 4
assert is_prime(7) is True, 7
assert is_prime(8) is False, 8
"""
Let's run the tests - they should work regardless
"""
if __name__ == "__main__":
test_is_prime_black_box()
test_is_prime_white_box()
@@ -0,0 +1,103 @@
"""
Created on Nov 14, 2016
@author: Arthur
"""
import unittest
"""
A function that we want tested
"""
def is_prime(nr):
"""
Verify if a number is prime
return True if nr is prime, False otherwise
raise ValueError if nr <= 0
"""
if nr <= 0:
raise ValueError("nr needs to be positive")
if nr == 1:
return False
if nr <= 3:
return True
for i in range(2, nr):
if nr % i == 0:
return False
return True
"""
Blackbox unit test for PyUnit
"""
class IsPrimeBlackBoxTest(unittest.TestCase):
"""
This function is called before any test cases.
We can add initialization code common to all methods here
(e.g. reading an input file)
"""
def setUp(self):
unittest.TestCase.setUp(self)
"""
This function is called after all test function are executed
It's like the opposite of setUp, here you dismantle the test scaffolding
"""
def tearDown(self):
unittest.TestCase.tearDown(self)
def test_is_prime_black_box(self):
for i in range(-100, 1):
try:
is_prime(i)
assert False
except ValueError:
pass
primes = [2, 3, 5, 7, 11, 13, 17, 19]
for i in range(2, 20):
assert is_prime(i) == (i in primes), "this is the value where it fails: " + str(i)
"""
White-box testing - we can see the source code, so we only write the required test cases
"""
class IsPrimeWhiteBoxTest(unittest.TestCase):
"""
This function is called before any test cases.
We can add initialization code common to all methods here
(e.g. reading an input file)
"""
def setUp(self):
unittest.TestCase.setUp(self)
"""
This function is called after all test function are executed
It's like the opposite of setUp, here you dismantle the test scaffolding
"""
def tearDown(self):
unittest.TestCase.tearDown(self)
def test_is_prime_white_box(self):
try:
is_prime(-5)
assert False
except ValueError:
pass
assert is_prime(1) is False, 1
assert is_prime(2) is True, 2
assert is_prime(3) is True, 3
assert is_prime(6) is False, 4
assert is_prime(7) is True, 7
assert is_prime(8) is False, 8
@@ -0,0 +1,78 @@
"""
Created on Sep 30, 2016
@author: Arthur
"""
'''
1. Write a test function
'''
def test_gcd():
assert gcd(0, 2) == 2
assert gcd(2, 0) == 2
assert gcd(1, 2) == 1
assert gcd(2, 1) == 1
assert gcd(6, 2) == 2
assert gcd(6, 3) == 3
assert gcd(21, 2) == 1
assert gcd(2, 21) == 1
assert gcd(210, 15) == 15
assert gcd(15, 210) == 15
'''
Write a first version of the function
'''
def gcd(a, b):
"""
Calculates the GCD of a and b
a,b - input integers, a,b >= 0
Returns the GCD of positive integers a and b
"""
pass
'''
2. Run the test function.
NB!
If it passes, you've done something wrong :-)
'''
test_gcd()
'''
3. Write the gcd function according to its specification
'''
# def gcd(a, b):
# '''
# Calculates the GCD of a and b
# a,b - input integers, a,b >= 0
# Returns the GCD of positive integers a and b
# '''
# if a == 0:
# return b
# if b == 0:
# return a
# while a != b:
# if a > b:
# a -= b
# else:
# b -= a
# return a
'''
4. Run the test(s) again
NB!
If they fail, you've done something wrong :-<
'''
# test_gcd()
'''
5. Refactor / optimize the function
'''
@@ -0,0 +1,128 @@
"""
How to apply TDD - a simple example
Problem statement:
Every even number larger or equal to 4 can be expressed as the sum of two primes (Goldbach conjecture). For a given even
number, determine the two primes that express it.
"""
'''
0. Think
- what function do you need?
- what are its specifications?
'''
'''
1. Write the function specification
'''
def is_prime(n):
"""
Tests whether the provided parameter is a prime number
input: n - the number to be tested
output: True if 'n' is prime
"""
pass
def find_goldbach_primes(n):
"""
Returns the two primes that sum to the given even 'n'
input: n - an even natural number
output: return the smallest of the two primes that sum to 'n'
error: raises a ValueError if input is not an even natural number
"""
pass
'''
2. Write a test for it
'''
def test_is_prime():
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
for i in range(-100, 50):
assert is_prime(i) == (i in primes)
def test_find_goldbach_primes(n):
pairs = [(4, 2), (8, 3), (12, 5), (16, 5)]
for p in pairs:
assert p[1] == find_goldbach_primes(p[0])
for i in range(-10, 2):
try:
find_goldbach_primes(i)
assert False
except ValueError:
assert True
for i in range(1, 100, 2):
try:
find_goldbach_primes(i)
assert False
except ValueError:
assert True
'''
3. Run the test. It will fail because the isPrime(n) function does not yet do anything useful.
'''
'''
4. Write the isPrime(n) function so that the test passes
'''
def is_prime(n):
"""
Tests whether the provided parameter is a prime number
input: n - the number to be tested
output: True if 'n' is prime
"""
if n < 2:
return False
for i in range(2, n // 2 + 1):
if n % i == 0:
return False
return True
def find_goldbach_primes(n):
"""
Returns the two primes that sum to the given even 'n'
input: n - an even natural number
output: return the smallest of the two primes that sum to 'n'
error: raises a ValueError if input is not an even natural number
"""
if n < 2 or n % 2 == 1:
raise ValueError("Goldbach conjecture requires an even parameter!")
i = 2
while i < n:
if is_prime(i) and is_prime(n - i):
return i
'''
5. Refactor the code. Make it easier to read, faster, ...
'''
def is_prime(n):
"""
Tests whether the provided parameter is a prime number
input: n - the number to be tested
output: True if 'n' is prime
"""
if n < 2:
return False
i = 2
while i * i < n:
if n % i == 0:
return False
return True
'''
6. Run the tests again! Make sure that they pass.
'''
@@ -0,0 +1,237 @@
"""
Examples for possible refactorings
Slides in Lecture 08 - Program Testing. Refactoring
"""
'''
The name of a method does not reveal its purpose
Sequence of conditional tests with the same result
The same fragment of code is in all branches of a conditional expression
'''
def maxx(lista, param):
t = ""
# print(param[0])
# gas,heating,electricity,water,other
max1 = 0
for i in range(0, len(lista)):
cmd = lista[i]
if cmd[0] == param[0] and cmd[1] == 'gas' and int(cmd[2]) > int(max1):
max1 = cmd[2]
t += "The maximum amount for gas is : " + str(max1) + "\n"
max1 = 0
for i in range(0, len(lista)):
cmd = lista[i]
if cmd[0] == param[0] and cmd[1] == 'water' and int(cmd[2]) > int(max1):
max1 = cmd[2]
t += "The maximum amount for water is : " + str(max1) + "\n"
max1 = 0
for i in range(0, len(lista)):
cmd = lista[i]
if cmd[0] == param[0] and cmd[1] == 'electricity' and int(cmd[2]) > int(max1):
max1 = cmd[2]
t += "The maximum amount for electricity is : " + str(max1) + "\n"
max1 = 0
for i in range(0, len(lista)):
cmd = lista[i]
if cmd[0] == param[0] and cmd[1] == 'heating' and int(cmd[2]) > int(max1):
max1 = cmd[2]
t += "The maximum amount for heating is : " + str(max1) + "\n"
max1 = 0
for i in range(0, len(lista)):
cmd = lista[i]
if cmd[0] == param[0] and cmd[1] == 'other' and int(cmd[2]) > int(max1):
max1 = cmd[2]
t += "The maximum amount for other expenses is : " + str(max1) + "\n"
return t
'''
Code fragment that can be grouped together
Parameter 'param' is not used
'''
def sortAP(lista, param):
ap = []
for i in range(0, len(lista)):
if cautAP(ap, lista[i][0]) == 1:
ap.append(lista[i][0])
for i in range(0, len(ap)):
s = 0
for j in range(0, len(lista)):
if lista[j][0] == ap[i]:
s += int(lista[j][2])
v = ap[i]
ap[i] = (v, s)
t = sorted(ap, key=lambda ap: ap[1])
return t
def sortT(lista, param):
ap = []
for i in range(0, len(lista)):
if cautAP(ap, lista[i][1]) == 1:
ap.append(lista[i][1])
for i in range(0, len(ap)):
s = 0
for j in range(0, len(lista)):
if lista[j][1] == ap[i]:
s += int(lista[j][2])
v = ap[i]
ap[i] = (v, s)
t = sorted(ap, key=lambda ap: ap[1])
return t
'''
Code fragment that can be grouped together.
Turn the fragment into a method whose name explains the purpose of the method
'''
def run_cmd_menu():
cmds = {"insert": ui_insert_expense, "list": ui_print_all, "add": ui_add_expense, "night": night,
"remove": ui_delete, "first": first_expenses, "sum": ui_sum_type, "undo": ui_undo, "max": ui_max_day,
"sort": ui_sort_day}
current_day = 1
expenses = []
backup_list = []
count = 0
first_expenses(expenses)
# backup(expenses, backup_list)
first_len = len(expenses)
while True:
(cmd, args) = read_command()
if cmd == "exit":
break
if cmd == "night":
current_day = night(current_day)
if cmd == "remove":
count = len(expenses)
ui_delete(expenses, args, backup_list)
if cmd == "list":
if len(args) == 0:
ui_print_all(expenses)
if len(args) == 1:
if is_int(args[0]):
ui_print_day(expenses, int(args[0]))
else:
ui_print_type(expenses, args[0])
if len(args) == 3:
if args[1] == "<":
print_smaller(expenses, args[0], args[2])
if args[1] == "=":
print_equals(expenses, args[0], args[2])
if args[1] == ">":
print_bigger(expenses, args[0], args[2])
if cmd == "filter":
count = len(expenses)
# backup(expenses, backup_list)
backup_list.extend(expenses)
if len(args) == 1:
filter_type(expenses, args[0])
if len(args) == 3:
if args[1] == "<":
filter_smaller(expenses, args[0], args[2])
if args[1] == "=":
filter_equals(expenses, args[0], args[2])
if args[1] == ">":
filter_bigger(expenses, args[0], args[2])
if cmd == "sort":
if is_int(args[0]):
ui_sort_day(expenses, int(args[0]))
else:
ui_sort_type(expenses, args[0])
if cmd == "add":
count = len(expenses)
ui_add_expense(expenses, backup_list, current_day, args)
if cmd == "undo":
ui_undo(expenses, backup_list, count, first_len)
count = len(backup_list)
if cmd == "backup":
ui_print_backup(backup_list)
if cmd == "insert":
count = len(backup_list)
backup_list.extend(expenses)
ui_insert_expense(expenses, *args, backup_list)
try:
if cmd != "insert" and cmd != "backup" and cmd != "night" and cmd != "remove" and cmd != "list" and cmd != "add" and cmd != "undo" and cmd != "sort" and cmd != "filter":
cmds[cmd](expenses, *args)
except ValueError as ve:
print("invalid input.", ve)
except KeyError as ke:
print("option not yet implemented.", ke)
except AttributeError:
print("You need to add arguments to an option.")
except IndexError:
print("You need to add more arguments")
'''
Complicated conditional (if-then-else) statement
'''
def validation_replace(x, words):
"""
This function checks if the replace command is written as it is supposed to be written.
Input:the list of words contained in the command
Output: true or false
"""
if len(words) == 5:
if isInteger(words[1]) == True and isInteger(words[4]) == True and words[2] == 'P1' or words[2] == 'P2' or \
words[2] == 'P3' and words[3] == 'with':
if int(words[4]) >= 0 and int(words[4]) <= 10:
if int(words[1]) <= len(x) and int(words[1]) > 0:
return True
return False
'''
Code fragments that can be grouped together
Turn these fragments into methods whose name explains the purpose of the method.
'''
def listVal(bank_account, stVal, number, check):
if number <= 0:
check += 1
elif len(bank_account) == 0:
print("Bank account is empty!")
else:
if stVal == '=':
k = False
big = max(bank_account)
for day in range(0, big + 1):
if day in bank_account:
for obj in bank_account[day]:
if obj[getValue()] == number:
k = True
print("Day", day, ":", obj[getValue()], obj[getType()], "for", obj[getDes()])
if k is False:
print("You don't have any value that is equal to", number)
elif stVal == '>':
k = False
big = max(bank_account)
for day in range(0, big + 1):
if day in bank_account:
for obj in bank_account[day]:
if obj[getValue()] > number:
k = True
print("Day", day, ":", obj[getValue()], obj[getType()], "for", obj[getDes()])
elif stVal == '<':
k = False
big = max(bank_account)
for day in range(0, big + 1):
if day in bank_account:
for obj in bank_account[day]:
if obj[getValue()] < number:
k = True
print("Day", day, ":", obj[getValue()], obj[getType()], "for", obj[getDes()])
if k is False:
print("You don't have any value that is less to", number)
@@ -0,0 +1,38 @@
import math
def isPrime(n):
"""
Verifies if a number is prime.
:param n: integer
:return:
True - if the number n is prime
False - otherwise
"""
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
def allPrimesLessThan(n):
"""
Returns a list with all prime numbers less than a given number n
:param n: integer
:return:
list with prime numbers less than n
"""
result = []
for i in range(2, n):
if isPrime(i):
result.append(i)
return result
if __name__ == "__main__":
while True:
n = int(input("Input an integer number (0 to exit the program): "))
if n == 0:
break
l = allPrimesLessThan(n)
print("Prime numbers less than " + str(n) + ": ")
print(l)
@@ -0,0 +1,25 @@
import ex01_PrimesLessThan
def fib(n):
if n < 2:
return n
return fib(n - 2) + fib(n - 1)
cache = {0: 0, 1: 1}
def fib_opt(n):
if n in cache:
return cache[n]
cache[n] = fib_opt(n - 2) + fib_opt(n - 1)
return cache[n]
if __name__ == "__main__":
print(fib_opt(80))
# check out these functions
print(locals())
print(globals())
@@ -0,0 +1,27 @@
"""
Return the product of the positive numbers found on even positions in a list, or None if there are no positive numbers
on an even position.
"""
def array_product_iter(array: list):
product = 1
stack = [(0, len(array) - 1)]
while len(stack) > 0:
left, right = stack.pop()
if left == right:
if left % 2 == 0 and array[left] > 0:
product *= array[left]
else:
mid = (left + right) // 2
stack.append((left, mid))
stack.append((mid + 1, right))
return product
data = [1, 2, 5, 2, 2, 2, 6, 7, 8, 2, 11, 9, 4, 5]
print(array_product_iter(data))
@@ -0,0 +1,88 @@
"""
Create a calculator program for rational numbers with the following functionalities:
+ add a rational number to the calculator
u undo the last operation
x exit
"""
from math import gcd
# ----------------------#
# Non-UI functions here #
# ----------------------#
def create_q(num, den: int = 1):
"""
Create Rational number
:param num:
:param den:
:return: The number, or None if number is invalid
"""
if den == 0:
return None
# return [num, den]
return {"num": num, "den": den}
def get_num(q):
# return q[0]
return q["num"]
def get_den(q):
# return q[1]
return q["den"]
def add_q(q1, q2):
num = get_num(q1) * get_den(q2) + get_num(q2) * get_den(q1)
den = get_den(q1) * get_den(q2)
g = gcd(num, den)
return create_q(num // g, den // g)
def to_str(q):
if get_den(q) == 1:
return str(get_num(q))
return str(get_num(q)) + "/" + str(get_den(q))
# -----------------------#
# Only UI functions here #
# -----------------------#
def add_rational(total):
num = int(input("enter numerator:"))
den = int(input("enter denominator:"))
q = create_q(num, den)
if q is None:
print("Invalid rational number")
return
return add_q(q, total)
def print_menu():
print("+ add a rational number to the calculator")
print("u undo the last operation")
print("x exit")
def start():
total = create_q(0)
while True:
print_menu()
print("Total: " + to_str(total))
opt = input(">")
if opt == "+":
total = add_rational(total)
elif opt == "x":
break
else:
print("Bad user option")
if __name__ == "__main__":
start()
@@ -0,0 +1,25 @@
"""
Rational number as dictionary
"""
def create_q(num, den: int = 1):
"""
Create Rational number
:param num:
:param den:
:return: The number, or None if number is invalid
"""
if den == 0:
return None
# return [num, den]
return {"num": num, "den": den}
def get_num(q):
# return q[0]
return q["num"]
def get_den(q):
# return q[1]
return q["den"]
@@ -0,0 +1,25 @@
"""
Rational number as list
"""
def create_q(num, den: int = 1):
"""
Create Rational number
:param num:
:param den:
:return: The number, or None if number is invalid
"""
if den == 0:
raise ValueError("denominator cannot be 0")
return [num, den]
# return {"num": num, "den": den}
def get_num(q):
return q[0]
# return q["num"]
def get_den(q):
return q[1]
# return q["den"]
@@ -0,0 +1,41 @@
from lecture.live.lecture_07.domain.rational_list import *
from math import gcd
def add_q(q1, q2):
num = get_num(q1) * get_den(q2) + get_num(q2) * get_den(q1)
den = get_den(q1) * get_den(q2)
g = gcd(num, den)
return create_q(num // g, den // g)
def add_qs(numbers: list):
"""
Add the rational nrs in the list
:param numbers: List of Q numbers
:return: The sum of numbers
"""
total = create_q(0)
for q in numbers:
total = add_q(total, q)
return total
def test_add_qs():
assert add_qs([]) == create_q(0)
q1 = create_q(1, 3)
q2 = create_q(2, 3)
q3 = create_q(1, 6)
q4 = create_q(1)
q5 = create_q(0)
assert add_qs([q1]) == q1
assert add_qs([q1, q2]) == create_q(1, 1)
assert add_qs([q4, q5]) == create_q(1, 1)
assert add_qs([q1, q2, q3, q4, q5]) == create_q(13, 6)
def to_str(q):
if get_den(q) == 1:
return str(get_num(q))
return str(get_num(q)) + "/" + str(get_den(q))
@@ -0,0 +1,4 @@
from ui.ui_menu import start
# from ui.ui_command import start
start()
@@ -0,0 +1,72 @@
"""
command driven calculator
add 1/2,4/5,99/98,45,1/0,...
exit
"""
from lecture.live.lecture_07.domain.rational_list import create_q
from lecture.live.lecture_07.functions.calculator import add_q, to_str
def add_ui(total, param: str):
"""
Add the numbers to the calculator
:param param:
:return: The new value of the calculator
Raise ValueError if param contains a non-number, a 0-denominator, missing numerator
"""
numbers_str = param.split(",")
for number in numbers_str:
if number.find("/") == -1:
q = create_q(int(number))
else:
# FIXME crash if multiple /
num, denom = number.split("/")
if len(num.strip()) == 0:
raise ValueError("Numerator missing")
q = create_q(int(num), int(denom))
total = add_q(total, q)
return total
#
# def test_add_ui():
# total = create_q(0)
# assert add_ui(total, "0") == create_q(0)
# assert add_ui(total, "1") == create_q(1, 1)
# assert add_ui(total, "1/2,1/2") == create_q(1)
# assert add_ui(total, "1/2,1/3") == create_q(5, 6)
# assert add_ui(total, "1/2,-1/3") == create_q(1, 6)
#
# # Check that a ValueError is actually raised
# try:
# add_ui(total, "1/2,1/0")
# assert False
# except ValueError:
# assert True
#
# try:
# add_ui(total, "1/2,1/e")
# assert False
# except ValueError:
# assert True
#
# try:
# add_ui(total, "1/2,/6")
# assert False
# except ValueError:
# assert True
def start():
total = create_q(0)
while True:
print(to_str(total))
command = input(">")
if command.startswith("add"):
total = add_ui(total, command[4:])
elif command.startswith("exit"):
return
else:
print("Invalid command")
start()
@@ -0,0 +1,46 @@
# import lecture.live.lecture_07.domain.rational_dict as rational_d
# import lecture.live.lecture_07.domain.rational_list as rational_l
# from lecture.live.lecture_07.domain.rational_dict import create_q
from lecture.live.lecture_07.domain.rational_list import create_q
from lecture.live.lecture_07.functions.calculator import add_q,to_str
def add_rational(total):
num = int(input("enter numerator:"))
den = int(input("enter denominator:"))
q = create_q(num, den)
if q is None:
print("Invalid rational number")
return
return add_q(q, total)
def print_menu():
print("+ add a rational number to the calculator")
print("u undo the last operation")
print("x exit")
def start():
total = create_q(0)
while True:
print_menu()
print("Total: " + to_str(total))
opt = input(">")
if opt == "+":
total = add_rational(total)
elif opt == "x":
break
else:
print("Bad user option")
if __name__ == "__main__":
start()
# q = rational_d.create_q(1,2)
# print(q)
# q = rational_l.create_q(1, 2)
# print(q)
@@ -0,0 +1,65 @@
class Ingredient:
def __init__(self, id, name):
"""
How do we protect class fields from changes?
C++/Java/C# -> private (just inside the class)
protected (inside the class and derived classes)
public (everywhere)
// default (Java), internal (C#)
Python
public -> <name> (e.g., class.field_name)
protected -> _<name> (e.g., class._field_name)
private -> __<name> (e.g., class.__field_name) -> Python name mangling
"""
self.__id = id
self.__name = name
@property
def id(self):
return self.__id
@property
def name(self):
return self.__name
@name.setter
def name(self, new_value):
if len(new_value) < 5:
raise ValueError("Name must have at least length 5")
self.__name = new_value
def __str__(self):
return str(self.id) + " -> " + self.name
if __name__ == "__main__":
ingr = Ingredient(100, "Random Ingredient")
flour = Ingredient(101, "Flour")
ingr.x = 123
# print(ingr.__dict__)
# print(flour.__dict__)
# print( isinstance(ingr,Ingredient))
# print( isinstance(ingr,str))
# print(type(ingr) == str)
# data = list()
# print(ingr.x)
# print(ingr.__id, ingr.__name)
print(ingr)
# print(Ingredient.get_name(ingr))
ingr.name = "Magical ingredient"
# ingr.id = 123
print(ingr.id, ingr.name)
print(ingr)
# print(ingr.__id, ingr.__name)
# print(flour.x)
# print(type(ingr))
# print(type(data))
@@ -0,0 +1,93 @@
from lecture.live.lecture_08.ingredient import Ingredient
import pickle
class Repository:
def __init__(self):
self._data = {}
def add(self, ingredient):
if type(ingredient) != Ingredient:
raise TypeError("Can only add ingredients to repo!")
if ingredient.id in self._data:
raise ValueError("Ingredient already added")
self._data[ingredient.id] = ingredient
def get(self, ingr_id):
return self._data[ingr_id]
def __len__(self):
return len(self._data)
"""
This repository stores data in memory. Let's add a few ingredients
"""
# repo = Repository()
# repo.add(Ingredient(100, "Flour"))
# repo.add(Ingredient(101, "Spices"))
# print(repo.get(101))
# print(len(repo))
"""
We want to save all changes to a file.
We want to use both memory-repo and file-repo
We don't want to duplicate code :)
We want to allow new file-based repo implementations
"""
# fileRepository inherits from Repository
# it has all fields and methods of Repository
class fileRepository(Repository):
def __init__(self):
# call base class constructor
super().__init__()
# load input file
self._load_file()
# NOTE - this is a Template Method design pattern
def add(self, ingredient):
# do everything the parent's add() does
super().add(ingredient)
# save the ingredients to file
self._save_file()
def _load_file(self):
raise NotImplementedError()
def _save_file(self):
raise NotImplementedError()
# code below cannot work due to NotImplementedError
# repo = fileRepository()
# repo.add(Ingredient(100, "Flour"))
# repo.add(Ingredient(101, "Spices"))
# print(repo.get(101))
# print(len(repo))
"""
Create a binary file repo that DOES work
"""
class binFileRepository(fileRepository):
def _load_file(self):
fin = open("repo.bin", "rb")
self._data = pickle.load(fin)
fin.close()
def _save_file(self):
# w -write, b - binary
fout = open("repo.bin", "wb")
pickle.dump(self._data, fout)
fout.close()
if __name__ == "__main__":
repo = binFileRepository()
# repo.add(Ingredient(100, "Flour"))
# repo.add(Ingredient(101, "Spices"))
print(repo.get(101))
print(len(repo))
@@ -0,0 +1 @@
<mxfile host="Electron" modified="2022-12-20T10:31:55.480Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.6.2 Chrome/106.0.5249.199 Electron/21.3.3 Safari/537.36" etag="dbdKQqo_Ai7oW_w7mAxP" version="20.6.2" type="device"><diagram id="TUFEDdWwblI0UiAdTFXX" name="Page-1">7V1db6M4FP01kWYfOsJ8JXls0vladWardqWd2ZfIDW5CS3CW0CadX792MASwCYRgoKqlSq2NMcbn2L732NwOjOlq9yWA6+V37CBvoGvObmBcDXQd2LZNftGc1yhnZOlRxiJwHVbokHHn/kYsU2O5z66DNpmCIcZe6K6zmXPs+2geZvJgEOBtttgD9rJPXcMF4jLu5tDjc/9xnXDJ3kIfHvK/InexjJ8M7HF0ZQXjwuxNNkvo4G0qy/g0MKYBxmH012o3RR7tvLhfovs+F1xNGhYgP6xyw49vk+t/t0h/8h+eHj+vJnc/TO2C1fICvWf2wt+cv+4faU9GjQ5f457YbN2VB32SmjxgP7xjVwBJQ89d+OTvOWkKCkjGCwpCl3TiJbsQ4jXJnS9dz7mGr/iZNngTwvlTnJosceD+JtVCj9VJLgch44NuZ0rc0TtJtkZyA7QhZW7iXgC5rO9wlyl4DTchy5hjz4PrjXufvMYKBgvXn+AwxCtWiO/kuMfIG6JdKot1+heEVygMXkkRdjXGnw0AYLD09kAnYLO8ZYpKIzZyIGPwIqk5edgtwQn6C/ICydPAMPc4m3/cWPC0uFXx06BHkPRhiCb42Xc2aWaRP1Lvecja8+0E7ukc9wa6QTJm5C7SacYlrcDniUj6PdwTJMBPaIo9TBh35eOIma7n5bJicnroISyk5mYN566/uN6XuTIPObesi2gWJvc+ePsxvHQdB/mUVjiEIYw4RAmzxqTJ+z60JuSH9OpU+2gNLNLwKUmDQ5r80OJBOMU+eRfo7hmGCEG3iJJUwL2jQ7ickIwSul2NgPoRAma4cCrwhgD4HMaeu8cuwjieeEEtgFcEKg8dEP2bAn51ATjUDR51Q4CwB++Rd4M3buhiWn8Qlc0hXwZulr2uv0SBKxP0eNktnXUkYW4KMJ/sb/rwhxrtEoEXzP/tjnaLNzH8RYAcFwnQVkZGI0aGPmrVyDCA4HFaP6wMm6PfBUn6cIWSaYeMRzL21cxzfOaxKnOyL3bGUNkZ59oZJ4PetZ0xKrAz6IBPWRpqyEtBv3NjY8zB/2kXonhpSUNNMi+pPkRS9x6mS/2EZDFbANhR8rNLH7/v/WW4iq2GgC5WyGEXSBcGrz9pgnQ+S/5iJfeJq1265NVrnNq5Yeo2kvqVunK4iSbiewothQ1+DuaoHEpi7yxQBfsSORlZjAdchHCAPBi6L1nl7IhlcYP3Bn9sVVyAnBFj5ayF6CXZXWmlK1cRsEoqinqBq6gpkyN+jxQL70JKMWXtNmTt5gGuau4C48i6U2zvmlq5vTvsh7kLeEU3UtX+e4Z+6Iavytk+wdnWKjOzLzYvEAktxh7ug9PNGHDED1dEOJMIVe1geUQQyevK+zlNZT0Z9uGoGuyy3B8g0tYnqemf+EBq9peF/qjz2Z8XW5nMnkz1KSdYzf/yqABA5wsAr3zK94Vr+LSJ/5w4zL8GGWe6wH8u9CNKfeEYzVJnmEHYhTOcSCkxn4Y1nWEzf/4gX5FsZ5hXYSPxvRIHGelAlnQUkNhZJoN7iRfYh96nQ261CalAxzmRhwnn04xP+N+0jhP7d6XctStyNxFfGqEtMLNsM0FN2hpaSUWyacsribdo7q554ioRp56IY9rtijg2KBdxRv0QceIpm3Pi1bbl6abbuDIx+6Lh6LzlFsG/oSryJiHAtUs6Qrc92htMYLYXh65RnGiOE53LObpI11Vyzmlyzsmwdy3nxCCr7exuhn3ngo4uOsMySZaCFAXUYtAaK7rXdnRe5u25tvNR0+ysX62NR2WeNU3dkDmedBp1n851nRnQfZZ9OLVmVNN/tkYlFUn2n3V+H1LJPvW5q1eVLGN9qGXdxzRyck3ea67M23FJRbJ5yx8gvAmw86w+iGtM+LGHwwzE1YWfsVbKS4Hwk3yseUT46csncbzqWCz8TPXBpcaxUll34vH8duQfQ7SHX3iES7FACgs6F3yMooNcAduGYBwo2JVQBDiTAFWlH3kEEG0CKMXvJMXvdNiBZlbDXZbkZ4iEXiX5tTbuuxd3jKJDXKkzfOoQt0wKGJ3P/aIdwEmy+KcIoJZ/SRywK64D8jjA7/20qPGall1d5SWJvEybVn71nLQGEiW4HeE3Rr/PH7+Zeb3Wrimg2WZJRZIFNKPCV9e9FX0NrdaeRDxsLrSPGsjowQYYnTx06hM9Vg7Kdzi6OR2YP4JlmjVJPhyWVNQcyTXH+7k1Xh5/jtFX909Lm2wfPEHYtFu0pr4OZhO/0onF662A14WkA8AGGZT1qjrxsQOCVddkIe78khxpMg6xbxKTzHEFmwXvyyArwb14TPVClRU2TwUtK1Njmge9xWAiwtYVBS2DjvMB3z8mIz6OmUncMkI/ur/07o/dNE+GNmOLCJtXJMsQQ4OyIRe2UnFBIhfaPJcnbF6RPjObecifzfIKnWKCLCa0KtcK28f7u8SRRdQfUJ6ANE8AVJwAjsU3PAt10UENZQ6eaw4OG/EBZJmDgrhO7Lsc+IJmBNW9MP9OJ/j0hKbLYoDRoigvpkCRAuBh6CgKtECBzt0AQYQt6fsyA5FaW6oRHzNX0hLxKWaXhK0PoOW+BDbqHnoH+TjIXE2ydWFeJToEWVFmYdNmoZaH265oISReQ/OzQ9GxPbo2zPLHeN/tUlF/dYiGWH91YmEIJuUZnC0UH4e9a6VYEGxJuQbSB3vntmBhoA7lDkiEvXP1VxDT6i25AEBwTOQUT0yGDwByppxVN3AU0EclNUn2AQSROvbf4yvzv2nzn/P2Kpv/8g6IFOlDyvxvZmno0cd74vYVGYI0Auss2I//5Oh24ZExxYMzedDm53vi9qnzQjLcwP7EaxI3r0j9UW6gRNQ79wd0keaj3EDJsHd/9EMQnest+YHJP1TvkR9oWk3tBXHxXlveCxJEkqnIihY/hOmCTe0EojZy30FZdb81AZZWUlMBkQiy8DVVjM2pR5qc2wcdjXO8jGpslKWCKAMs3pHSK5rWK/LB3XqgVwijDSi9ojkbJhpf/dUrBF8Zp8PMKMWiLSZ0rlgIw04pxeJcxaIE9q4Vi8IwU0qxkIh654qFMMaMUiwkw969YiEILPOWFIvYW+mTYqHndAa77n/qAsawpKamHM28R6tbbXiabyEwSBdcbUcP0XJ6iF03wAzQtZKaGqNpzl9mw6EmS0kywDhMFw/gevkdO4iW+B8=</diagram></mxfile>
@@ -0,0 +1,10 @@
import unittest
class IdObject:
def __init__(self, _id: int):
self._id = _id
@property
def id(self):
return self._id
@@ -0,0 +1,24 @@
import unittest
from lecture.live.lecture_09_10.domain.idobject import IdObject
class Ingredient(IdObject):
def __init__(self, _id: int, name: str):
super().__init__(_id)
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, new_value):
if len(new_value) < 5:
raise ValueError("Name must have at least length 5")
if type(new_value) != str:
raise TypeError("Name must be string")
self.__name = new_value
def __str__(self):
return str(self.id) + " -> " + self.name
@@ -0,0 +1,22 @@
from lecture.live.lecture_09_10.domain.idobject import IdObject
from lecture.live.lecture_09_10.domain.recipe import Recipe
class Product(IdObject):
def __init__(self, _id: int, name: str, quantity: int, recipe: Recipe):
super().__init__(_id)
self._name = name
self._quantity = quantity
self._recipe = recipe
@property
def name(self):
return self._name
@property
def quantity(self):
return self._quantity
@property
def recipe(self):
return self._recipe
@@ -0,0 +1,21 @@
from lecture.live.lecture_09_10.domain.idobject import IdObject
class Recipe(IdObject):
# TODO What is *stocks?
def __init__(self, _id: int, name: str, *stocks):
super().__init__(_id)
self._name = name
self._stocks = list(stocks)
@property
def name(self):
return self._name
@property
def stocks(self):
return self._stocks
@stocks.setter
def stocks(self, new_value):
self._stocks = new_value
@@ -0,0 +1,21 @@
from lecture.live.lecture_09_10.domain.idobject import IdObject
from lecture.live.lecture_09_10.domain.ingredient import Ingredient
class Stock(IdObject):
def __init__(self, _id: int, ingredient: Ingredient, quantity: int):
super().__init__(_id)
self._ingredient = ingredient
self._quantity = quantity
@property
def ingredient(self):
return self._ingredient
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, new_value):
self._quantity = new_value
@@ -0,0 +1,10 @@
100,Bread Flour (White 550)
101,Yeast (dry)
102,Sugar (white)
103,Salt (regular)
104,Oil (canola)
105,Butter
106,Egg (chicken)
107,Cake flour
108,Baking powder
109,Vanilla (extract)
@@ -0,0 +1,2 @@
3000,Basic Homemade Bread,0,500
3001,Simple Vanilla Cake,0,501
@@ -0,0 +1,2 @@
500,Basic Homemade Bread,101,20,102,175,103,2,104,10,100,1000
501,Tasty Cookies,105,175,102,175,106,3,107,175,108,5,109,5,103,2
@@ -0,0 +1,23 @@
from lecture.live.lecture_09_10.domain.idobject import IdObject
from lecture.live.lecture_09_10.domain.stock import Stock
from lecture.live.lecture_09_10.repo.mem_repo import Repository
class Converter:
def to_str(self, obj: IdObject):
raise NotImplementedError()
def from_str(self, string: str) -> IdObject:
raise NotImplementedError()
class StockConverter(Converter):
def __init__(self, ingr_repo: Repository):
self._ingr_repo = ingr_repo
def to_str(self, stock: Stock):
return str(stock.id) + "," + str(stock.ingredient.id) + "," + str(stock.quantity)
def from_str(self, string: str):
tokens = string.split(",")
return Stock(int(tokens[0].strip()), self._ingr_repo.get(int(tokens[1].strip())), int(tokens[2].strip()))
@@ -0,0 +1,22 @@
from lecture.live.lecture_09_10.domain.idobject import IdObject
from lecture.live.lecture_09_10.domain.ingredient import Ingredient
from lecture.live.lecture_09_10.domain.stock import Stock
from lecture.live.lecture_09_10.repo.mem_repo import Repository, RepositoryError
class FileRepo(Repository):
def __init__(self):
super().__init__()
# load the file on startup
self._load_file()
def add(self, id_object: IdObject):
super().add(id_object)
# if no exceptions are raised we get here :)
self._save_file()
def _save_file(self):
raise NotImplementedError()
def _load_file(self):
raise NotImplementedError()
@@ -0,0 +1,38 @@
from lecture.live.lecture_09_10.domain.ingredient import Ingredient
from lecture.live.lecture_09_10.repo.file_repo import FileRepo
from lecture.live.lecture_09_10.repo.mem_repo import RepositoryError
class IngredientFileRepo(FileRepo):
def __init__(self, file_name="ingredients.txt"):
self._file_name = file_name
super().__init__()
def _save_file(self):
fout = open(self._file_name, "wt")
for ingr in self._data.values():
line = str(ingr.id) + "," + ingr.name + "\n"
fout.write(line)
fout.close()
def _load_file(self):
try:
fin = open(self._file_name, "rt")
lines = fin.readlines()
fin.close()
except FileNotFoundError:
# option 1: behave like no input file is normal => empty repo
# we eat the exception :)
# pass
# option 2: file must be present => program cannot make progress
raise RepositoryError("Input file does not exit")
except IOError:
# NOTE FileNotFoundError is handled in the except block above here
# NOTE IOError that are not FileNotFoundError are handled here
pass
# NOTE PyCharm highlights the lines variable below, as it is initially defined in the try ... except block.
# In case an IOError that is NOT a FileNotFoundError is raised, the variable remains undefined :)
for line in lines:
tokens = line.split(",")
self.add(Ingredient(int(tokens[0].strip()), tokens[1].strip()))
@@ -0,0 +1,50 @@
from lecture.live.lecture_09_10.domain.idobject import IdObject
from lecture.live.lecture_09_10.domain.ingredient import Ingredient
class RepositoryError(Exception):
pass
class RepoIterator:
def __init__(self, data: dict):
self._data = list(data.values())
self._index = 0
def __next__(self):
if self._index == len(self._data):
raise StopIteration()
elem = self._data[self._index]
self._index += 1
return elem
class Repository:
def __init__(self):
self._data = {}
def add(self, id_object: IdObject):
if not isinstance(id_object, IdObject):
raise TypeError("Can only add IdObjects to repo!")
if id_object.id in self._data:
raise RepositoryError("Object already added")
self._data[id_object.id] = id_object
def get(self, object_id):
return self._data[object_id]
def __len__(self):
return len(self._data)
def __iter__(self):
return RepoIterator(self._data)
if __name__ == "__main__":
ingr = Ingredient(100, "abcd")
# NOTE type() returns the actual type of the object
print(type(ingr) == IdObject)
# NOTE isinstance() checks that ingr is also an IdObject => works with inheritance
print(isinstance(ingr, IdObject))
@@ -0,0 +1,43 @@
from lecture.live.lecture_09_10.domain.product import Product
from lecture.live.lecture_09_10.domain.stock import Stock
from lecture.live.lecture_09_10.repo.file_repo import FileRepo
from lecture.live.lecture_09_10.repo.mem_repo import Repository, RepositoryError
class ProductFileRepo(FileRepo):
def __init__(self, recipe_repo: Repository, file_name="products.txt"):
self._file_name = file_name
self._recipe_repo = recipe_repo
super().__init__()
def _save_file(self):
fout = open(self._file_name, "wt")
for product in self._data.values():
line = str(product.id) + "," + str(product.name) + "," + str(product.quantity) + "," + str(
product.recipe.id) + "\n"
fout.write(line)
fout.close()
def _load_file(self):
try:
fin = open(self._file_name, "rt")
lines = fin.readlines()
fin.close()
except FileNotFoundError:
# option 1: behave like no input file is normal => empty repo
# we eat the exception :)
return
# option 2: file must be present => program cannot make progress
# raise RepositoryError("Input file does not exit")
except IOError:
# NOTE FileNotFoundError is handled in the except block above here
# NOTE IOError that are not FileNotFoundError are handled here
pass
# NOTE PyCharm highlights the lines variable below, as it is initially defined in the try ... except block.
# In case an IOError that is NOT a FileNotFoundError is raised, the variable remains undefined :)
for line in lines:
tokens = line.split(",")
recipe = self._recipe_repo.get(int(tokens[3].strip()))
self.add(Product(int(tokens[0].strip()), tokens[1].strip(), int(tokens[2].strip()), recipe))
@@ -0,0 +1,56 @@
from lecture.live.lecture_09_10.domain.recipe import Recipe
from lecture.live.lecture_09_10.domain.stock import Stock
from lecture.live.lecture_09_10.repo.file_repo import FileRepo
from lecture.live.lecture_09_10.repo.mem_repo import Repository, RepositoryError
class RecipeFileRepo(FileRepo):
def __init__(self, stock_repo: Repository, file_name="recipes.txt"):
self._file_name = file_name
self._stock_repo = stock_repo
super().__init__()
def _save_file(self):
fout = open(self._file_name, "wt")
line = ""
for recipe in self._data.values():
line = str(recipe.id) + "," + str(recipe.name) + ","
for stock in recipe.stocks:
line += str(stock.id) + "," + str(stock.quantity) + ","
line = line[:-1]
line += "\n"
fout.write(line)
fout.close()
def _load_file(self):
try:
fin = open(self._file_name, "rt")
lines = fin.readlines()
fin.close()
except FileNotFoundError:
# option 1: behave like no input file is normal => empty repo
# we eat the exception :)
return
# option 2: file must be present => program cannot make progress
# raise RepositoryError("Input file does not exit")
except IOError:
# NOTE FileNotFoundError is handled in the except block above here
# NOTE IOError that are not FileNotFoundError are handled here
pass
# NOTE PyCharm highlights the lines variable below, as it is initially defined in the try ... except block.
# In case an IOError that is NOT a FileNotFoundError is raised, the variable remains undefined :)
for line in lines:
tokens = line.split(",")
recipe_id = int(tokens[0].strip())
recipe_name = tokens[1].strip()
recipe = Recipe(recipe_id, recipe_name)
for index in range(2, len(tokens) - 1, 2):
stock_id = int(tokens[index].strip())
stock_quantity = int(tokens[index + 1].strip())
stock = self._stock_repo.get(stock_id)
stock.quantity = stock_quantity
# add stock item to recipe
recipe.stocks.append(stock)
self.add(recipe)
@@ -0,0 +1,40 @@
from lecture.live.lecture_09_10.domain.stock import Stock
from lecture.live.lecture_09_10.repo.file_repo import FileRepo
from lecture.live.lecture_09_10.repo.mem_repo import Repository, RepositoryError
class StockFileRepo(FileRepo):
def __init__(self, ingr_repo: Repository, file_name="stocks.txt"):
self._file_name = file_name
self._ingr_repo = ingr_repo
super().__init__()
def _save_file(self):
fout = open(self._file_name, "wt")
for stock in self._data.values():
line = str(stock.id) + "," + str(stock.ingredient.id) + "," + str(stock.quantity) + "\n"
fout.write(line)
fout.close()
def _load_file(self):
try:
fin = open(self._file_name, "rt")
lines = fin.readlines()
fin.close()
except FileNotFoundError:
# option 1: behave like no input file is normal => empty repo
# we eat the exception :)
# pass
# option 2: file must be present => program cannot make progress
raise RepositoryError("Input file does not exit")
except IOError:
# NOTE FileNotFoundError is handled in the except block above here
# NOTE IOError that are not FileNotFoundError are handled here
pass
# NOTE PyCharm highlights the lines variable below, as it is initially defined in the try ... except block.
# In case an IOError that is NOT a FileNotFoundError is raised, the variable remains undefined :)
for line in lines:
tokens = line.split(",")
ingr = self._ingr_repo.get(int(tokens[1].strip()))
self.add(Stock(int(tokens[0].strip()), ingr, int(tokens[2].strip())))
@@ -0,0 +1,37 @@
from lecture.live.lecture_09_10.repo.converters import Converter
from lecture.live.lecture_09_10.repo.file_repo import FileRepo
from lecture.live.lecture_09_10.repo.mem_repo import RepositoryError
class TextFileRepo(FileRepo):
def __init__(self, converter: Converter, file_name="file.txt"):
self._file_name = file_name
self._converter = converter
super().__init__()
def _save_file(self):
fout = open(self._file_name, "wt")
for idobj in self._data.values():
fout.write(self._converter.to_str(idobj) + "\n")
fout.close()
def _load_file(self):
try:
fin = open(self._file_name, "rt")
lines = fin.readlines()
fin.close()
except FileNotFoundError:
# option 1: behave like no input file is normal => empty repo
# we eat the exception :)
# pass
# option 2: file must be present => program cannot make progress
raise RepositoryError("Input file does not exit")
except IOError:
# NOTE FileNotFoundError is handled in the except block above here
# NOTE IOError that are not FileNotFoundError are handled here
pass
# NOTE PyCharm highlights the lines variable below, as it is initially defined in the try ... except block.
# In case an IOError that is NOT a FileNotFoundError is raised, the variable remains undefined :)
for line in lines:
self.add(self._converter.from_str(line))

Some files were not shown because too many files have changed in this diff Show More