School Commit Init
This commit is contained in:
@@ -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 program’s 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
|
||||
+40
@@ -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))
|
||||
+72
@@ -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!')
|
||||
+45
@@ -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?
|
||||
"""
|
||||
+153
@@ -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()
|
||||
+100
@@ -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
|
||||
'''
|
||||
+78
@@ -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
|
||||
'''
|
||||
+61
@@ -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())
|
||||
+53
@@ -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()
|
||||
+50
@@ -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)
|
||||
+52
@@ -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
|
||||
+36
@@ -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)
|
||||
+649
@@ -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()
|
||||
+356
@@ -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)
|
||||
+102
@@ -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])
|
||||
+119
@@ -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()
|
||||
+67
@@ -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))
|
||||
+54
@@ -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))
|
||||
+121
@@ -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))
|
||||
+62
@@ -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))
|
||||
+67
@@ -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()
|
||||
+96
@@ -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)
|
||||
+13
@@ -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))
|
||||
+24
@@ -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))
|
||||
+30
@@ -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)
|
||||
+157
@@ -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()
|
||||
+63
@@ -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))
|
||||
+23
@@ -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"]
|
||||
+22
@@ -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]
|
||||
+83
@@ -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)
|
||||
+36
@@ -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)
|
||||
+10
@@ -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()
|
||||
+58
@@ -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")
|
||||
+48
@@ -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")
|
||||
+60
@@ -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)
|
||||
+71
@@ -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()
|
||||
+169
@@ -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())
|
||||
+52
@@ -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)
|
||||
+37
@@ -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)
|
||||
+165
@@ -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()
|
||||
+62
@@ -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()
|
||||
+76
@@ -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)
|
||||
+69
@@ -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)
|
||||
+128
@@ -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))
|
||||
+73
@@ -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()
|
||||
+103
@@ -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.
|
||||
'''
|
||||
+237
@@ -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)
|
||||
+38
@@ -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()
|
||||
+25
@@ -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"]
|
||||
+25
@@ -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"]
|
||||
+41
@@ -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()
|
||||
+72
@@ -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()
|
||||
+46
@@ -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)
|
||||
|
||||
+65
@@ -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))
|
||||
+1
@@ -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>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class IdObject:
|
||||
def __init__(self, _id: int):
|
||||
self._id = _id
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
+24
@@ -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
|
||||
+22
@@ -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
|
||||
+21
@@ -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
|
||||
+21
@@ -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
|
||||
+10
@@ -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)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
3000,Basic Homemade Bread,0,500
|
||||
3001,Simple Vanilla Cake,0,501
|
||||
+2
@@ -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
|
||||
+23
@@ -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()))
|
||||
+22
@@ -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()
|
||||
+38
@@ -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()))
|
||||
+50
@@ -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))
|
||||
+43
@@ -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))
|
||||
+56
@@ -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)
|
||||
+40
@@ -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())))
|
||||
+37
@@ -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))
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
class IngredientService:
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user