School Commit Init

This commit is contained in:
2024-08-31 12:07:21 +03:00
commit 0b130ee18c
2801 changed files with 4720552 additions and 0 deletions
@@ -0,0 +1,52 @@
"""
Let's write a menu-driven application. What does the menu look like?
1. Generate (random) person names
2. Exit
"""
import random
def sort_names(name_list):
pass
def generate_names():
count = int(input("How many names to generate? ")) # int() converts to integer
family_names = ["Albu", "Pop", "Gheorghe", "Morar", "Negrea", "Bodnar"] # list()
given_names = ["Anca", "Elisa", "Ionut", "Vasile", "Ioana", "Rares"]
result = []
for i in range(count):
family_name = random.randint(0, len(family_names) - 1)
given_name = random.randint(0, len(given_names) - 1)
name = family_names[family_name] + " " + given_names[given_name]
result.append(name)
# Let's print out the names
print(result)
return result
def start():
print("Welcome to seminar 2!")
while True:
print("1. Generate (random) person names")
print("2. Sort list of names")
print("0. Exit")
opt = input(">")
names_list = []
if opt == "1":
names_list = generate_names()
elif opt == "2":
sort_names(names_list)
elif opt == "0":
return # break would have also been acceptable
else:
print("Bad command or file name")
start()
@@ -0,0 +1,249 @@
"""
1. Determine the time complexity of the following algorithms as a function of n.
source: https://complex-systems-ai.com/en/algorithmic/corrected-exercises-time-complexity/
"""
# Overall
# T(n) = 10 * n * 1 = 10 * n, so O(n) is n
# O(n) is also any function growing faster than "n"
# O(n) is also n*log(n), n^2, n^3 ,... , 2^n and so on
# what about a low bound for f_1 ?
# Theta(n) = n, and T(n) = n is both high-bound and low-bound
def f_1(n: int):
# T(n) = 1, for loop does not depend on n's value, O(n) = 1
for i in range(10): # <=> range(0,10) -> from 0 to 9
# for loop depends on n's value, T(n) = n, so complexity O(n)
for j in range(n): # <=> range(0,n) -> from 0 to n - 1
# print does not depend on n's value, so complexity is O(1)
# 1 is like f(n) = 1
print("Hello World")
# Overall
# T(n) = 10 * n * 1, so O(n) = n, Theta(n) = n
def f_2(n: int):
# does not actually depend on n's value
for i in range(n, n + 10): # loops between n .. n + 9 (10 values)
# inner loop depends on n linearly, so its O(n)
for j in range(n):
# print takes constant time to run, so O(1)
print("Hello World")
# T(n) = n * n * 1 => O(n) = n^2
def f_3(n: int):
# T(n) = n for the outer loop (linear time)
for i in range(1, n):
# Inner loop depends on n, starts with for j in range(1, n)
# inner loop calculation:
# 1 + 2 + ... + n - 1
# 2 + ... + n - 1
# ...
# ... n - 1
for j in range(i, n):
print("Hello World")
# Overall
# T(n) = n * n * 1 => O(n) is n^2
def f_4(n: int):
# outer loop is O(n), with T(n) = n * inner loop
for i in range(n):
# j depends on n, final iteration is between 0 and 2 * (n-1) + 1
for j in range(2 * i + 1):
print("Hello World") # O(1) as usual :)
# T(n) = n^2 * n^2 * 1
# O(n) is n^4
#
# Question:
# let's say for n = 1000, it takes 1 ms
# how long do we expect it to run for n = 2000?
# answer: we doubled the size of the input => 2 ^ 4 times more => 16 ms.
# if n = 3000
# 3 ^ 4 => 81 ms.
def f_5(n: int):
# T(n) = n^2 for outer loop
for i in range(n ** 2):
# T(n) = n^2 for inner loop
for j in range(i):
print("Hello World") # O(1)
def f_6(n: int):
for i in range(n):
# How many times can we multiply by 2 in order to go from 1 to n?
# Answer is log(n) times
t = 1
while t < n:
print("Hello World")
t *= 2 # log is base 2 as we multiply by 2
"""
1. Time complexity in both "n" and "m"
"""
# overall ??
# T(n,m) = n + m, so its O(n+m)
# another example -- when merging arrays of lengths n and m, O(n+m) = n + m
# as we look at each merged element exactly one time
def f_7(n, m: int):
# T(n) = n, so for the loop O(n) = n
for i in range(0, n):
print("Hello World")
# T(n) = m, so for the loop O(n) = m
for j in range(0, m):
print("Hello World")
# T(n) = n + n = 2*n
# O(n) = n
def f_8(n, m: int):
for i in range(0, n):
print("Hello World")
for j in range(0, n):
print("Hello World")
# O(n) is n^2
def f_9(n: int):
for i in range(n):
for j in range(n):
print("Hello World")
for k in range(n):
print("Hello World")
# O(n) is n^2
def f_10(n: int):
for i in range(n):
for j in range(n, i, -1):
print("Hello World")
"""
Analyze the time and space complexity
"""
# O(n) is n * log(n) (log is base 3)
# extra space complexity is O(1)
def f_11(data: list):
data_sum = 0
for el in data:
j = len(data)
while j > 1:
data_sum += el * j
j = j // 3 # we get log base 3 of n (j always stars at n)
return data_sum
"""
time complexity
T(n) = 1, if n <= 1
T(n) = 2 * T(n/2) + 1
T(n) = 2 * T(n/2) + 1
T(n/2) = 2 * T(n/4) + 1
T(n/4) = 2 * T(n/8) + 1
T(n) = 2 * T(n/2) + 1 = 2 * [2 * T(n/4) + 1] + 1 = 4 * T(n/4) + 2 + 1 =
4 * [2 * T(n/8) + 1] + 2 + 1 = 8 * T(n/8) + 4 + 2 + 1
T(n) = 8 * T(n/8) + 4 + 2 + 1 ... and so on
we know T(0) = T(1) = 1
let's say we have k natural number so that 2 ^ k = n, k = log(n)
T(n) = k * T(1) + sum of the powers of 2 (2^0 + 2^1 + ... + 2^(k-1))
T(n) = k * 1 + 2^k - 1 = log(n) + n => O(n) is n
extra space complexity
T(1) = 1
T(n) = 2 * T(n/2)
"""
def f_12(data: list):
if len(data) == 0:
return 0
if len(data) == 1:
return data[0]
m = len(data) // 2
return f_12(data[:m]) + f_12(data[m:]) # T(n/2) + T(n/2)
def f_13(n: int):
s = 0
for i in range(1, n ** 2):
j = i
while j != 0:
s = s + j - 10 * j // 10
j //= 10
return s
def f_14(n, i: int):
if n > 1:
i *= 2
m = n // 2
f_14(m, i - 2)
f_14(m, i - 1)
f_14(m, i + 2)
f_14(m, i + 1)
else:
print(i)
"""
Analyze the algorithm's time complexity. Write an equivalent algorithm with
a strictly better time complexity
"""
def f_15(data: list):
i = 0
j = 0
m = 0
c = 0
while i < len(data):
if data[i] == data[j]:
c += 1
j += 1
if j >= len(data):
if c > m:
m = c
c = 0
i += 1
j = i
return m
"""
What is the time complexity when the following algorithm is implemented via linear exponentiation. How can this be
optimized and how will that improve the complexity?
"""
def f_16(x, n: int):
"""
The algorithms returns x ** n
:param x:
:param n:
:return:
"""
# TODO Implement me
pass
"""
Implement and discuss the complexity of merge sort
"""
def merge_sort(data: list):
# TODO Implement me
pass
@@ -0,0 +1,213 @@
"""
Divide & Conquer
1. Divide - the problem into smaller subproblems (not overlapping)
2. Conquer - solve the "small" problems directly (no d&c)
3. Combine - "small" problem results into the solution of the original one
Binary search
1. Divide => decide in which half of the array to continue
2. Conquer => find the element/run out of list (left >= right)
3. Combine => nothing to do here
Merge Sort
1. Divide => split the array into two halves
2. Conquer => single-element arrays are already sorted
3. Combine => merge arrays until we get to the large, sorted one
Quick Sort
1. Divide => select a pivot, partition the array around it
2. Conquer => continue for the subarrays left and right of pivot
3. Combine => nothing to do here
"""
"""
1. Find the smallest number in a list (chip & conquer, divide in halves, recursive vs non-recursive)
a. Chip & conquer, recursive
b. Divide in halves, non-recursive
c. Divide in halves, recursive
"""
import random
def _find_min_impl(array: list, index: int):
"""
Return the smallest element in the array.
:param index:
:param array:
:return: Smallest element in array. None if array is empty.
"""
if len(array) - 1 == index:
return array[index]
return min(array[index], _find_min_impl(array, index + 1))
def find_min(array: list):
if len(array) == 0:
return None
return _find_min_impl(array, 0)
def test_find_min():
# length of random list
n = random.randint(0, 50)
data = []
for i in range(n):
data.append(random.randint(1, 10))
# insert the known minimal element in a random, but known position
index = random.randint(0, len(data) - 1)
data.insert(index, -1)
print(data)
result = find_min(data)
assert result == -1, (result, index)
# test_find_min()
"""
2. Exponential search
a. Generate a pseudo-random array of increasing elements
b. Implement exponential search
c. Implement binary search
d. Driver & test functions
"""
def random_number_gen():
n = random.randint(50, 1000)
x = random.randint(0, 100)
array = [x]
for i in range(1, n):
array.append(x + i)
return array
def random_number_gen_v2():
array = [random.randint(0, 10)]
for i in range(random.randint(5, 10)):
array.append(array[-1] + random.randint(0, 3))
return array
# print(random_number_gen())
# print(random_number_gen_v2())
# TODO Improve this for cases when the searched element is beyond the (min, max)
# of the list
def exponential_search(data: list, n: int):
if data[0] == n:
return 0, 0
i = 1
# Python will not check the second term of the expression if the first
# is false
while i < len(data) and data[i] < n:
if data[i] == n:
return i, i
else:
i *= 2
return i // 2, min(i, len(data) - 1)
def exponential_search_v2(data: list, n: int):
"""
Search element n in list
:param data:
:param n:
:return: The position of element n, -1 if not found
"""
if data[0] == n:
return 0
i = 1
while i < len(data) and data[i] < n:
i *= 2
for idx in range(i // 2, min(i + 1, len(data) - 1)):
if data[idx] == n:
return idx
return -1
# TODO Start with linear search
# return i // 2, min(i, len(data) - 1)
# data = random_number_gen_v2()
# FIXME does not work in this case
data = [0, 0, 2, 2, 3, 6, 7, 10]
print(data)
print(exponential_search_v2(data, 10))
"""
3. Calculate the r-th root of a given number x with a given precision p
"""
"""
4. Calculate the maximum subarray sum (subarray = elements having continuous indices)
a. Naive implementation
b. Divide & conquer implementation
e.g.
for data = [-2, -5, 6, -2, -3, 1, 5, -6], maximum subarray sum is 7.
"""
"""
Backtracking
"""
"""
5. Recursive implementation for permutations
"""
def consistent(x):
"""
Determines whether the current partial array can lead to a solution
"""
return len(set(x)) == len(x)
def solution(x, n):
"""
Determines whether we have a solution
"""
return len(x) == n
def solution_found(x):
"""
What to do when a solution is found
"""
print("Solution: ", x)
def bkt_rec(x, n):
"""
Backtracking algorithm for permutations problem, recursive implementation
"""
x.append(0)
for i in range(0, n):
x[len(x) - 1] = i
if consistent(x):
if solution(x, n):
solution_found(x)
else:
bkt_rec(x[:], n)
# bkt_rec([], 4)
"""
6. Change the code for generating the permutation above to work for the n-Queen problem
"""
"""
A Latin square is an n × n square filled with n different symbols, each occurring exactly once in each row and exactly
once in each column
7. Generate all the N x N Latin squares for a given number N.
8. Generate all reduced N x N Latin squares for a given number N. In a reduced Latin square, the elements of the first
row and column are sorted.
"""
@@ -0,0 +1,115 @@
"""
Dynamic programming
"""
"""
1. Calculate the maximum subarray sum (subarray = elements having
continuous indices)
e.g.
for data = [-2, -5, 6, -2, -3, 1, 5, -6], maximum subarray sum is 7.
"""
import sys
def max_crossing_sum(array: list, left, mid, right: int):
left_max = -sys.maxsize
left_sum = 0
for i in range(mid, left, -1):
left_sum += array[i]
left_max = max(left_max, left_sum)
right_max = -sys.maxsize
right_sum = 0
for i in range(mid + 1, right):
right_sum += array[i]
right_max = max(right_max, right_sum)
return left_max + right_max
def max_subarray_dc(array: list, left, right: int):
if left >= right:
return array[left]
mid = (left + right) // 2
# T(n) = 2 * T(n/2) + n (looks a lot like merge sort)
return max(max_subarray_dc(array, left, mid),
max_subarray_dc(array, mid + 1, right),
max_crossing_sum(array, left, mid, right))
array = [-2, -5, 6, -2, -3, 1, 5, -6]
# print(max_subarray_dc(array, 0, len(array) - 1))
# import array as arr
def max_subarray_dp(array: list):
s = 0
smax = -9999999999999
init = 1
# v = arr.array('i', [-2, -5, 6, -2, -3, 1, 5, -6])
for i in range(0, len(array)):
# choose whether we extend subarray or start a new one
# max_here = max(array[i], max_here + array[i])
# check for new global maximum
# max_global = max(max_global,max_here)
s += array[i]
# check that we have a new maximum
if s > smax:
smax = s
# don't carry over <0 values
if s < 0:
s = 0
init = i + 1
return smax
# print(max_subarray_dp(array))
"""
2. Count in how many ways we can provide change to a given sum of money (N), when provided infinite
supply of given coin denominations.
e.g. Let's say N = 10, and we have coins of values (1, 5, 10); we can give change in 4 ways (10, 5 + 5, 5 + 1 + ...
and 1 + ... + 1)
"""
"""
3. 0-1 Knapsack problem. Given the weights and values of N items, put them
in a knapsack having capacity W so that you
maximize the value of the stored items. Items cannot be broken up
(0-1 property)
"""
def knapsack_01(W: int, weights, values: list, current: int):
if current < 0:
return 0
value_include = 0
if W - weights[current] >= 0:
value_include = values[current] + knapsack_01(W - weights[current], weights, values, current - 1)
value_exclude = knapsack_01(W, weights, values, current - 1)
# T(n) = 2 * T(n-1) + 1
return max(value_include, value_exclude)
W = 10
weights = [1, 2, 3, 5, 7]
values = [2, 4, 8, 7, 3]
print(knapsack_01(W, weights, values, len(weights) - 1))
"""
4. Gold mine problem (a.k.a checkerboard problem)
https://www.geeksforgeeks.org/gold-mine-problem
"""
@@ -0,0 +1,258 @@
"""
Write an application that manages a list of circles.
Each circle has a unique center (x,y - ints) and a positive radius (int).
The application will have a menu-driven user interface and will provide the following features:
1. Add a circle
- adds the given circle to the list.
- error if circle with given center already exists, the center
or radius not given, empty or radius < 0
2. Delete a circle
- deletes the circle with the given center
- error if non-existing center is given
3. Show all circles
- shows all circles in descending order of their radius
4. Show circles that intersect a given one
- select a circle from the list of existing circles
- print those which intersect it
(bonus: sort printed circles by descending order of radius)
5. exit
- exit the program
Observations:
- Add 10 random circles at program startup
- Write specifications for non-UI functions
- Each function does one thing only, and communicates via parameters and return value
- The program reports errors to the user. It must also report errors from non-UI functions!
- Make sure you understand the circle's representation
- Try to reuse functions across functionalities (Less code to write and test)
- Don't use global variables!
"""
import random
#
# Write the implementation for Seminar 06 in this file
#
#
# Write below this comment
# Functions to deal with circles -- list representation
# -> There should be no print or input statements in this section
# -> Each function should do one thing only
# -> Functions communicate using input parameters and their return values
#
# circle centered at (1,2) with radius 3 => [1, 2, 3]
def new_circle(x, y, radius: int):
return [x, y, radius]
def get_center(circle):
return [circle[0], circle[1]]
def get_radius(circle):
return circle[2]
def to_str(circle):
"""
Return the circle's representation as a string
:param circle: The circle
:return: A string; for circle centered (1,2), radius 4,
return "circle at (1,2) radius 4"
"""
return "circle at (" + str(circle[0]) + "," + str(circle[1]) \
+ ") radius " + str(circle[2])
#
# Write below this comment
# Functions to deal with circles -- dict representation
# -> There should be no print or input statements in this section
# -> Each function should do one thing only
# -> Functions communicate using input parameters and their return values
#
# TODO Copy function signatures from list representation and implement them
# circle centered at (1,2) with radius 3 => {"x": 1,"y": 2,"radius": 3}
# def new_circle(x, y, radius: int):
# return {"x": x, "y": y, "radius": radius}
#
#
# def get_center(circle):
# return circle.pop("radius")
#
#
# def get_radius(circle):
# return circle["radius"]
#
#
# def to_str(circle):
# """
# Return the circle's representation as a string
# :param circle: The circle
# :return: A string; for circle centered (1,2), radius 4,
# return "circle at (1,2) radius 4"
# """
# return "circle at (" + str(circle["x"]) + "," + str(circle["y"]) \
# + ") radius " + str(circle["radius"])
#
# Write below this comment
# Functions that deal with the required functionalities properties
# -> There should be no print or input statements in this section
# -> Each function should do one thing only
# -> Functions communicate using input parameters and their return values
#
def make_random_circles(count: int):
"""
Create count random circles
:return: The list of newly created circles
"""
assert count < 40 ** 2
circles_list = []
centers_x = list(range(-20, 20))
centers_y = list(range(-20, 20))
while count > 0:
x = random.choice(centers_x)
y = random.choice(centers_y)
centers_x.remove(x)
centers_y.remove(y)
radius = random.randint(1, 20)
circles_list.append(new_circle(x, y, radius))
count -= 1
return circles_list
def add_circle(circles_list: list, new_circle):
"""
Adds the new_circle to the list of circles
:param circles_list: List of circles maintained by the program
:param new_circle: The new circle to add
:return: 0 on success, 1 if circle with given center already exists
"""
if new_circle in circles_list:
return 1
circles_list.append(new_circle)
return 0
def delete_circle(circles_list: list, circle):
"""
Deletes a circle from the list of circles
:param circles_list: List of circles maintained by the program
:param circle: The circle to delete
:return: 0 on success, 1 if the circle does not exist
"""
if circle not in circles_list:
return 1
circles_list.remove(circle)
return 0
#
# Write below this comment
# UI section
# Write all functions that have input or print statements here
# Ideally, this section should not contain any calculations relevant to program functionalities
#
def read_circle(circles_list: list):
"""
Reads a circle from the console; Circle center must be int X,Y coordinates,
radius must be > 0 integer (keep reading until true)
:param circles_list: List of circles maintained by the program.
:return: The new circle.
"""
while True:
print()
x = input("Enter new X coordinate: ")
if not x.lstrip('-').isdigit():
print("X must be an integer.")
continue
x = int(x)
y = input("Enter new Y coordinate: ")
if not y.lstrip('-').isdigit():
print("X must be an integer.")
continue
y = int(y)
radius = input("Enter radius (type 0 to stop reading...): ")
if radius == "0":
print("Done reading.")
break
if not radius.isdigit():
print("Radius must be an integer greater than 0!")
continue
radius = int(radius)
return new_circle(x, y, radius)
def show_circles(circles_list):
sorted_list = sorted(circles_list, key=lambda c: get_radius(c), reverse=True)
print("Current list of circles:\n" + ",\n".join(map(to_str, sorted_list)))
def start():
# TODO this is the program's entry point
# What do to here !!!???
# 1. Print out main menu in a loop
# 2. Keep the list of circles
# 3. Call the function corresponding to user choice
# 4. Print out error messages coming from functions
circles_list = make_random_circles(10)
while True:
print()
print("Welcome to Circle Manager 9000.")
print("Please type the number of the operation to execute.")
print()
print("1. Add a bunch of circles.")
print("2. Delete a circle.")
print()
print("3. Show a list of circles in descending order of radius.")
print("4. Show a list of circles which intersect another given circle.")
print()
print("5. Exit")
operation = input()
if operation == "1":
circle = read_circle(circles_list)
exists = add_circle(circles_list, circle)
if exists:
print("Circle already exists in the list!")
else:
print("OK")
elif operation == "2":
circle = read_circle(circles_list)
not_exists = delete_circle(circles_list, circle)
if not_exists:
print("Circle does not exist in the list!")
else:
print("OK")
elif operation == "3":
show_circles(circles_list)
elif operation == "4":
# TODO
pass
elif operation == "5":
print("Goodbye!")
return
else:
print("Unknown operation", operation, "- please try again.")
# print()
# print("Press Enter to continue...")
# input()
if __name__ == "__main__":
start()
@@ -0,0 +1,16 @@
"""
Tic Tac Toe board game
- Human vs. computer player
- Human plays first
- Computer always plays valid moves
Command-driven UI !!!???
play 1,1 # (1,1) is center square on the 3x3 board
takeback # takes back human player's last move
ragequit
What to look out for today:
1. Modular programming
2. Test-Driven Development (first examples)
3. Using Exceptions
"""
@@ -0,0 +1,95 @@
def create_board():
"""
Create the Tic Tac Toe board
:return: The empty game board
"""
board = []
for i in range(3):
board.append([None, None, None])
return board
def get_position_on_board(board, row, col: int):
"""
Return the symbol at (row,col)
:param row:
:param col:
:return: X, O or None
Raise ValueError if (row,col) outside of board
"""
if row not in [0, 1, 2] or col not in [0, 1, 2]:
raise ValueError("Position not on board - (" + str(row) + "," + str(col) + ")")
return board[row][col]
def str_position(board, row, col):
symbol = get_position_on_board(board, row, col)
# This is <=> to what is below
# return ' ' if symbol is None else symbol
if symbol is not None:
return symbol
else:
return ' '
def str_board(board):
"""
Return the board's str representation
:param board: The game board
:return: In str form
"""
gp = str_position
result = "-----\n"
for i in range(3):
result += gp(board, i, 0) + "|" + gp(board, i, 1) + "|" + gp(board, i, 2) + "\n"
result += "-----\n"
return result
def make_move_on_board(board, symbol, row, column):
"""
Represent a move on the board
:param board: The game board
:param symbol: One of ['X', 'O']
:param row, column: Position to play
:return: None
Raise ValueError if invalid symbol, already occupied square or play
outside of board
"""
if symbol not in ['X', 'O']:
raise ValueError("Invalid symbol")
if row not in [0, 1, 2] or column not in [0, 1, 2]:
raise ValueError("Move outside board")
if get_position_on_board(board, row, column) is not None:
raise ValueError("Cannot overwrite board at (" + str(row) + "," + str(column) + ")")
# This works as a setter function
board[row][column] = symbol
def test_board():
board = create_board()
# Check that the board is empty
for i in range(3):
for j in range(3):
assert get_position_on_board(board, i, j) is None
# Make some moves on the board
make_move_on_board(board, 'X', 1, 1)
assert get_position_on_board(board, 1, 1) == 'X'
make_move_on_board(board, 'O', 0, 0)
assert get_position_on_board(board, 0, 0) == 'O'
# Check that moving outside of board raises a ValueError
try:
make_move_on_board(board, 'X', 3, 3)
assert False
except ValueError:
assert True
# Check that we cannot overwrite a square
try:
make_move_on_board(board, 'O', 0, 0)
assert False
except ValueError:
assert True
@@ -0,0 +1,25 @@
import board
def human_move(game_board, row, col):
"""
Record the human's move on the board
:param game_board:
:param row:
:param col:
:return:
"""
board.make_move_on_board(game_board, 'X', row, col)
def computer_move(game_board):
"""
Determine where the computer plays and make the move
:param game_board:
:return: The position where computer moved
"""
for row in [0, 1, 2]:
for col in [0, 1, 2]:
if board.get_position_on_board(game_board, row, col) is None:
board.make_move_on_board(game_board, 'O', row, col)
return row, col
@@ -0,0 +1,39 @@
import board
import game
def start_game():
game_board = board.create_board()
humans_turn = True
while True:
print(board.str_board(game_board))
if humans_turn:
user_input = input(">")
command, params = user_input.split(" ", maxsplit=1)
if command == 'play':
params = params.split(",")
try:
row = int(params[0])
col = int(params[1])
game.human_move(game_board, row, col)
except ValueError as ve:
# TODO Allow user to attempt to make a move
print(ve)
elif command == 'takeback':
# Take back human's last move, if possible
pass
elif command == 'ragequit':
print("Computer wins!")
return
else:
print("Invalid command")
else:
# Computer player's turn
row, col = game.computer_move(game_board)
print("Computer moved at (" + str(row) + "," + str(col) + ")")
humans_turn = not humans_turn
start_game()
@@ -0,0 +1,34 @@
"""
Turning seminar 7 game into object-oriented representation
1. Let's turn the board module functions into the Board class
How can we protect class attributes from outside changes?
protecting class attributes from outside change:
C#, Java, Kotlin, C++ -> public, protected, private (keywords)
public -> anyone can access and change
private -> only class functions can access and change
protected -> only class functions and derived classes can access and change
(default)
Python
<var. name> -> public
_<var. name> -> private (convention)
__<var. name> -> private (convention!?, name mangling)
a. Created the game_board class
b. changed create_board to __init__
c. changed str_board to __str__
d. added remaining methods to class
e. shortened their names as it makes more sense
(e.g., make_move_on_board -> move)
f. rewrote test_board to work with class
2. Let's do the same to the game module
a. Create the game class and add the board as a constructor parameter
b. Add the *_move methods into the class
c. Update them to use the private __board attribute
3. Update the UI module
4. Added the skynet_level_2 random move strategy :)
"""
@@ -0,0 +1,114 @@
class game_board:
def __init__(self):
"""
Create the Tic Tac Toe board
:return: The empty game board
"""
# board is a local variable in __init__
# board = []
# Let's make the game_board object "remember it"
self.__board = []
for i in range(3):
self.__board.append([None, None, None])
# The interpreter returns the object reference in __init__
# return board
def __str_position(self, row, col):
symbol = self.get_position(row, col)
# This is <=> to what is below
# return ' ' if symbol is None else symbol
if symbol is not None:
return symbol
else:
return ' '
def get_position(self, row, col: int):
"""
Return the symbol at (row,col)
:param row:
:param col:
:return: X, O or None
Raise ValueError if (row,col) outside of board
"""
if row not in [0, 1, 2] or col not in [0, 1, 2]:
raise ValueError("Position not on board - (" + str(row) + "," + str(col) + ")")
return self.__board[row][col]
def move(self, symbol, row, column):
"""
Represent a move on the board
:param board: The game board
:param symbol: One of ['X', 'O']
:param row, column: Position to play
:return: None
Raise ValueError if invalid symbol, already occupied square or play
outside of board
"""
if symbol not in ['X', 'O']:
raise ValueError("Invalid symbol")
if row not in [0, 1, 2] or column not in [0, 1, 2]:
raise ValueError("Move outside board")
if self.get_position(row, column) is not None:
raise ValueError("Cannot overwrite board at (" + str(row) + "," + str(column) + ")")
# This works as a setter function
self.__board[row][column] = symbol
def __str__(self):
"""
Return the board's str representation
:param board: The game board
:return: In str form
"""
gp = self.__str_position
result = "-----\n"
for i in range(3):
result += gp(i, 0) + "|" + gp(i, 1) + "|" + gp(i, 2) + "\n"
result += "-----\n"
return result
def test_board():
board = game_board()
# Check that the board is empty
for i in range(3):
for j in range(3):
assert board.get_position(i, j) is None
# Make some moves on the board
board.move('X', 1, 1)
assert board.get_position(1, 1) == 'X'
board.move('O', 0, 0)
assert board.get_position(0, 0) == 'O'
# Check that moving outside of board raises a ValueError
try:
board.move('X', 3, 3)
assert False
except ValueError:
assert True
# Check that we cannot overwrite a square
try:
board.move('O', 0, 0)
assert False
except ValueError:
assert True
gb = game_board()
# data = list() # []
# data.append(1)
# list.append(data, 1)
# print(str(data))
# gb = game_board()
# print(game_board.str_board(gb)) # gb is self from str_board
# print(gb.str_board()) # gb is passed as self implicitely by the interpreter
# print(str(gb)) # how do we tell the interpreter what to call here?
# gb.__board[1][1] = '1234'
# print(gb.__dict__)
# print(gb.__board)
@@ -0,0 +1,65 @@
# import board
from board import game_board
from random import choice
class skynet_level_1():
def __init__(self, board):
self.__board = board
def computer_move(self):
"""
Determine where the computer plays and make the move
:param game_board:
:return: The position where computer moved
"""
for row in [0, 1, 2]:
for col in [0, 1, 2]:
if self.__board.get_position(row, col) is None:
self.__board.move('O', row, col)
return row, col
class skynet_level_2():
def __init__(self, board):
self.__board = board
def computer_move(self):
"""
Determine where the computer plays and make the move
:param game_board:
:return: The position where computer moved
"""
empty_pos = []
for row in [0, 1, 2]:
for col in [0, 1, 2]:
if self.__board.get_position(row, col) is None:
empty_pos.append((row, col))
row, col = choice(empty_pos)
self.__board.move('O', row, col)
return row, col
class game:
def __init__(self, board: game_board, computer_player):
self.__board = board
self.__computer_player = computer_player
def human_move(self, row, col):
"""
Record the human's move on the board
:param game_board:
:param row:
:param col:
:return:
"""
self.__board.move('X', row, col)
# board.make_move_on_board(game_board, )
def computer_move(self):
"""
Determine where the computer plays and make the move
:param game_board:
:return: The position where computer moved
"""
return self.__computer_player.computer_move()
@@ -0,0 +1,44 @@
from board import game_board
from game import game, skynet_level_1, skynet_level_2
def start_game():
board = game_board()
# Strategy design pattern - https://en.wikipedia.org/wiki/Strategy_pattern
# computer_player = skynet_level_1(board)
computer_player = skynet_level_2(board)
ttt_game = game(board, computer_player)
humans_turn = True
while True:
# print(board.str_board(game_board))
print(str(board))
if humans_turn:
user_input = input(">")
command, params = user_input.split(" ", maxsplit=1)
if command == 'play':
params = params.split(",")
try:
row = int(params[0])
col = int(params[1])
ttt_game.human_move(row, col)
except ValueError as ve:
# TODO Allow user to attempt to make a move
print(ve)
elif command == 'takeback':
# Take back human's last move, if possible
pass
elif command == 'ragequit':
print("Computer wins!")
return
else:
print("Invalid command")
else:
# Computer player's turn
row, col = ttt_game.computer_move()
print("Computer moved at (" + str(row) + "," + str(col) + ")")
humans_turn = not humans_turn
start_game()
@@ -0,0 +1,45 @@
"""
Create an application for a car rental business using a console based user interface.
The application must allow keeping records of the companys list of clients, existing car pool and rental history.
The application must allow its users to manage clients, cars and rentals in the following ways:
Clients
 Add a new client. Each client is a physical person having a unique ID (driver license series), name, age.
 Update the data for any client.
 Remove a client from active clients. Note that removing a client must not remove existing car rental statistics.
 Search for clients based on ID and name [both at the same time]
 All client operations must undergo proper validation!
Cars
 Add a new car to the car pool. Each car must have a valid license plate number, a make and model taken from a
list of makes and models. In addition, each car will have a color.
 Remove a car from the car pool.
 Search for cars based on license number, make, model and color.
[make = VW, model = Polo, CJ01ABC], [make = VW, model = Polo, CJ01XYZ]
 All car operations must undergo proper validation!
Rentals
 An existing client can rent one or several cars from the car pool for a determined period. When rented, a car
becomes unavailable for further renting.
 When a car is returned, it becomes available for renting once again.
 Search the rental history of a given client, car, or all rentals during any given period.
Statistics
 The list of all cars in the car pool sorted by number of days they were rented.
 The list of clients sorted descending by the number of cars they have rented.
The application must have support for unlimited undo/redo with cascading.
"""
"""
week 7 - Tic Tac Toe -- modular programming
week 8 - Tic Tac Toe with classes & objects
week 9-11 - moar classes & objects
-> writing domain classes using properties
-> intro to layered architecture
-> intro to the repository layer
-> working with text and binary files
-> intro to inheritance
-> writing our own exception classes !?
"""
@@ -0,0 +1,64 @@
class car:
"""
Add a new car to the car pool. Each car must have
-> a valid license plate number,
-> a make and model taken from a list of makes and models.
-> each car will have a color.
"""
def __init__(self, car_id: str, make: str, model: str, color: str):
self.__car_id = car_id
self.__make = make
self.__model = model
self.__color = color
@property
def car_id(self):
return self.__car_id
@car_id.setter
def car_id(self, new_value):
self.__car_id = new_value
@property
def make(self):
return self.__make
@property
def model(self):
return self.__model
@property
def color(self):
return self.__color
@color.setter
def color(self, new_value):
self.__color = new_value
def __str__(self):
return self.car_id + " -> " + self.make + " " + self.model + ", " + self.color
def test_car():
new_car = car("CJ 01 ABC", "Dacia", "Sandero", "red")
# assert new_car.get_id() == "CJ 01 ABC"
assert new_car.car_id == "CJ 01 ABC"
assert new_car.make == "Dacia"
assert new_car.model == "Sandero"
assert new_car.color == "red"
assert str(new_car) == "CJ 01 ABC -> Dacia Sandero, red"
# repaint it
# new_car.set_color("blue")
new_car.color = "blue"
assert new_car.color == "blue"
assert str(new_car) == "CJ 01 ABC -> Dacia Sandero, blue"
# change license plates
new_car.car_id = "CJ 99 XYZ"
assert str(new_car) == "CJ 99 XYZ -> Dacia Sandero, blue"
if __name__ == "__main__":
test_car()
@@ -0,0 +1,214 @@
from seminar.group_911.seminar_09.domain.car import car
from random import choice, randint
import pickle
# RepoException inherits from Python's builtin Exception class
# RepoException "IS AN" exception
class RepoException(Exception):
pass
class car_repo(object):
def __init__(self):
# keys are car license numbers, values are car objects
self._data = {}
def add(self, new_car: car):
if new_car.car_id in self._data:
raise RepoException("Car already in repo")
self._data[new_car.car_id] = new_car
def get(self, car_id: str):
# If car cannot be found in repo, catch the dict's KeyError and
# re-raise it as RepoException
try:
return self._data[car_id]
except KeyError:
raise RepoException("Car is not in repo")
def get_all(self):
return list(self._data.values())
def __len__(self):
return len(self._data)
class car_repo_bin_file(car_repo):
def __init__(self, file_name="cars.bin"):
# call superclass constructor
super(car_repo_bin_file, self).__init__()
# remember the name of the file we're working with
self._file_name = file_name
# load the cars from the file
self._load_file()
def add(self, new_car: car):
# call the add() method on the super class
# we want to do everything the superclass add() already does
super().add(new_car)
# we also want to save all cars to a text file
self._save_file()
def _load_file(self):
# r - read, b - binary
fin = open(self._file_name, "rb")
obj = pickle.load(fin)
for c in obj:
super().add(c)
fin.close()
def _save_file(self):
# w - write mode (overwrite), b - binary mode
fout = open(self._file_name, "wb")
pickle.dump(self.get_all(), fout)
# NOTE Don't forget to close the file!
fout.close()
# just a plain old regular class :)
class car_repo_text_file(car_repo):
# this class inherits from car_repo
# => has all the mathods and attributes in car_repo
def __init__(self, file_name="cars.txt"):
# call superclass constructor
super(car_repo_text_file, self).__init__()
# remember the name of the file we're working with
self._file_name = file_name
# load the cars from the file
self._load_file()
def _load_file(self):
"""
Load the cars from a text file
"""
# open a text file for reading
# t - text file mode, r - reading
lines = []
try:
fin = open(self._file_name, "rt")
# each car should be on its own line
lines = fin.readlines()
# close the file when done reading
fin.close()
except IOError:
# It's ok if we don't find the input file
pass
for line in lines:
current_line = line.split(",")
new_car = car(current_line[0].strip(), current_line[1].strip(), current_line[2].strip(),
current_line[3].strip())
# NOTE call super() so that we don't write the file we're reading from
super().add(new_car)
def _save_file(self):
"""
Save all cars to a text file
"""
# open a text file for writing
# t - text file mode, w - writing (rewrite the file every time)
fout = open(self._file_name, "wt")
# writes car_string into the text file
# fout.write(car_string)
for car in self.get_all():
car_string = str(car.car_id) + "," + str(car.make) + "," + str(car.model) + "," + str(car.color) + "\n"
fout.write(car_string)
# call close when done writing
fout.close()
def add(self, new_car: car):
# call the add() method on the super class
# we want to do everything the superclass add() already does
super().add(new_car)
# we also want to save all cars to a text file
self._save_file()
#
# def test_car_repo():
# repo = car_repo()
# # car repository is empty
# assert len(repo) == 0
#
# # add cars to the repo
# c1 = car("CJ 01 ABC", "Dacia", "Sandero", "red")
# repo.add(c1)
# c2 = car("CJ 01 XYZ", "Dacia", "Logdy", "white")
# repo.add(c2)
# assert len(repo) == 2
#
# # try to add the same car again
# try:
# repo.add(c1)
# assert False
# except RepoException:
# assert True
#
# # retrieve cars from repo
# assert repo.get("CJ 01 ABC") == c1
#
# # TODO Try to implement repo["CJ 01 XYZ"] == c2
# assert repo.get("CJ 01 XYZ") == c2
#
# # try to retrieve a non-existing car
# try:
# repo.get("SJ 04 RTY")
# assert False
# except RepoException:
# assert True
def generate_cars(n: int):
"""
Generates n car instances
:return: A list of n cars
"""
counties = ["AB", "SJ", "VS", "CJ", "B", "TL", "TR", "GL", "GR", "IS"]
make_model = {"Dacia": ["Logan", "Sandero", "Lodgy"], "Toyota": ["Corolla", "RAV-4", "Yaris"]}
colors = ["red", "blue", "green", "black"]
result = []
while n > 0:
letters = ""
for i in [0, 1, 2]:
letters += chr(randint(65, 90)) # A -> Z
car_id = choice(counties) + " " + str(randint(10, 99)) + " " + letters
make = choice(list(make_model.keys()))
model = choice(make_model[make])
color = choice(colors)
result.append(car(car_id, make, model, color))
n -= 1
return result
if __name__ == "__main__":
# repo = car_repo()
# repo_text = car_repo_text_file()
# # NOTE Save the generated cars to the file
# for c in generate_cars(10):
# print(str(c))
# # repo.add(c)
# repo_text.add(c)
# read the cars.bin input file
car_repo_bin = car_repo_bin_file()
print("Cars saved in cars.bin")
for c in car_repo_bin.get_all():
print(str(c))
# read the cars.txt file
car_repo_text = car_repo_text_file()
print("\n\nCars saved in cars.txt")
for c in car_repo_text.get_all():
print(str(c))
# NOTE Load the cars and display them again
# new_car_repo = car_repo_text_file()
# for c in new_car_repo.get_all():
# print(str(c))
@@ -0,0 +1,40 @@
VS 65 HNV,Dacia,Logan,blue
TL 51 QQP,Dacia,Lodgy,red
IS 97 EJG,Toyota,Yaris,red
TR 91 KTU,Dacia,Sandero,blue
TL 89 SSI,Dacia,Sandero,black
TL 23 LTR,Dacia,Sandero,green
TR 24 UVD,Toyota,RAV-4,red
CJ 92 TRD,Dacia,Logan,green
CJ 36 ZIA,Dacia,Logan,red
IS 66 DJX,Dacia,Sandero,blue
CJ 51 HCN,Dacia,Lodgy,red
VS 23 GWQ,Toyota,Yaris,red
IS 59 WCC,Toyota,Corolla,red
CJ 92 YDU,Toyota,Yaris,green
AB 19 FIN,Dacia,Lodgy,blue
TL 70 GYH,Toyota,Corolla,red
TR 64 QBZ,Dacia,Logan,blue
TL 79 NEB,Dacia,Logan,black
GL 42 RKZ,Dacia,Lodgy,green
CJ 56 KNZ,Toyota,RAV-4,green
GR 29 DUQ,Toyota,RAV-4,green
AB 65 OJD,Dacia,Sandero,red
TL 53 KYY,Dacia,Lodgy,green
GL 58 ETL,Dacia,Logan,black
IS 73 YZI,Dacia,Lodgy,black
IS 61 VWR,Dacia,Sandero,green
CJ 57 FTB,Toyota,Corolla,black
B 36 VEF,Toyota,Yaris,blue
GR 17 ERN,Toyota,RAV-4,green
AB 40 ZXT,Dacia,Sandero,green
GR 95 USA,Toyota,Yaris,green
SJ 39 JTW,Dacia,Logan,blue
AB 93 WVL,Dacia,Sandero,black
TL 62 DZS,Dacia,Lodgy,green
IS 23 CUK,Dacia,Logan,blue
TR 99 JYB,Toyota,Corolla,green
TL 97 TRC,Toyota,Yaris,green
AB 19 SMK,Dacia,Logan,green
B 54 FNU,Dacia,Sandero,black
IS 27 WBE,Toyota,Yaris,green
@@ -0,0 +1,64 @@
class Car:
"""
Add a new car to the car pool. Each car must have
-> a valid license plate number,
-> a make and model taken from a list of makes and models.
-> each car will have a color.
"""
def __init__(self, car_id: str, make: str, model: str, color: str):
self.__car_id = car_id
self.__make = make
self.__model = model
self.__color = color
@property
def car_id(self):
return self.__car_id
@car_id.setter
def car_id(self, new_value):
self.__car_id = new_value
@property
def make(self):
return self.__make
@property
def model(self):
return self.__model
@property
def color(self):
return self.__color
@color.setter
def color(self, new_value):
self.__color = new_value
def __str__(self):
return self.car_id + " -> " + self.make + " " + self.model + ", " + self.color
def test_car():
new_car = Car("CJ 01 ABC", "Dacia", "Sandero", "red")
# assert new_car.get_id() == "CJ 01 ABC"
assert new_car.car_id == "CJ 01 ABC"
assert new_car.make == "Dacia"
assert new_car.model == "Sandero"
assert new_car.color == "red"
assert str(new_car) == "CJ 01 ABC -> Dacia Sandero, red"
# repaint it
# new_car.set_color("blue")
new_car.color = "blue"
assert new_car.color == "blue"
assert str(new_car) == "CJ 01 ABC -> Dacia Sandero, blue"
# change license plates
new_car.car_id = "CJ 99 XYZ"
assert str(new_car) == "CJ 99 XYZ -> Dacia Sandero, blue"
if __name__ == "__main__":
test_car()
@@ -0,0 +1,27 @@
from seminar.group_911.seminar_11.domain.exceptions import CarValidationException
class CarValidatorRO:
@staticmethod
def _is_license_valid(license):
# TODO Implement full validation
"""
Implement Romanian license plate validation
@param license:
@return: ...
"""
return len(license) > 2
# FIXME Duplicated code across validators, use inheritance to remove it
def validate(self, car):
errors = []
# V1 - All properties are non-empty
if not CarValidatorRO._is_license_valid(car.license_plate):
errors.append('Invalid license plate')
if len(car.make) < 2:
errors.append('Car make should have at least 3 letters')
if len(car.model) < 2:
errors.append('Car model should have at least 3 letters')
if len(errors) > 0:
raise CarValidationException(errors)
@@ -0,0 +1,45 @@
class Client:
def __init__(self, client_id, cnp, name):
self._client_id = client_id
self._cnp = cnp
self._name = name
@property
def id(self):
return self._client_id
@property
def cnp(self):
return self._cnp
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
def __eq__(self, z):
"""
Two Clients are equal if they have the same id
:param z:
:return:
"""
if type(z) != Client:
return False
return self.id == z.id
def __str__(self):
return "Id=" + str(self.id) + ", Name=" + str(self.name)
def __repr__(self):
return str(self)
if __name__ == "__main__":
c1 = Client(100, "280122334506070", "Pop Maria")
c2 = Client(101, "2334506070", "Pop Maria")
print(id(c1), id(c2))
# Note fun with == __eq__
print(c1 == [])
@@ -0,0 +1,5 @@
class ClientValidator:
# TODO Implement
def validate(self, client):
return True
@@ -0,0 +1,35 @@
class ValidatorException(Exception):
def __init__(self, message_list="Validation error!"):
self._message_list = message_list
@property
def messages(self):
return self._message_list
def __str__(self):
result = ""
for message in self.messages:
result += message
result += "\n"
return result
class CarException(Exception):
def __init__(self, msg):
self._msg = msg
def __str__(self):
return self._msg
class CarValidationException(CarException):
def __init__(self, error_list):
self._errors = error_list
def __str__(self):
result = ''
for er in self._errors:
result += er
result += '\n'
return result
@@ -0,0 +1,74 @@
from datetime import date
from seminar.group_911.seminar_11.domain.car import Car
from seminar.group_911.seminar_11.domain.client import Client
class Rental:
def __init__(self, rental_id: int, start: date, end: date, client: Client, car: Car):
self._rentalId = rental_id
self._client = client
self._car = car
self._start = start
self._end = end
@property
def id(self):
return self._rentalId
@property
def client(self):
return self._client
@client.setter
def client(self, client):
self._client = client
@property
def car(self):
return self._car
@car.setter
def car(self, car):
self._car = car
@property
def start(self):
return self._start
@start.setter
def start(self, start):
self._start = start
@property
def end(self):
return self._end
@end.setter
def end(self, end):
self._end = end
# len(rental)
def __len__(self):
if self._end is not None:
return (self._end - self._start).days + 1
today = date.today()
return (today - self._start).days + 1
def __repr__(self):
return str(self)
def __str__(self):
return "Rental: " + str(self.id) + "\nCar: " + str(self.car) + "\nClient: " + str(
self.client) + "\nPeriod: " + self._start.strftime("%Y-%m-%d") + " to " + self._end.strftime("%Y-%m-%d")
if __name__ == "__main__":
car = Car("CJ 01 ABC", "Dacia", "Sandero", "red")
client = Client(100, "280122334506070", "Pop Maria")
r = Rental(100, date(2022, 10, 15), date(2022, 11, 20), client, car)
print(len(r))
r = Rental(100, date(2022, 10, 15), None, client, car)
print(len(r))
@@ -0,0 +1,19 @@
from datetime import date
from seminar.group_911.seminar_11.domain.exceptions import ValidatorException
from seminar.group_911.seminar_11.domain.rental import Rental
class RentalValidator:
def validate(self, rental):
if isinstance(rental, Rental) is False:
raise TypeError("Not a Rental")
_errorList = []
now = date(2000, 1, 1)
if rental.start < now:
_errorList.append("Rental starts in past;")
if len(rental) < 1:
_errorList.append("Rental must be at least 1 day;")
if len(_errorList) > 0:
raise ValidatorException(_errorList)
@@ -0,0 +1,214 @@
from seminar.group_911.seminar_11.domain.car import Car
from random import choice, randint
import pickle
# RepoException inherits from Python's builtin Exception class
# RepoException "IS AN" exception
class RepoException(Exception):
pass
class car_repo(object):
def __init__(self):
# keys are car license numbers, values are car objects
self._data = {}
def add(self, new_car: Car):
if new_car.car_id in self._data:
raise RepoException("Car already in repo")
self._data[new_car.car_id] = new_car
def get(self, car_id: str):
# If car cannot be found in repo, catch the dict's KeyError and
# re-raise it as RepoException
try:
return self._data[car_id]
except KeyError:
raise RepoException("Car is not in repo")
def get_all(self):
return list(self._data.values())
def __len__(self):
return len(self._data)
class car_repo_bin_file(car_repo):
def __init__(self, file_name="cars.bin"):
# call superclass constructor
super(car_repo_bin_file, self).__init__()
# remember the name of the file we're working with
self._file_name = file_name
# load the cars from the file
self._load_file()
def add(self, new_car: Car):
# call the add() method on the super class
# we want to do everything the superclass add() already does
super().add(new_car)
# we also want to save all cars to a text file
self._save_file()
def _load_file(self):
# r - read, b - binary
fin = open(self._file_name, "rb")
obj = pickle.load(fin)
for c in obj:
super().add(c)
fin.close()
def _save_file(self):
# w - write mode (overwrite), b - binary mode
fout = open(self._file_name, "wb")
pickle.dump(self.get_all(), fout)
# NOTE Don't forget to close the file!
fout.close()
# just a plain old regular class :)
class car_repo_text_file(car_repo):
# this class inherits from car_repo
# => has all the mathods and attributes in car_repo
def __init__(self, file_name="cars.txt"):
# call superclass constructor
super(car_repo_text_file, self).__init__()
# remember the name of the file we're working with
self._file_name = file_name
# load the cars from the file
self._load_file()
def _load_file(self):
"""
Load the cars from a text file
"""
# open a text file for reading
# t - text file mode, r - reading
lines = []
try:
fin = open(self._file_name, "rt")
# each car should be on its own line
lines = fin.readlines()
# close the file when done reading
fin.close()
except IOError:
# It's ok if we don't find the input file
pass
for line in lines:
current_line = line.split(",")
new_car = Car(current_line[0].strip(), current_line[1].strip(), current_line[2].strip(),
current_line[3].strip())
# NOTE call super() so that we don't write the file we're reading from
super().add(new_car)
def _save_file(self):
"""
Save all cars to a text file
"""
# open a text file for writing
# t - text file mode, w - writing (rewrite the file every time)
fout = open(self._file_name, "wt")
# writes car_string into the text file
# fout.write(car_string)
for car in self.get_all():
car_string = str(car.car_id) + "," + str(car.make) + "," + str(car.model) + "," + str(car.color) + "\n"
fout.write(car_string)
# call close when done writing
fout.close()
def add(self, new_car: Car):
# call the add() method on the super class
# we want to do everything the superclass add() already does
super().add(new_car)
# we also want to save all cars to a text file
self._save_file()
#
# def test_car_repo():
# repo = car_repo()
# # car repository is empty
# assert len(repo) == 0
#
# # add cars to the repo
# c1 = car("CJ 01 ABC", "Dacia", "Sandero", "red")
# repo.add(c1)
# c2 = car("CJ 01 XYZ", "Dacia", "Logdy", "white")
# repo.add(c2)
# assert len(repo) == 2
#
# # try to add the same car again
# try:
# repo.add(c1)
# assert False
# except RepoException:
# assert True
#
# # retrieve cars from repo
# assert repo.get("CJ 01 ABC") == c1
#
# # TODO Try to implement repo["CJ 01 XYZ"] == c2
# assert repo.get("CJ 01 XYZ") == c2
#
# # try to retrieve a non-existing car
# try:
# repo.get("SJ 04 RTY")
# assert False
# except RepoException:
# assert True
def generate_cars(n: int):
"""
Generates n car instances
:return: A list of n cars
"""
counties = ["AB", "SJ", "VS", "CJ", "B", "TL", "TR", "GL", "GR", "IS"]
make_model = {"Dacia": ["Logan", "Sandero", "Lodgy"], "Toyota": ["Corolla", "RAV-4", "Yaris"]}
colors = ["red", "blue", "green", "black"]
result = []
while n > 0:
letters = ""
for i in [0, 1, 2]:
letters += chr(randint(65, 90)) # A -> Z
car_id = choice(counties) + " " + str(randint(10, 99)) + " " + letters
make = choice(list(make_model.keys()))
model = choice(make_model[make])
color = choice(colors)
result.append(Car(car_id, make, model, color))
n -= 1
return result
if __name__ == "__main__":
# repo = car_repo()
# repo_text = car_repo_text_file()
# # NOTE Save the generated cars to the file
# for c in generate_cars(10):
# print(str(c))
# # repo.add(c)
# repo_text.add(c)
# read the cars.bin input file
car_repo_bin = car_repo_bin_file()
print("Cars saved in cars.bin")
for c in car_repo_bin.get_all():
print(str(c))
# read the cars.txt file
car_repo_text = car_repo_text_file()
print("\n\nCars saved in cars.txt")
for c in car_repo_text.get_all():
print(str(c))
# NOTE Load the cars and display them again
# new_car_repo = car_repo_text_file()
# for c in new_car_repo.get_all():
# print(str(c))
@@ -0,0 +1,13 @@
from seminar.group_911.seminar_11.domain.client import Client
from seminar.group_911.seminar_11.repository.repository_exception import RepositoryException
class ClientRepo:
# TODO Finish implementation
def __init__(self):
self._clients = {}
def add(self, client):
if client.id in self._clients.keys():
raise RepositoryException("Duplicate Client id")
self._clients[client.id] = client
@@ -0,0 +1,23 @@
import datetime
from seminar.group_911.seminar_11.repository.repository_exception import RepositoryException
class RentalRepository:
# TODO Finish implementation
def __init__(self):
self._data = {}
def add(self, rental):
if rental.id in self._data.keys():
raise RepositoryException("Duplicate Rental ID")
self._data[rental.id] = rental
def remove(self, rental_id):
if rental_id in self._data.keys():
del self.data[rental_id]
else:
raise RepositoryException("Rental was not found")
def get_all(self):
return list(self._data.values())
@@ -0,0 +1,10 @@
class RepositoryException(Exception):
def __init__(self, message):
self._message = message
@property
def message(self):
return self._message
def __str__(self):
return self._message
@@ -0,0 +1,23 @@
from seminar.group_911.seminar_11.domain.car import Car
class CarService:
def __init__(self, repo, validator):
# self._repo = CarRepo()
# NOTE Taking parameters in ctor allows you to change them
self._repo = repo
self._validator = validator
def add_car(self, car_id: str, car_make: str, car_model: str, color: str):
"""
Add a new car
"""
# 1. Build Car instance
car = Car(car_id, car_make, car_model, color)
# 2. Validate Car instance
self._validator.validate(car)
# 3. Add car to repo
self._repo.add(car)
def get_all(self):
return self._repo.get_all()
@@ -0,0 +1,72 @@
from datetime import date
from seminar.group_911.seminar_11.domain.car import Car
from seminar.group_911.seminar_11.domain.client import Client
from seminar.group_911.seminar_11.domain.rental import Rental
from seminar.group_911.seminar_11.repository.rental_repo import RentalRepository
from seminar.group_911.seminar_11.services.car_service import CarService
class CarsRentalsDTO:
"""
Data transfer object for car rental statistic
Holds the number of total rental days for one specific car
"""
def __init__(self, car: Car, rental_days: int):
self._car = car
self._rental_days = rental_days
@property
def days(self):
return self._rental_days
@days.setter
def days(self, new_value):
self._rental_days = new_value
def __repr__(self):
return str(self.days) + " days for -> " + str(self._car)
class RentalService:
def __init__(self, repo: RentalRepository, car_service: CarService, validator):
self._repo = repo
# NOTE all *Service classes are the same layer so they can know about each other
self._car_service = car_service
self._validator = validator
def add_rental(self, rental_start: date, rental_end: date, client: Client, car: Car):
# 1. Build Rental instance
# TODO rental ID!
rent = Rental(100, rental_start, rental_end, client, car)
# 2. Validate it
self._validator.validate(rent)
# 3. Add to repo
self._repo.add(rent)
#  The list of all cars in the car pool sorted by number of days they
# were rented.
def cars_sorted_by_rental_days(self):
# let's print all cars
cars = self._car_service.get_all()
# print(cars)
# let's print all rentals
rentals = self._repo.get_all()
# print(rentals)
# NOTE license plates are keys, DTO instances are values
rental_dict = {}
for rental in self._repo.get_all():
license_plate = rental.car.car_id
if license_plate in rental_dict:
rental_dict[license_plate].days += len(rental)
else:
rental_dict[license_plate] = CarsRentalsDTO(rental.car, len(rental))
# move data from the {} to []
result = list(rental_dict.values())
result.sort(key=lambda x: x.days, reverse=True)
return result
@@ -0,0 +1,108 @@
"""
Create an application for a car rental business using a console based user interface.
The application must allow keeping records of the companys list of clients, existing car pool and rental history.
The application must allow its users to manage clients, cars and rentals in the following ways:
Clients
 Add a new client. Each client is a physical person having a unique ID (driver license series), name, age.
 Update the data for any client.
 Remove a client from active clients. Note that removing a client must not remove existing car rental statistics.
 Search for clients based on ID and name [both at the same time]
 All client operations must undergo proper validation!
Cars
 Add a new car to the car pool. Each car must have a valid license plate number, a make and model taken from a
list of makes and models. In addition, each car will have a color.
 Remove a car from the car pool.
 Search for cars based on license number, make and model and color.
[search with no parameters displays everything, omitting a param disregards it]
[make = VW, model = Polo, CJ01ABC], [make = VW, model = Polo, CJ01XYZ]
 All car operations must undergo proper validation!
Rentals
 An existing client can rent one or several cars from the car pool for a determined period. When rented, a car
becomes unavailable for further renting.
 When a car is returned, it becomes available for renting once again.
 Search the rental history of a given client, car, or all rentals during any given period.
Statistics
 The list of all cars in the car pool sorted by number of days they were rented.
 The list of clients sorted descending by the number of cars they have rented.
The application must have support for unlimited undo/redo with cascading.
"""
import random
from datetime import date, timedelta
from seminar.group_911.seminar_09.repository.repo_memory import car_repo_bin_file
from seminar.group_911.seminar_11.domain.car import Car
from seminar.group_911.seminar_11.domain.car_validators import CarValidatorRO
from seminar.group_911.seminar_11.domain.client import Client
from seminar.group_911.seminar_11.domain.client_validators import ClientValidator
from seminar.group_911.seminar_11.domain.rental import Rental
from seminar.group_911.seminar_11.domain.rental_validators import RentalValidator
from seminar.group_911.seminar_11.repository.car_repo import car_repo_text_file
from seminar.group_911.seminar_11.repository.client_repo import ClientRepo
from seminar.group_911.seminar_11.repository.rental_repo import RentalRepository
# Initialize repositories
from seminar.group_911.seminar_11.services.car_service import CarService
from seminar.group_911.seminar_11.services.client_service import ClientService
from seminar.group_911.seminar_11.services.rental_service import RentalService
from seminar.group_911.seminar_11.ui.ui import UI
car_repo = car_repo_text_file()
# for c in car_repo.get_all():
# print(c)
def generate_rentals(n: int):
car_repo = car_repo_text_file()
client = Client(100, "290010203445566", "Pop Maria")
# TODO Generate n rentals
# all rentals have the same client (Pop Maria)
# rented car may vary
# Rental - id, client, car, start_date, end_date
# TODO - generate a random start date and end date
# select a car from the list of existing cars
# have a unique rental_id (start from 1000 and +1 for each instance)
# return the list of rentals
rental_id = 1000
rentals = []
while n > 0:
rd = random.randint
start_date = date(rd(2021, 2022), rd(1, 12), rd(1, 28))
day_count = timedelta(days=rd(1, 10))
end_here = start_date + day_count
rentals.append(Rental(rental_id, start_date, end_here, client, random.choice(car_repo.get_all())))
rental_id += 1
n -= 1
return rentals
# rentals = generate_rentals(10)
# print(rentals)
# NOTE you should be able to change the types of repos, services etc.
# car_repo = car_repo_bin_file()
client_repo = ClientRepo()
rent_repo = RentalRepository()
for rental in generate_rentals(100):
rent_repo.add(rental)
# Start up services layer
# NOTE dependency injection of car repository for car service
car_service = CarService(car_repo, CarValidatorRO())
# client_service = ClientService(client_repo, ClientValidator())
rent_service = RentalService(rent_repo, car_service, RentalValidator())
# TODO Move this code to the UI
for r in rent_service.cars_sorted_by_rental_days():
print(r)
"""
ui = UI(car_service, client_service, rent_service)
ui.start()
"""
@@ -0,0 +1,33 @@
class UI:
def __init__(self, car_service, rent_service, client_service):
pass
"""
Layered architecture
ui -> user interface
-> all print/input (or all GUI windows/dialogs/menus etc)
-> in our case we catch exception and display them here
services
-> below the UI layer (UI calls functions in services)
-> does not know about the UI
-> forward calls to repo, implement functionalities (undo/redo, statistics, search, etc.)
repository
-> stores everything (preferably using files/SQL/noSQL)
-> does not know about UI, services
domain
-> classes that we find in the problem statement (cars, expense, client, student, book, etc.)
-> does not know about any layer
function call direction:
ui -> services -> repository
Dependency injection
-> e.g., services need a repo to work, but you can vary the repo implementation
Statistics
 The list of all cars in the car pool sorted by number of days they
were rented.
 The list of clients sorted descending by the number of cars they have rented.
"""
@@ -0,0 +1,91 @@
from seminar.group_911.seminar_12.domain.validator_exception import ValidatorException
class Car:
def __init__(self, _id, license_plate, make, model):
self._id = _id
self._license = license_plate
self._make = make
self._model = model
@property
def id(self):
return self._id
@property
def license(self):
return self._license
@property
def make(self):
return self._make
@property
def model(self):
return self._model
def __eq__(self, z):
if not isinstance(z, Car):
return False
return self.id == z.id
def __str__(self):
return "Id: " + str(self.id) + ", License: " + self.license + ", Car type: " + self.make + ", " + self.model
def __repr__(self):
return str(self)
class CarValidator:
def __init__(self):
# and so on...
self.__counties = ["AB", "B", "CJ"]
self._errors = ""
def _license_valid(self, plate):
token = str(plate).split(' ')
if len(token) != 3:
return False
if token[0] not in self.__counties:
return False
try:
n = int(token[1])
if len(token[1]) < 2 or len(token[1]) > 3:
return False
if n < 1 or n > 999:
return False
if n > 99 and token[0] != "B":
return False
except TypeError:
return False
if len(token[2]) != 3:
return False
tu = str(token[2]).upper()
if tu[0] in ['I', 'O']:
return False
for x in tu:
if x < 'A' or x > 'Z':
return False
if x == 'Q':
return False
return True
def validate(self, car):
"""
Validate if provided Car instance is valid
car - Instance of Car type
Return List of validation errors. An empty list if instance is valid.
"""
if isinstance(car, Car) == False:
raise TypeError("Can only validate Car objects!")
_errors = []
if len(car.make) == 0:
_errors.append("Car must have x make")
if len(car.model) == 0:
_errors.append("Car must have x model;")
if self._license_valid(car.license) is False:
_errors.append("Bad license plate number;")
if len(_errors) > 0:
raise ValidatorException(_errors)
return True
@@ -0,0 +1,9 @@
class CarRentalException(Exception):
def __init__(self, msg):
self._message = msg
def getMessage(self):
return self._message
def __str__(self):
return self._message
@@ -0,0 +1,56 @@
class Client:
def __init__(self, _id, cnp, name):
self._id = _id
self._cnp = cnp
self._name = name
@property
def id(self):
return self._id
@property
def cnp(self):
return self._cnp
@property
def name(self):
return self._name
def __eq__(self, z):
if isinstance(z, Client) is False:
return False
return self.id == z.id
def __str__(self):
return "Id=" + str(self.id) + ", Name=" + str(self.name)
def __repr__(self):
return str(self)
class ClientValidator:
def _is_cnp_valid(self, cnp):
# SAALLZZJJNNNC
if len(cnp) != 13:
# This is not x full CNP validation
return False
for x in cnp:
if x < '0' or x > '9':
return False
return True
def validate(self, client):
"""
Validate if provided Client instance is valid
client - Instance of Client type
Return List of validation errors. An empty list if instance is valid.
"""
if isinstance(client, Client) is False:
raise TypeError("Not x Client")
_errors = []
if self._is_cnp_valid(client.cnp) is False:
_errors.append("CNP not valid.;")
if len(client.name) == 0:
_errors.append("Name not valid.")
if len(_errors) != 0:
raise ValueError(_errors)
@@ -0,0 +1,57 @@
from datetime import date
from seminar.group_911.seminar_12.domain.validator_exception import ValidatorException
class Rental:
def __init__(self, _id, start, end, client, car):
self._id = _id
self._client = client
self._car = car
self._start = start
self._end = end
@property
def id(self):
return self._id
@property
def client(self):
return self._client
@property
def car(self):
return self._car
@property
def start(self):
return self._start
@property
def end(self):
return self._end
def __len__(self):
return (self._end - self._start).days + 1
def __repr__(self):
return str(self)
def __str__(self):
return "Rental: " + str(self.id) + "\nCar: " + str(self.car) + "\nClient: " + str(
self.client) + "\nPeriod: " + self.start.strftime("%Y-%m-%d") + " to " + self.end.strftime("%Y-%m-%d")
class RentalValidator:
def validate(self, rental):
if isinstance(rental, Rental) is False:
raise TypeError("Not a Rental")
_errorList = []
now = date(2000, 1, 1)
if rental.start < now:
_errorList.append("Rental starts in past;")
if len(rental) < 1:
_errorList.append("Rental must be at least 1 day;")
if len(_errorList) > 0:
raise ValidatorException(_errorList)
@@ -0,0 +1,13 @@
class ValidatorException(Exception):
def __init__(self, messageList):
self._messageList = messageList
def getMessage(self):
return self._messageList
def __str__(self):
result = ""
for message in self.getMessage():
result += message
result += "\n"
return result
@@ -0,0 +1,60 @@
from seminar.group_911.seminar_12.repository.repository_exception import RepositoryException
class Repository:
"""
Repository for storing IDObject instances
"""
def __init__(self):
self._objects = []
def store(self, obj):
if self.find(obj.id) is not None:
raise RepositoryException("Element having id=" + str(obj.id) + " already stored!")
self._objects.append(obj)
def update(self, object):
"""
Update the instance given as parameter. The provided instance replaces the one having the same ID
object - The object that will be updated
Raises RepositoryException in case the object is not contained within the repository
"""
el = self.find(object.id)
if el is None:
raise RepositoryException("Element not found!")
idx = self._objects.index(el)
self._objects.remove(el)
self._objects.insert(idx, object)
def find(self, objectId):
for e in self._objects:
if objectId == e.id:
return e
return None
def delete(self, objectId):
"""
Remove the object with given objectId from repository
objectId - The objectId that will be removed
Returns the object that was removed
Raises RepositoryException if object with given objectId is not contained in the repository
"""
object = self.find(objectId)
if object is None:
raise RepositoryException("Element not in repository!")
self._objects.remove(object)
return object
def get_all(self):
return self._objects
def __len__(self):
return len(self._objects)
def __str__(self):
r = ""
for e in self._objects:
r += str(e)
r += "\n"
return r
@@ -0,0 +1,9 @@
class RepositoryException(Exception):
def __init__(self, message):
self._message = message
def get_message(self):
return self._message
def __str__(self):
return self._message
@@ -0,0 +1,6 @@
class CarRentalException(Exception):
def __init__(self, msg):
self.__msg = msg
def __str__(self):
return str(self.__msg)
@@ -0,0 +1,44 @@
from seminar.group_911.seminar_12.domain.car import Car
from seminar.group_911.seminar_12.service.undo_service import call, operation
class CarService:
def __init__(self, undo_service, rental_service, validator, repository):
self._validator = validator
self._repository = repository
self._rental_service = rental_service
self._undo_service = undo_service
def create(self, car_id, license_plate, car_make, car_model):
car = Car(car_id, license_plate, car_make, car_model)
self._validator.validate(car)
self._repository.store(car)
return car
def delete(self, car_id):
"""
1. Delete the car from the repository
"""
car = self._repository.delete(car_id)
# undo/redo support for car deletion
redo = call(self.delete, car_id)
undo = call(self.create, car.id, car.license, car.make, car.model)
self._undo_service.record_for_undo(operation(undo, redo))
'''
2. Delete its rentals
NB! This implementation is not transactional, i.e. the two delete operations are performed separately
'''
# NOTE also add rentals to undo/redo support
rentals = self._rental_service.filter_rentals(None, car)
for rent in rentals:
self._rental_service.delete_rental(rent.id)
return car
def update(self, car):
"""
NB! Undo/redo is also needed here
"""
# TODO Implement later...
pass
@@ -0,0 +1,49 @@
from seminar.group_911.seminar_12.domain.client import Client
from seminar.group_911.seminar_12.service.undo_service import call, operation
class ClientService:
def __init__(self, undo_service, rental_service, validator, repository):
self._validator = validator
self._repository = repository
self._rental_service = rental_service
self._undo_service = undo_service
def create(self, client_id, client_cnp, client_name):
client = Client(client_id, client_cnp, client_name)
self._validator.validate(client)
self._repository.store(client)
return client
def delete(self, client_id):
"""
1. Delete the client
"""
client = self._repository.delete(client_id)
# NOTE Record client deletion for undo/redo
undo = call(self.create, client.id, client.cnp, client.name)
redo = call(self.delete, client.id)
# Operations go on the undo/redo stack
self._undo_service.record_for_undo(operation(undo, redo))
'''
2. Delete their rentals
NB! This implementation is not transactional, i.e. the two delete operations are performed separately
'''
# TODO Add undo/redo support for the client's rentals
rentals = self._rental_service.filter_rentals(client, None)
for rent in rentals:
self._rental_service.delete_rental(rent.getId(), False)
return client
def get_client_count(self):
return len(self._repository)
def update(self, car):
"""
NB! Undo/redo is also needed here
"""
pass
@@ -0,0 +1,62 @@
from seminar.group_911.seminar_12.domain.car_rental_exception import CarRentalException
from seminar.group_911.seminar_12.domain.rental import Rental
class RentalService:
"""
Service for rental operations
"""
def __init__(self, undo_service, validator, rental_repo, car_repo, client_repo):
self._validator = validator
self._carRepo = car_repo
self._cliRepo = client_repo
self._repository = rental_repo
self._undoController = undo_service
def create_rental(self, rental_id, client, car, start, end):
rental = Rental(rental_id, start, end, client, car)
self._validator.validate(rental)
'''
Check the car's availability for the given period
'''
if self.is_car_available(rental.car, rental.start, rental.end) is False:
raise CarRentalException("Car is not available during that time!")
self._repository.store(rental)
return rental
def is_car_available(self, car, start, end):
"""
Check the availability of the given car to be rented in the provided time period
car - The availability of this car is verified
start, end - The time span. The car is available if it is not rented in this time span
Return True if the car is available, False otherwise
"""
rentals = self.filter_rentals(None, car)
for rent in rentals:
if start > rent.end or end < rent.start:
continue
return False
return True
def filter_rentals(self, client, car):
"""
Return a list of rentals performed by the provided client for the provided car
client - The client performing the rental. None means all clients
cars - The rented car. None means all cars
"""
result = []
for rental in self._repository.get_all():
if client is not None and rental.client != client:
continue
if car is not None and rental.car != car:
continue
result.append(rental)
return result
def delete_rental(self, rental_id):
rental = self._repository.delete(rental_id)
return rental
@@ -0,0 +1,103 @@
"""
Ways to implement undo/redo
1. remember the program's state before each operation
(deep copy list/dict/repository)
Memento design pattern -- https://refactoring.guru/design-patterns/memento
2. carry out the opposite of each operation (undo) or redo the operation
itself (redo)
Command design pattern -- https://refactoring.guru/design-patterns/command
3. state-diffing
What are the differences in the program before/after the operation?
"""
class call():
def __init__(self, func_name, *func_params):
self._func_name = func_name
self._func_params = func_params
def call(self):
return self._func_name(*self._func_params)
class operation():
def __init__(self, undo: call, redo: call):
self._undo = undo
self._redo = redo
def undo(self):
self._undo.call()
def redo(self):
self._redo.call()
class cascaded_operation():
def __init__(self, *operations):
self._operations = operations
def undo(self):
for oper in self._operations:
oper.undo()
def redo(self):
for oper in self._operations:
oper.redo()
class UndoRedoError(Exception):
pass
class UndoService:
def __init__(self):
self._operations = []
self._index = 0
# flag == true means operation is not from undo_service
# flag == false means don't record for undo/redo
self._undo_flag = True
def record_for_undo(self, op: operation):
# this is a callback from undo_service so it should not be recorded
if self._undo_flag is False:
return
# NOTE this isn't actually complete
self._operations.append(op)
# update the undo/redo index to the latest value
self._index = len(self._operations)
def undo(self):
if self._index == 0:
raise UndoRedoError("No more undos")
self._undo_flag = False
self._operations[self._index - 1].undo()
self._undo_flag = True
self._index -= 1
def redo(self):
if self._index >= len(self._operations):
raise UndoRedoError("No more redos")
self._undo_flag = False
self._operations[self._index].redo()
self._undo_flag = True
self._index += 1
if __name__ == "__main__":
def a(x, y, z, t):
return x + y + z + t
def b(x):
return x ** 2
call_b = call(b, 11)
call_a = call(a, 1, 2, 3, 4)
# ... other things ...
print(call_a.call())
print(call_b.call())
@@ -0,0 +1,83 @@
"""
Created on Nov 17, 2018
@author: Arthur
"""
from seminar.group_911.seminar_12.domain.car import CarValidator
from seminar.group_911.seminar_12.domain.client import ClientValidator
from seminar.group_911.seminar_12.domain.rental import RentalValidator
from seminar.group_911.seminar_12.repository.repository import Repository
from seminar.group_911.seminar_12.service.car_service import CarService
from seminar.group_911.seminar_12.service.client_service import ClientService
from seminar.group_911.seminar_12.service.rental_service import RentalService
from seminar.group_911.seminar_12.service.undo_service import UndoService
from seminar.group_911.seminar_12.util import print_repos_with_message
def undo_example_hard():
undo_service = UndoService()
client_repo = Repository()
car_repo = Repository()
'''
Start rental Service
'''
rent_repo = Repository()
rent_validator = RentalValidator()
rent_service = RentalService(undo_service, rent_validator, rent_repo, car_repo, client_repo)
'''
Start client Service
'''
client_validator = ClientValidator()
client_service = ClientService(undo_service, rent_service, client_validator, client_repo)
'''
Start car Service
'''
car_validator = CarValidator()
car_service = CarService(undo_service, rent_service, car_validator, car_repo)
'''
We add 2 clients
'''
sophia = client_service.create(103, "2990511035588", "Sophia")
carol = client_service.create(104, "2670511035588", "Carol")
'''
We add 2 cars
'''
hyundai_tucson = car_service.create(201, "CJ 02 TWD", "Hyundai", "Tucson")
toyota_corolla = car_service.create(202, "CJ 02 FWD", "Toyota", "Corolla")
print_repos_with_message("Added 2 clients and 2 cars", client_repo, car_repo, None)
'''
We delete 1 client and 1 car
'''
client_service.delete(103)
car_service.delete(202)
print_repos_with_message("Deleted Sophia and the Corolla", client_repo, car_repo, None)
'''
We undo twice
'''
undo_service.undo()
print_repos_with_message("1 undo, the Corolla is back", client_repo, car_repo, None)
undo_service.undo()
print_repos_with_message("1 undo, Sophia is back", client_repo, car_repo, None)
'''
Redo twice
'''
undo_service.redo()
undo_service.redo()
print_repos_with_message("2 redos, Sophia and the Corolla are again deleted", client_repo, car_repo, None)
'''
Last redo
'''
# undo_service.redo()
print_repos_with_message("1 redo - but there are no more redos", client_repo, car_repo, None)
undo_example_hard()
@@ -0,0 +1,130 @@
from datetime import date
from seminar.group_911.seminar_12.domain.car import CarValidator
from seminar.group_911.seminar_12.domain.client import ClientValidator
from seminar.group_911.seminar_12.domain.rental import RentalValidator
from seminar.group_911.seminar_12.repository.repository import Repository
from seminar.group_911.seminar_12.service.car_service import CarService
from seminar.group_911.seminar_12.service.client_service import ClientService
from seminar.group_911.seminar_12.service.rental_service import RentalService
from seminar.group_911.seminar_12.service.undo_service import UndoService
from seminar.group_911.seminar_12.util import print_repos_with_message
def undo_example_hardest():
"""
An example for doing multiple undo operations.
This is a bit more difficult due to the fact that there are now several controllers,
and each of them can perform operations that require undo support.
Follow the code below and figure out how it works!
"""
undo_service = UndoService()
client_repo = Repository()
car_repo = Repository()
'''
Start rental Service
'''
rent_repo = Repository()
rent_validator = RentalValidator()
rent_service = RentalService(undo_service, rent_validator, rent_repo, car_repo, client_repo)
'''
Start client Service
'''
client_validator = ClientValidator()
client_service = ClientService(undo_service, rent_service, client_validator, client_repo)
'''
Start car Service
'''
car_validator = CarValidator()
car_service = CarService(undo_service, rent_service, car_validator, car_repo)
'''
We add 1 client, 1 car and 2 rentals
'''
sophia = client_service.create(103, "2990511035588", "Sophia")
hyundai_tucson = car_service.create(201, "CJ 02 TWD", "Hyundai", "Tucson")
rent_service.create_rental(301, sophia, hyundai_tucson, date(2016, 11, 1), date(2016, 11, 30))
rent_service.create_rental(302, sophia, hyundai_tucson, date(2016, 12, 1), date(2016, 12, 31))
print_repos_with_message("We added Sophia, the Hyundai and its 2 rentals", client_repo, car_repo, rent_repo)
car_service.delete(201)
print_repos_with_message("Delete the Hyundai (also deletes its rentals)", client_repo, car_repo, rent_repo)
'''
Now undo the performed operations, one by one
'''
undo_service.undo()
print_repos_with_message("1 undo, the Hyundai and its rentals are back", client_repo, car_repo, rent_repo)
undo_service.undo()
print_repos_with_message("1 undo deletes the second rental", client_repo, car_repo, rent_repo)
undo_service.undo()
print_repos_with_message("1 undo deletes the first rental", client_repo, car_repo, rent_repo)
undo_service.undo()
print_repos_with_message("1 undo deletes the Hyundai", client_repo, car_repo, rent_repo)
'''
After 5 undos, all repos should be empty, as we did 5 operations in total
'''
undo_service.undo()
print_repos_with_message("1 undo deletes Sophia (no more undos)", client_repo, car_repo, rent_repo)
'''
Redos start here
'''
undo_service.redo()
print_repos_with_message("1 redo and Sophia is added", client_repo, car_repo, rent_repo)
undo_service.redo()
print_repos_with_message("1 redo adds the Hyundai", client_repo, car_repo, rent_repo)
undo_service.redo()
print_repos_with_message("1 redo adds the first rental", client_repo, car_repo, rent_repo)
undo_service.redo()
print_repos_with_message("1 redo adds the second rental", client_repo, car_repo, rent_repo)
undo_service.redo()
print_repos_with_message("1 redo deletes the Hyundai and its rentals (again)", client_repo, car_repo, rent_repo)
'''
Let's do a few undos again...
'''
undo_service.undo()
undo_service.undo()
undo_service.undo()
print_repos_with_message("3 undos later, we have Sophia and the Hyundai", client_repo, car_repo, rent_repo)
'''
Now we try something new - let's add another car!
NB!
A new operation must invalidate the history for redo() operations
'''
car_service.create(202, "CJ 02 SSE", "Dacia", "Sandero")
print("\n Do we have a redo? -", undo_service.redo(), "\n")
'''
Now we should have 2 cars
'''
print_repos_with_message("After adding the Dacia, there is no redo ", client_repo, car_repo, rent_repo)
'''
However, undos is still available !
'''
undo_service.undo()
print_repos_with_message("1 undo deletes the Dacia", client_repo, car_repo, rent_repo)
undo_service.undo()
print_repos_with_message("1 undo deletes the Hyundai", client_repo, car_repo, rent_repo)
undo_example_hardest()
@@ -0,0 +1,71 @@
"""
Created on Nov 17, 2018
@author: Arthur
"""
from seminar.group_911.seminar_12.domain.car import CarValidator
from seminar.group_911.seminar_12.domain.client import ClientValidator
from seminar.group_911.seminar_12.domain.rental import RentalValidator
from seminar.group_911.seminar_12.repository.repository import Repository
from seminar.group_911.seminar_12.service.car_service import CarService
from seminar.group_911.seminar_12.service.client_service import ClientService
from seminar.group_911.seminar_12.service.rental_service import RentalService
from seminar.group_911.seminar_12.service.undo_service import UndoService
from seminar.group_911.seminar_12.util import print_repos_with_message
def undo_example_medium():
undo_service = UndoService()
client_repo = Repository()
car_repo = Repository()
'''
Start rental Controller
'''
rent_repo = Repository()
rent_validator = RentalValidator()
rent_service = RentalService(undo_service, rent_validator, rent_repo, car_repo, client_repo)
'''
Start client Controller
'''
client_validator = ClientValidator()
client_service = ClientService(undo_service, rent_service, client_validator, client_repo)
'''
Start car Controller
'''
car_validator = CarValidator()
car_service = CarService(undo_service, rent_service, car_validator, car_repo)
'''
We add 3 clients
'''
sophia = client_service.create(103, "2990511035588", "Sophia")
carol = client_service.create(104, "2670511035588", "Carol")
bob = client_service.create(105, "2590411035588", "Bob")
print_repos_with_message("We added 3 clients", client_repo, None, None)
'''
We delete 2 of the clients
'''
client_service.delete(103)
client_service.delete(105)
print_repos_with_message("Deleted Sophia and Bob", client_repo, None, None)
'''
We undo twice
'''
undo_service.undo()
print_repos_with_message("1 undo, so Bob is back", client_repo, None, None)
undo_service.undo()
print_repos_with_message("Another undo, so Sophia is back too", client_repo, None, None)
'''
We redo once
'''
undo_service.redo()
print_repos_with_message("1 redo, so Sophia is again deleted", client_repo, None, None)
undo_example_medium()
@@ -0,0 +1,8 @@
def print_repos_with_message(msg, client_repo, car_repo, rent_repo):
print("-" * 15 + msg + "-" * 15)
if client_repo is not None:
print("Clients:\n" + str(client_repo))
if car_repo is not None:
print("Cars:\n" + str(car_repo))
if rent_repo is not None:
print("Rentals:\n" + str(rent_repo))
@@ -0,0 +1,63 @@
import random
from texttable import Texttable
class MineField():
def __init__(self, rows, cols, mines: int):
self._rows = rows
self._cols = cols
self._mines = mines
# we always want to create the mine field when initializing
self._data = []
"""
Meaning of values in self._data:
0 - 8 => number of adjacent mines (square is not mined)
9 => square is mined
+10 => square is revealed
+100 => square is flaged
"""
for row in range(self._rows):
self._data.append([0] * self._cols)
self._lay_mines()
def _lay_mines(self):
# 9 means the square is mined
my_mines = [9] * self._mines
my_mines += [0] * (self._rows * self._cols - self._mines)
random.shuffle(my_mines)
for row in range(self._rows):
for col in range(self._cols):
if my_mines[row * self._cols + col] == 9:
# lay the mine
self._data[row][col] = my_mines[row * self._cols + col]
# update mine adjacency
for i in [-1, 0, 1]:
for j in [-1, 0, 1]:
if i == 0 and j == 0:
continue
if (0 > row + i) or (row + i >= self._rows):
continue
if (0 > col + j) or (col + j >= self._cols):
continue
if self._data[row + i][col + j] != 9:
self._data[row + i][col + j] += 1
def __str__(self):
t = Texttable()
header = ['X']
for ascii in range(self._cols):
header.append(chr(65 + ascii))
t.header(header)
for r in range(self._rows):
t.add_row([r + 1] + self._data[r])
return t.draw()
f = MineField(8, 10, 50)
print(f)
@@ -0,0 +1,22 @@
RO650,Cluj,05:45,Bucuresti,06:40
0B3302,Cluj,07:15,Bucuresti,08:15
SLD322,Timisoara,09:05,Cluj,10:00
RO643,Bucuresti,10:15,Cluj,11:10
RO734,Timisoara,10:45,Iasi,12:25
KL2710,Timisoara,14:25,Bucuresti,15:40
RO745,Cluj,12:50,Iasi,14:05
RO746,Iasi,14:30,Cluj,15:50
RO647,Bucuresti,18:05,Cluj,19:00
KL2706,Bucuresti,18:10,Timisoara,19:05
RO733,Iasi,08:30,Timisoara,10:20
SLD363,Cluj,19:35,Timisoara,20:30
RO649,Bucuresti,21:55,Cluj,22:50
0B3101,Bucuresti,22:55,Cluj,23:55
RP621,Bucuresti,07:30,Oradea,08:55
RO622,Oradea,09:20,Bucuresti,10:40
RO627,Bucuresti,17:55,Oradea,19:20
RO628,Oradea,19:45,Bucuresti,21:05
XL897,TgMures,08:55,Oradea,09:30
XL898,Oradea,20:45,TgMures,21:25
LH012,Iasi,14:35,Oradea,15:35
LH013,Oradea,17:00,Iasi,18:10
@@ -0,0 +1,77 @@
Write a console-based application to help air traffic control (ATC) monitor all domestic flights taking place during one day (00:00 23:59). The application must include the following features:
1. Flight information is kept in a text file, using the format in the example below. When the program starts, flight information is read from the file [1p]. Each modification is persisted to the text file [1p].
2. Add a new flight. Each flight has an identifier, a departure city and time, and an arrival city and time [1p]. Flight identifiers are unique; flight times are between 15 and 90 minutes; an airport can handle a single operation (departure or arrival) during each minute [1p].
3. Delete a flight. The user provides the flight identifier. If it does not exist, an error message is displayed [1p].
4. List the airports, in decreasing order of activity (number of departures and arrivals during the day) [1p].
5. List the time intervals during which no flights are taking place, in decreasing order of length. [1.5p].
6. The tracking radar suffers a failure. The backup radar can be used, but it can only track a single flight at a time. Determine the maximum number of flights that can proceed as planned. List them using the format below [1.5p]:
05:45 | 06:40 | RO650 | Cluj - Bucuresti
Non-functional requirements:
• Implement an object-oriented, layered architecture solution using the Python language.
• Provide specification and unit tests for Repository/Controller functions related with the second functionality. In case specification or tests are missing, the functionality is graded at 50%.
Observations!
• The day starts at 00:00 and ends at 23:59.
• Default 1p.
## TODO
-= Iteration 1 =-
1. Write the Flight class
-> what are the attributes?
-> ! use properties for each of them
2. Write a FlightTextRepo class
-> read the flights on program start (__init__)
# -> save the flights text file (def save_file(...))
# -> write method add_flight
3. Write the FlightServices class
-> get_all_flights() # return all flights from repo
4. Write the UI class
-> write the __init__
-> display the main menu:
a. Display all flights
x. Exit
-> displays all flights at menu (a)
-= Iteration 2 =-
0. Create /tests directory and add PyUnit tests
1. Write FlightValidation class
-> is_valid (return True iff flight is valid)
2. class FlightTextRepo
-> write method add_flight
-> save the flights text file (def save_file(...))
3. Write the FlightServices class
-> get_all_flights() # return all flights from repo
-> add_flight(...)
4. Write the UI class
-> display the main menu:
a. Display all flights
b. Add flight
x. Exit
-> displays all flights at menu (a)
-> read flight data -> save it via services/repo
@@ -0,0 +1,78 @@
from traceback import print_exc
import random
import math
"""
Let's create a menu-driven application. This is the menu:
1. Generate rational numbers
2. Sort the list of numbers
0. Exit
"""
"""
Functions for Q numbers
"""
def create_q(num, denom=1):
# TODO What about 1/0 !!
gcd = math.gcd(num, denom)
return [num // gcd, denom // gcd]
def generate_rationals_v2():
"""Function used in order to generate rational numbers"""
numbers_list = []
nr_numbers = input("Please input how many numbers you'd like to store.")
nr_numbers = int(nr_numbers)
for iterator in range(0, nr_numbers):
num = random.randint(-10, 10)
denom = random.randint(1, 20)
numbers_list.append(create_q(num, denom))
for q in numbers_list:
print(tranform_to_string(q))
return numbers_list
def tranform_to_string(rational_nr_list):
"""
Function used in order to represent the rational number as a string
:param rational_nr_list: a list of num and denom
:return: a string
"""
num, denom = rational_nr_list
result = num / denom
int_result = int(result)
if result == int_result:
string_to_be_returned = str(result)
else:
string_to_be_returned = f"{num} / {denom}"
return string_to_be_returned
def start():
while True:
print("1. Generate rational numbers")
print("2. Sort the list of numbers")
print("0. Exit")
opt = input(">") # by default reads str
# print(type(opt))
if opt == "1":
generate_rationals_v2()
elif opt == "2":
pass
elif opt == "0":
return # or break
else:
print("Bad command or file name :)")
start()
@@ -0,0 +1,276 @@
"""
1. Determine the time complexity of the following algorithms as a function of n.
source: https://complex-systems-ai.com/en/algorithmic/corrected-exercises-time-complexity/
"""
"""
n -> size of the algorithm's input
T(n) -> number of operations made by program
T(n) = 1 -> constant time (a single operation)
O notation -> O(1) is also O(n) is also O(n^2) ...
"""
# T(n) = 10 * n * 1 => O(n)
def f_1(n: int):
# does not depend on n
for i in range(10):
# depends on n linearly -> O(n)
for j in range(n):
# assume print is constant time -> T(1)
print("Hello World")
# T(n) = 10 * n * 1 => O(n)
def f_2(n: int):
# does not actually depend on n
for i in range(n, n + 10):
# depends on n linearly -> O(n)
for j in range(n):
# assume print is constant time -> T(1)
print("Hello World")
# T(n) = (n-1) * n (simplified)
# T(n) = (n-1) + (n-2) + ... + 1 => O(n^2)
def f_3(n: int):
# depends on n linearly
for i in range(1, n):
# depends on n linearly
for j in range(i, n):
print("Hello World")
def f_4(n: int):
for i in range(n):
for j in range(2 * i + 1):
print("Hello World")
# time complexity O(n^4)
# 1 + 2 + ... + (n^2-2) = ((n^2 - 2)*(n^2-1))/2
# (n^2 - 1) * (n^2 - 1)
# complexity is O(n^4)
# if for n = 10, runtime is 1ms, what's the runtime for n = 20 ?
def f_5(n: int):
# i ranges between 0 and n^2 => first loop is O(n^2)
for i in range(n ** 2):
# j depends on i which loops to n^2 => second loop is O(n^2)
# for j in range(i): # probably faster, as i starts with lower values
for j in range(1, n ** 2):
print("Hello World")
# O(n * log(n))
def f_6(n: int):
# outer loop is O(n)
for i in range(n):
t = 1
# how many times can we multiply by 2 until we reach n?
# inner loop is O(log(n))
while t < n:
print("Hello World")
t *= 2
"""
1. Time complexity in both "n" and "m"
"""
# complexity is O(n + m)
# e.g., merging an array with n elements with an array having m elements
def f_7(n, m: int):
# O(n)
for i in range(0, n):
print("Hello World")
# O(m)
for j in range(0, m):
print("Hello World")
# time complexity is O(n)
def f_8(n, m: int):
for i in range(0, n):
print("Hello World")
for j in range(0, n):
print("Hello World")
# O(n^2)
def f_9(n: int):
for i in range(n):
for j in range(n):
print("Hello World")
for k in range(n):
print("Hello World")
# O(n^2)
def f_10(n: int):
for i in range(n):
# depends on n => O(n) for inner loop
for j in range(n, i, -1):
print("Hello World")
"""
Analyze the time and space complexity
"""
# n = len(data)
# time complexity is O(n * log_3(n))
# space complexity is O(1)
def f_11(data: list):
data_sum = 0
for el in data:
j = len(data)
while j > 1:
data_sum += el * j
j = j // 3
return data_sum
"""
1. Time comlexity
T(n) = 1, n <= 1
T(n) = 2 * T(n /2) + 1
T(n/2) = 2 * T(n/4) + 1
T(n/4) = 2 * T(n/8) + 1
T(n) = 2 * T(n /2) + 1 = 2 * [2 * T(n/4) + 1] + 1 = 4 * T(n/4) + 2 + 1
= 4 * [2 * T(n/8) + 1] + 2 + 1 = 8 * T(n/8) + 4 + 2 + 1
T(n) = 8 * T(n/8) + 4 + 2 + 1
assume 2^k = n, k = log_2(n)
T(n) = n * T(1) + 2^(k-1) + ... + 2^0 = n + 2^k - 1 = n => O(n)
2. Space complexity
T(n) = 1, n <= 1
T(n) = 2 * T(n /2) + n
T(n/2) = 2 * T(n/4) + n/2
T(n/4) = 2 * T(n/8) + n/4
T(n) = 2 * T(n /2) + n = 2 * [2 * T(n/4) + n/2] + n = ...
like previously, only with n instead of 1 as final term
"""
def f_12(data: list):
if len(data) == 0:
return 0
if len(data) == 1:
return data[0]
m = len(data) // 2
return f_12(data[:m]) + f_12(data[m:])
# O(n^2 * log_10(n))
def f_13(n: int):
s = 0
for i in range(1, n ** 2): # n^2 loop
j = i
while j != 0: # log_10(i) < log_10(n^2), if n large
s = s + j - 10 * j // 10
j //= 10
return s
"""
T(n) = 1, n <= 1
T(n) = 4 * T(n/2) + 1, n > 1
T(n/2) = 4 * T(n/4) + 1
T(n/4) = 4 * T(n/8) + 1
T(n) = 4 * [4 * T(n/4) + 1] + 1 = 16 * T(n/4) + 4 + 1 =
= 16 * [4 * T(n/8) + 1] + 4 + 1 =
2^k = n
T(n) = (2^k)^2 * T(1) + 4^(k-1) + ... + 4^0 = n^2 + => O(n^2)
"""
def f_14(n, i: int):
if n > 1:
i *= 2
m = n // 2
f_14(m, i - 2)
f_14(m, i - 1)
f_14(m, i + 2)
f_14(m, i + 1)
else:
print(i)
"""
Analyze the algorithm's time complexity. Write an equivalent algorithm with
a strictly better time complexity
1. What's the time complexity?
2. What does it do?
3. Find a better O(n) to do it in...
"""
def f_15(data: list):
i = 0
j = 0
m = 0
c = 0
while i < len(data):
if data[i] == data[j]:
c += 1
j += 1
if j >= len(data):
if c > m:
m = c
c = 0
i += 1
j = i
return m
# TODO Time complexity
def f_15_better(data: list):
freq_dict = {}
max_freq = 1
for el in list:
if el not in freq_dict:
freq_dict[el] = 1
else:
freq_dict[el] += 1
max_freq = max(freq_dict[el], max_freq)
return max_freq
"""
What is the time complexity when the following algorithm is implemented via linear exponentiation. How can this be
optimized and how will that improve the complexity?
"""
def f_16(x, n: int):
"""
The algorithms returns x ** n
:param x:
:param n:
:return:
"""
# TODO Implement me
pass
"""
Implement and discuss the complexity of merge sort
"""
def merge_sort(data: list):
# TODO Implement me
pass
@@ -0,0 +1,201 @@
"""
Divide & Conquer + Combine (the results of the small, already solved subproblems
"""
import random
"""
1. Find the smallest number in a list (chip & conquer, divide in halves, recursive vs non-recursive). Return None for
an empty list
a. Chip & conquer, recursive
b. Divide in halves, non-recursive
c. Divide in halves, recursive
"""
# a. Chip & conquer, recursive
def array_min_impl(array: list, start_index: int):
if start_index == len(array) - 1:
return array[start_index]
return min(array_min_impl(array, start_index + 1), array[start_index])
def array_min(array: list):
if len(array) == 0:
return None
return array_min_impl(array, 0)
# b. Divide in halves, non-recursive
def divide_in_halves_iter(my_list: list):
"""Returns the smallest number from a list"""
if not len(my_list):
return None
min_found = my_list[0]
stack = [(0, len(my_list) - 1)]
# As long as we have elements in the stack
while len(stack):
left, right = stack.pop()
if left == right:
min_found = min(my_list[left], min_found)
# Then we continue with the next item from the stack
continue
# If we got to this point, it means left != right
mid = (left + right) // 2
# We look in the first half
stack.append((left, mid))
# We look in the second half
stack.append((mid + 1, right))
return min_found
# c. Divide in halves, recursive
def calc_array_min_impl(array: list, left, right: int):
if right == left:
return array[left]
mid = (left + right) // 2
return min(calc_array_min_impl(array, left, mid), calc_array_min_impl(array, mid + 1, right))
def calc_array_min(array: list):
if len(array) == 0:
return None
return calc_array_min_impl(array, 0, len(array) - 1)
# def test_divide():
# for count in range(1000):
# length = random.randint(1, 100)
# array = []
# for i in range(length):
# array.append(random.randint(-100, 100))
# assert calc_array_min(array) == min(array), (calc_array_min(array), array)
# assert array_min(array) == min(array), (array_min(array), array)
# assert divide_in_halves_iter(array) == min(array), (divide_in_halves_iter(array), array)
# # special case - empty array
# assert calc_array_min([]) is None
# assert array_min([]) is None
# assert divide_in_halves_iter([]) is None
#
#
# test_divide()
"""
2. Exponential search
a. Generate a pseudo-random array of increasing elements
b. Implement exponential search
c. Implement binary search
d. Driver & test functions
"""
def generate_random_increasing_array():
# n=length of array
n = random.randint(0, 100)
array = [random.randint(0, 100)]
for i_ul in range(1, n):
array.append(array[i_ul - 1] + random.randint(0, 2))
return array
def exponential_search(array: list, key: int):
poz = 1
while poz <= len(array) - 1 and array[poz] < key:
poz *= 2
# TODO Replace the linear search with binary search
for i in range(poz // 2, min(poz + 1, len(array))):
if array[i] == key:
return i
return -1
# def test_exponential_search():
# for i in range(1000):
# array = generate_random_increasing_array()
# pos = random.randint(0, len(array) - 1)
#
# # array[...] as array the array is not strictly increasing
# assert array[exponential_search(array, array[pos])] == array[pos], (pos, array, array[pos])
# test_exponential_search()
"""
3. Calculate the r-th root of a given number x with a given precision p
"""
"""
4. Calculate the maximum subarray sum (subarray = elements having continuous indices)
a. Naive implementation
b. Divide & conquer implementation
e.g.
for data = [-2, -5, 6, -2, -3, 1, 5, -6], maximum subarray sum is 7.
"""
"""
Backtracking
"""
"""
5. Recursive implementation for permutations
"""
def consistent(x):
"""
Determines whether the current partial array can lead to a solution
"""
return len(set(x)) == len(x)
def solution(x, n):
"""
Determines whether we have a solution
"""
return len(x) == n
def solution_found(x):
"""
What to do when a solution is found
"""
print("Solution: ", x)
def bkt_rec(x, n):
"""
Backtracking algorithm for permutations problem, recursive implementation
"""
x.append(0)
for i in range(0, n):
x[len(x) - 1] = i
if consistent(x):
if solution(x, n):
solution_found(x)
else:
bkt_rec(x[:], n)
bkt_rec([], 4)
"""
6. Change the code for generating the permutation above to work for the n-Queen problem
"""
"""
A Latin square is an n × n square filled with n different symbols, each occurring exactly once in each row and exactly
once in each column
7. Generate all the N x N Latin squares for a given number N.
8. Generate all reduced N x N Latin squares for a given number N. In a reduced Latin square, the elements of the first
row and column are sorted.
"""
@@ -0,0 +1,147 @@
"""
Dynamic programming
"""
"""
1. Calculate the maximum subarray sum (subarray = elements having continuous
indices)
e.g.
for data = [-2, -5, 6, -2, -3, 1, 5, -6], maximum subarray sum is 7.
"""
import sys
# sys.maxsize
# Ineficient solution-Dobocan Raul
# An O(n^2) implementation
def max_subarray_sum(array: list):
if len(array) == 0:
return None
maxx = array[0]
for i in range(0, len(array)):
sum = 0
for j in range(i, len(array)):
sum += array[j]
if sum >= maxx:
maxx = sum
return maxx
# l = [-2, -5, 6, -2, -3, 1, 5, -2]
# print(max_subarray_sum(l))
# Solution with dinamic programming - Enache Vlad
def max_subarray_sum_DP(arr: list):
if len(arr) == 0:
return None
max_ending_here = arr[0] # initialising the sum with the first element
max_global = arr[0]
for i in range(1, len(arr)):
if arr[i] + max_ending_here < arr[i]: # the case when we start the sum again
max_ending_here = arr[i]
else: # the case when we continue the sum
max_ending_here += arr[i]
max_global = max(max_global, max_ending_here)
return max_global
# print([-2, -5, 6, -2, -3, 1, 5, -2])
"""
Maximum subarray divide and conquer - Cirlea Mihai Alexandru
e.g.
for data = [-2, -5, 6, -2, -3, 1, 5, -6], maximum subarray sum is 7.
"""
# TODO Check this for bugs
def max_subarray_div_conq(arr: list):
"""Uses div and conquer iterative technique to get the max subarray."""
# Checking for empty arrays
if not arr:
return
stack = [(0, len(arr) - 1)]
max_sum = arr[0]
while stack:
left, right = stack.pop()
if left == right:
max_sum = max(arr[left], max_sum)
# Continue with the next item from the stack
continue
middle = (left + right) // 2
max_left, max_right = arr[left], arr[middle + 1]
sum_left, sum_right = 0, 0
# Creating the maximum sum in the left part
for iterator in range(left, middle + 1):
if arr[iterator] > sum_left:
sum_left = arr[iterator]
else:
sum_left += arr[iterator]
max_left = sum_left
# Creating the maximum sum in the right part
for iterator in range(middle + 1, right + 1):
sum_right += arr[iterator]
if sum_right > max_right:
max_right = sum_right
max_sum = max(max_right + max_left, max_sum)
stack.append((left, middle))
stack.append((middle + 1, right))
return max_sum
"""
2. Count in how many ways we can provide change to a given sum of money (N), when provided infinite
supply of given coin denominations.
e.g. Let's say N = 10, and we have coins of values (1, 5, 10); we can give change in 4 ways (10, 5 + 5, 5 + 1 + ...
and 1 + ... + 1)
"""
"""
3. 0-1 Knapsack problem. Given the weights and values of N items, put them
in a knapsack having capacity W so that you
maximize the value of the stored items. Items cannot be broken up
(0-1 property)
"""
W = 11
weights = [1, 2, 3, 4, 7]
values = [1, 5, 8, 3, 2]
def knapsack_01(W: int, weights, values: list, current: int):
if current == len(weights):
return 0
value_include = 0
if W - weights[current] >= 0:
value_include = values[current] + knapsack_01(W - weights[current], weights, values, current + 1)
value_exclude = knapsack_01(W, weights, values, current + 1)
return max(value_include, value_exclude)
print(knapsack_01(W, weights, values, 0))
"""
4. Gold mine problem (a.k.a checkerboard problem)
https://www.geeksforgeeks.org/gold-mine-problem
"""
@@ -0,0 +1,223 @@
"""
Write an application that manages a list of rectangles.
Each rectangle is represented using its two opposite corners (x1, y1) and (x2, y2)
The application will have a menu-driven user interface and will provide the following features:
1. Add a rectangle
- adds the given rectangle to the list.
- error if the given rectangle already exists, x1 <= x2 or y1 <= y2
2. Delete a rectangle
- deletes the rectangle with the given corners
- error if non-existing rectangle is given
3. Show all rectangles
- shows all rectangles in descending order of their area
4. Show rectangles that intersect a given one
- select a rectangle from the list of existing rectangle
- print those which intersect it by descending order of area
5. exit
- exit the program
Observations:
- Add 10 random rectangles at program startup
- Write specifications for non-UI functions
- Each function does one thing only, and communicates via parameters and return value
- The program reports errors to the user. It must also report errors from non-UI functions!
- Make sure you understand the rectangle's representation
- Try to reuse functions across functionalities (Less code to write and test)
- Don't use global variables!
"""
import random
#
# Write the implementation for Seminar 06 in this file
#
#
# Write below this comment
# Functions to deal with rectangles -- list representation
# -> There should be no print or input statements in this section
# -> Each function should do one thing only
# -> Functions communicate using input parameters and their return values
#
# def create_rect(x1, y1, x2, y2: int):
# """
# Create a rectangle with corners (x1, y1) and (x2, y2).
# :param x1:
# :param y1:
# :param x2:
# :param y2:
# :return: The newly created rectangle
# """
# pass
# def rect_equal(rect1, rect2):
# """
# Return True iff the two rectangles are equal (have the same corners)
# :param rect1:
# :param rect2:
# :return:
# """
# pass
#
# Write below this comment
# Functions to deal with rectangles -- dict representation
# -> There should be no print or input statements in this section
# -> Each function should do one thing only
# -> Functions communicate using input parameters and their return values
#
# Dumitrana Mihnea dictionary repres.
def create_rect(x1, y1, x2, y2):
if x1 == x2 or y1 == y2:
return None
rect = {}
rect['low_left'] = (min(x1, x2), min(y1, y2))
rect['up_right'] = (max(x1, x2), max(y1, y2))
return rect
def get_x1(rect):
return rect["low_left"][0]
def get_y1(rect):
return rect["low_left"][1]
def get_x2(rect):
return rect["up_right"][0]
def get_y2(rect):
return rect["up_right"][1]
def rect_equal(rect1, rect2: dict):
return get_x1(rect1) == get_x1(rect2) and get_x2(rect1) == get_x2(rect2) and get_y1(rect1) == get_y1(
rect2) and get_y2(rect1) == get_y2(rect2)
def to_str(rect):
return "{} {} | {} {}".format(get_x1(rect), get_y1(rect), get_x2(rect), get_y2(rect))
#
# Write below this comment
# Functions that deal with the required functionalities properties
# -> There should be no print or input statements in this section
# -> Each function should do one thing only
# -> Functions communicate using input parameters and their return values
#
def gen_rectangles(count: int):
result = []
while count > 0:
x1 = random.randint(-20, 20)
y1 = random.randint(-20, 20)
x2 = x1 + random.randint(1, 10)
y2 = y1 + random.randint(1, 10)
new_rect = create_rect(x1, y1, x2, y2)
rect_ok_flag = True
for rect in result:
if rect_equal(new_rect, rect):
rect_ok_flag = False
break
if rect_ok_flag:
result.append(new_rect)
count -= 1
return result
# rects = gen_rectangles(5)
# print(rects)
def add_rectangle(rectangles: list, new_rect):
"""
:param rectangles: list
:param x1,x2,y1,y2: int
:return:
None if rectangle already exists or is invalid
updated list if rectangle is ok
"""
# newRectangle = [x1, y1, x2, y2]
# newRectangle = create_rect()
# if x1 > x2 or y1 > y2:
# return None
for rectangle in rectangles:
if rect_equal(rectangle, new_rect):
return None
rectangles.append(new_rect)
return rectangles
#
# Write below this comment
# UI section
# Write all functions that have input or print statements here
# Ideally, this section should not contain any calculations relevant to program functionalities
#
def show_all_rect(rect_list: list):
print("Rectangle list:")
for rect in rect_list:
print(to_str(rect))
# Chisleac Remus
def read_rect_ui():
x1 = int(input("x1="))
y1 = int(input("y1="))
x2 = int(input("x2="))
y2 = int(input("y2="))
return create_rect(x1, y1, x2, y2)
def add_rectangle_ui(rectangles: list):
new_rect = read_rect_ui()
if new_rect is None:
print("Invalid rectangle. Cannot be added")
return
if add_rectangle(rectangles, new_rect) is None:
print("Overlapping rectangles. Rectangle was not added.")
def start_menu():
rectangles = gen_rectangles(1)
while True:
show_all_rect(rectangles)
print(rectangles)
print("1. Add a rectangle:\n",
"2. Delete a rectangle:\n",
"3. Show all rectangles:\n",
"4. Show rectangles that intersect a given one:\n",
"5. exit\n")
opt = input('>')
if opt == "1":
add_rectangle_ui(rectangles)
elif opt == "2":
pass
elif opt == "3":
pass
elif opt == "4":
pass
elif opt == "5":
return
else:
print("Choose a valid option")
# Dumitrescu David
if __name__ == "__main__":
start_menu()
@@ -0,0 +1,12 @@
"""
1. Modular programming
2. Test-driven development
3. Exceptions
4. command-driven UI
Tic Tac Toe - human vs. computer
play 5 (5th square on the board)
play top middle
play 1,1
play B2
"""
@@ -0,0 +1,83 @@
"""
Board module
"""
def create_board():
"""
Create the game board
:return: the board
"""
board = []
for i in [0, 1, 2]:
board.append([' ', ' ', ' '])
return board
def get_symbol(game_board, row, col):
# TODO Missing exceptions
symbol = game_board[row][col]
return symbol if symbol is not ' ' else None
def move_on_board(game_board, symbol, row, col):
"""
Play a move on the board
:param game_board: The game board
:param symbol: one of 'X' or 'O'
:param row: one of 0,1,2
:param col: one of 0,1,2
:return: None
Raise ValueError if (row,col) outside board, symbol not one of (X,O) and
if square already taken
"""
if row not in [0, 1, 2] or col not in [0, 1, 2]:
raise ValueError("Move outside the board")
if symbol not in ['X', 'O']:
raise ValueError("Invalid symbol")
if get_symbol(game_board, row, col) is not None:
raise ValueError("Square already taken")
game_board[row][col] = symbol
#
# def test_move_on_board():
# b = create_board()
# # Test empty board
# for row in [0, 1, 2]:
# for col in [0, 1, 2]:
# assert get_symbol(b, row, col) is None
#
# # check for placing moves on the board
# move_on_board(b, 'X', 1, 1)
# assert get_symbol(b, 1, 1) == 'X'
# move_on_board(b, 'X', 0, 0)
# assert get_symbol(b, 0, 0) == 'X'
# move_on_board(b, 'O', 2, 2)
# assert get_symbol(b, 2, 2) == 'O'
#
# # check error handling
# try:
# move_on_board(b, 'X', 1, 1)
# assert False
# except ValueError:
# assert True
def str_board(game_board):
result = ""
gb = game_board
for row in [0, 1]:
result += gb[row][0] + " | " + gb[row][1] + " | " + gb[row][2] + "\n"
result += '--+---+--\n'
result += gb[2][0] + " | " + gb[2][1] + " | " + gb[2][2] + "\n"
return result
if __name__ == "__main__":
b = create_board()
move_on_board(b, 'X', 1, 1)
move_on_board(b, 'O', 0, 0)
move_on_board(b, 'X', 2, 2)
print(str_board(b))
@@ -0,0 +1,20 @@
"""
Human and computer moves
"""
import board
from random import choice
def human_move(game_board, row, col):
board.move_on_board(game_board, 'X', row, col)
def computer_move(game_board):
positions = []
for row in [0, 1, 2]:
for col in [0, 1, 2]:
if board.get_symbol(game_board, row, col) is None:
positions.append((row, col))
pos = choice(positions)
board.move_on_board(game_board, 'O', pos[0], pos[1])
return pos
@@ -0,0 +1,73 @@
"""
UI module for game
user commands:
play 1,1 # plays in the center of the board
takeback # undo the user's last move
exit
"""
import board
import game
def help_user():
print(
"""
play 1, 1 # plays in the center of the board
takeback # undo the user's last move
exit
""")
def process_user_command(user_command):
"""
Return user's command and its parameters
:param user_command:
:return:
"""
user_command = user_command.strip()
tokens = user_command.split(" ", maxsplit=1)
command = tokens[0]
if len(tokens) == 1:
return command, ""
tokens = tokens[1].split(",")
for i in range(len(tokens)):
tokens[i] = tokens[i].strip()
return command, tokens
def start():
game_board = board.create_board()
help_user()
while True:
print(board.str_board(game_board))
user_command = input(">")
command, params = process_user_command(user_command)
if command == 'play':
try:
row = int(params[0])
col = int(params[1])
game.human_move(game_board, row, col)
except ValueError as ve:
print(str(ve))
else:
pos = game.computer_move(game_board)
print("Computer moved at " + str(pos))
elif command == 'takeback':
pass
elif command == 'exit':
return
else:
print("Valid commands are:")
help_user()
# print(process_user_command("play 1,1"))
# print(process_user_command(" play 1, 1 "))
start()
@@ -0,0 +1,192 @@
"""
Board module
"""
# This class is a subclass of Python's Exception class
# That allows us to raise and catch it
class BoardFullException(Exception):
pass
class GameWonException(Exception):
pass
class Board():
"""
this is Java
public Board() { ... }
Python's self is kind of like C++'s this
"""
def __init__(self):
"""
Create the game board
:return: the board
"""
# board is a local var in the __init__ method
# board = []
# self.board is an attribute of the Board class -> will be visible
# across all Board methods
"""
How do we protect self.board from being changed outside the class?
C++/JAVA/C#
private -> field/methods accessible only from within the class
public -> field/methods accessible from everywhere
protected -> field/methods accessible from within the class and derived classes
(and in the same package in Java)
Python -> private, etc keywords aren't used
<name> -> public (e.g., self.board)
_<name> -> private (by convention) (e.g., self._board)
__<name> -> private (using name mangling) (e.g., self.__board)
"""
self.__board = []
self.__free_squares = 9
for i in [0, 1, 2]:
self.__board.append([' ', ' ', ' '])
def get_symbol(self, row, col):
# TODO Missing exceptions
symbol = self.__board[row][col]
if symbol != ' ':
return symbol
return None
# return symbol if symbol =! ' ' else None
def move(self, symbol, row, col):
"""
Play a move on the board
:param game_board: The game board
:param symbol: one of 'X' or 'O'
:param row: one of 0,1,2
:param col: one of 0,1,2
:return: None
Raise ValueError if (row,col) outside board, symbol not one of (X,O) and
if square already taken
Raise BoardFullException if the board is full but it's not won
Raise GameWonException if the game was won
"""
if row not in [0, 1, 2] or col not in [0, 1, 2]:
raise ValueError("Move outside the board")
if symbol not in ['X', 'O']:
raise ValueError("Invalid symbol")
if self.get_symbol(row, col) is not None:
raise ValueError("Square already taken")
self.__board[row][col] = symbol
self.__free_squares -= 1
if self._is_won():
raise GameWonException()
if self._is_full():
raise BoardFullException()
def _is_won(self):
gb = self.__board
# checking for wins on rows
for row in [0, 1, 2]:
if gb[row][0] == ' ':
# jump to the next iteration in the innermost loop
continue
if gb[row][0] == gb[row][1] == gb[row][2]:
return True
# checking for wins on columns
for col in [0, 1, 2]:
if gb[0][col] == ' ':
# jump to the next iteration in the innermost loop
continue
if gb[0][col] == gb[1][col] == gb[2][col]:
return True
# checking on diagonals
if gb[1][1] == ' ':
return False
if gb[0][0] == gb[1][1] == gb[2][2]:
return True
if gb[2][0] == gb[1][1] == gb[0][2]:
return True
def _is_full(self):
return self.__free_squares == 0
def __str__(self):
result = ""
gb = self.__board
for row in [0, 1]:
result += gb[row][0] + " | " + gb[row][1] + " | " + gb[row][2] + "\n"
result += '--+---+--\n'
result += gb[2][0] + " | " + gb[2][1] + " | " + gb[2][2] + "\n"
return result
#
# def test_move_on_board():
# game_board = Board()
# # Test empty board
# for row in [0, 1, 2]:
# for col in [0, 1, 2]:
# assert game_board.get_symbol(row, col) is None
#
# # check for placing moves on the board
# game_board.move('X', 1, 1)
# assert game_board.get_symbol(1, 1) == 'X'
# game_board.move('X', 0, 0)
# assert game_board.get_symbol(0, 0) == 'X'
# game_board.move('O', 2, 2)
# assert game_board.get_symbol(2, 2) == 'O'
#
# # check error handling
# try:
# game_board.move('X', 1, 1)
# assert False
# except ValueError:
# assert True
if __name__ == "__main__":
# Board.__init__ is called here implicitly
# __init__ must return a reference to the new object --> handled by the
# Python runtime
b = Board()
b.move('X', 1, 1)
b.move('O', 2, 2)
# we can use this but it's not very Pythonic :)
# print(b.str_board())
# print(str(b))
# print(b)
# each Board object (e.g., b1, b2) has its own independent copy of
# self.__board
b1 = Board()
b2 = Board()
b1.move('O', 1, 1)
b2.move('X', 1, 1)
print(b1)
print(b2)
# b plays the role of self implicitly
# print(b.get_symbol(1, 1))
# in this version of the call, b plays the role of self explicitly
# print(Board.get_symbol(b, 1, 1))
# print(b.__board)
# b.__board[1][1] = 'X'
# print(b.__board)
# Python is called a dict programming language :)
# print(b.__dict__)
# print(type(b))
# print(type([]))
# print(type(str_board))
# b = create_board()
# move_on_board(b, 'X', 1, 1)
# move_on_board(b, 'O', 0, 0)
# move_on_board(b, 'X', 2, 2)
# print(str_board(b))
@@ -0,0 +1,27 @@
"""
Human and computer moves
"""
from board import Board
from random import choice
class Game:
def __init__(self):
# private field of a Game class instance
self.__board = Board()
def get_board(self):
return self.__board
def human_move(self, row, col):
self.__board.move('X', row, col)
def computer_move(self):
positions = []
for row in [0, 1, 2]:
for col in [0, 1, 2]:
if self.__board.get_symbol(row, col) is None:
positions.append((row, col))
pos = choice(positions)
self.__board.move('O', pos[0], pos[1])
return pos
@@ -0,0 +1,85 @@
"""
UI module for game
user commands:
play 1,1 # plays in the center of the board
takeback # undo the user's last move
exit
"""
from board import Board, GameWonException, BoardFullException
from game import Game
class UI:
def help_user(self):
print(
"""
play 1, 1 # plays in the center of the board
takeback # undo the user's last move
exit
""")
def _process_user_command(self, user_command):
"""
Return user's command and its parameters
:param user_command:
:return:
"""
user_command = user_command.strip()
tokens = user_command.split(" ", maxsplit=1)
command = tokens[0]
if len(tokens) == 1:
return command, ""
tokens = tokens[1].split(",")
for i in range(len(tokens)):
tokens[i] = tokens[i].strip()
return command, tokens
def start(self):
game = Game()
self.help_user()
while True:
print(game.get_board())
user_command = input(">")
command, params = self._process_user_command(user_command)
if command == 'play':
try:
row = int(params[0])
col = int(params[1])
game.human_move(row, col)
except ValueError as ve:
print(str(ve))
except BoardFullException:
print("-= Game Over. It's a draw! =-")
print(game.get_board())
return
except GameWonException:
print("-= Congratulations! =-")
print(game.get_board())
return
else:
try:
pos = game.computer_move()
print("Computer moved at " + str(pos))
except GameWonException:
print("-= Comiserations! =-")
print(game.get_board())
return
elif command == 'takeback':
pass
elif command == 'exit':
return
else:
print("Valid commands are:")
self.help_user()
if __name__ == "__main__":
ui = UI()
ui.start()
@@ -0,0 +1,76 @@
"""
Create an application for a car rental business using a console based user interface.
The application must allow keeping records of the companys list of clients, existing car pool and rental history.
The application must allow its users to manage clients, cars and rentals in the following ways:
Clients
 Add a new client. Each client is a physical person having a unique ID (driver license series), name, age.
 Update the data for any client.
 Remove a client from active clients. Note that removing a client must not remove existing car rental statistics.
 Search for clients based on ID and name [both at the same time]
 All client operations must undergo proper validation!
Cars
 Add a new car to the car pool. Each car must have a valid license plate number, a make and model taken from a
list of makes and models. In addition, each car will have a color.
 Remove a car from the car pool.
 Search for cars based on license number, make, model and color.
[make = VW, model = Polo, CJ01ABC], [make = VW, model = Polo, CJ01XYZ]
 All car operations must undergo proper validation!
Rentals
 An existing client can rent one or several cars from the car pool for a determined period. When rented, a car
becomes unavailable for further renting.
 When a car is returned, it becomes available for renting once again.
 Search the rental history of a given client, car, or all rentals during any given period.
Statistics
 The list of all cars in the car pool sorted by number of days they were rented.
 The list of clients sorted descending by the number of cars they have rented.
The application must have support for unlimited undo/redo with cascading.
"""
"""
1. Write a Car class so that:
- it has fields for license plate, make, model and color (all are str)
- class has properties for all fields
- all properties are set in the class constructor (__init__)
- property for license plates is read-only
- remaining properties are read/write
- override __str__ so cars are displayed nicely on console :)
2. Write a function that generates n cars (n - input parameter)
- make sure license plates are Ro, make, model color are real
(e.g., -> CJ 01 ABC, VW Polo, red)
- have lists of counties, makes, models, colors and randomly pick :)
3. Write a CarRepo class:
- keeps a list or dict of cars (protected field -> _data)
- methods to add a car, delete a car, get a car by license plate, get all cars
- override __len__ to return how many cars are in the repo
- define a RepoException class that inherits from Exception
- RepoException is in the same module as CarRepo
- raise RepoException when:
-> trying to add a car with existing license plates in repo
-> calling get on a car that's not in the repo
- NB! -> make the CarRepo iterable -> __iter__, __next__ ?
4. Write CarRepoTextFile class:
- CarRepoTextFile is derived from CarRepo class
- it adds a _load_file and a _save_file method
- cars are kept in a CSV file (CSV - comma separated values)
- cars are loaded from file when the constructor is called
- cars are saved after every operation
"""
# _load_file
# r - read, t - text
# fin = open(file_name, "rt")
# read all the lines in the file into a list
# each car should be represented on its own line
# list_of_lines = fin.readlines()
# split lines by "," -> add them to repo
# _save_file
# fout = open(file_name, "wt")
# turn each Car object into a one-line string -> CJ 01 ABC, Toyota, Yaris, red
# in a for loop :)
# fout.write(car_as_string)
# fout.close()
@@ -0,0 +1,54 @@
class Car:
def __init__(self, license_plate: str, make: str, model: str, color: str):
# self.__license_plate is a private field
self.__license_plate = license_plate
# self.make is a property setter
self.make = make
self.model = model
self.color = color
@property
def license(self):
return self.__license_plate
@property
def make(self):
return self.__make
@property
def model(self):
return self.__model
@property
def color(self):
return self.__color
@make.setter
def make(self, new_make: str):
self.__make = new_make
@model.setter
def model(self, new_model: str):
self.__model = new_model
@color.setter
def color(self, new_color: str):
self.__color = new_color
# repr is the str representation of the object
# called by data structures when str()-ing them
def __repr__(self):
return str(self)
def __str__(self):
display_str = f"{self.__license_plate}, {self.make} {self.model}," \
f" {self.color}"
return display_str
if __name__ == "__main__":
car = Car("CJ 01 ABC", "Audi", "A1", "blue")
print(car)
print(car.license)
car.make = "Toyota"
print(car)
@@ -0,0 +1,97 @@
from random import randint
from seminar.group_912.seminar_10.car import Car
# This class inherits from Exception
class RepoException(Exception):
pass
class CarRepoIterator():
def __init__(self, car_repo):
self._repo = car_repo
# start iteration at the beginning of the list
self._index = -1
def __next__(self):
if self._index == len(self._repo) - 1:
raise StopIteration()
self._index += 1
return self._repo._cars[self._index]
class CarRepo:
def __init__(self):
self._cars = []
def add_car(self, new_car: Car):
for car in self._cars:
if new_car.license == car.license:
raise RepoException("Duplicate license plates")
self._cars.append(new_car)
def get_all_cars(self):
return self._cars
def get_car_by_license_plate(self, license_plate: str):
for car in self._cars:
if car.get_license_plate() == license_plate:
return car
raise RepoException("Car not found!")
def remove_car(self, license_plate: str):
car = self.get_car_by_license_plate(license_plate)
self._cars.remove(car)
def update_car(self, license_plate: str, model: str, color: str, make: str):
car = self.get_car_by_license_plate(license_plate)
car.set_model(model)
car.set_color(color)
car.set_make(make)
def __iter__(self):
return CarRepoIterator(self)
def __len__(self) -> int:
return len(self._cars)
def __str__(self) -> str:
string = ""
for car in self._cars:
string += str(car) + "\n"
return string
def gen_cars(n):
car_list = []
counties = ["CJ", "HD", "MM", 'SV', "TM"]
make_model = [["VW", "Golf", "Polo", "Passat"], ["BMW", "E36", "M5 CS", "1 Series"],
["Renault", "Laguna", "Megane", "Clio"], ["Mercedes", "C63 AMG", "GLE 550", "E220"]]
colors = ["Red", "Green", "Grey", "Black", "Magenta", "Blue", "Light Pink"]
for i in range(n):
car_nr = randint(0, 3)
# 65 is the ASCII code for A
plate = f"{counties[randint(0, 4)]} {randint(0, 9)}{randint(1, 9)} {chr(randint(65, 90))}{chr(randint(65, 90))}{chr(randint(65, 90))}"
make = make_model[car_nr][0]
model = make_model[car_nr][randint(1, 3)]
color = colors[randint(0, 6)]
car = Car(plate, make, model, color)
car_list.append(car)
return car_list
if __name__ == "__main__":
cars = gen_cars(10)
repo = CarRepo()
for car in cars:
repo.add_car(car)
# print(len(repo))
# print(repo)
for car in repo:
print(car)
@@ -0,0 +1,40 @@
VS 65 HNV,Dacia,Logan,blue
TL 51 QQP,Dacia,Lodgy,red
IS 97 EJG,Toyota,Yaris,red
TR 91 KTU,Dacia,Sandero,blue
TL 89 SSI,Dacia,Sandero,black
TL 23 LTR,Dacia,Sandero,green
TR 24 UVD,Toyota,RAV-4,red
CJ 92 TRD,Dacia,Logan,green
CJ 36 ZIA,Dacia,Logan,red
IS 66 DJX,Dacia,Sandero,blue
CJ 51 HCN,Dacia,Lodgy,red
VS 23 GWQ,Toyota,Yaris,red
IS 59 WCC,Toyota,Corolla,red
CJ 92 YDU,Toyota,Yaris,green
AB 19 FIN,Dacia,Lodgy,blue
TL 70 GYH,Toyota,Corolla,red
TR 64 QBZ,Dacia,Logan,blue
TL 79 NEB,Dacia,Logan,black
GL 42 RKZ,Dacia,Lodgy,green
CJ 56 KNZ,Toyota,RAV-4,green
GR 29 DUQ,Toyota,RAV-4,green
AB 65 OJD,Dacia,Sandero,red
TL 53 KYY,Dacia,Lodgy,green
GL 58 ETL,Dacia,Logan,black
IS 73 YZI,Dacia,Lodgy,black
IS 61 VWR,Dacia,Sandero,green
CJ 57 FTB,Toyota,Corolla,black
B 36 VEF,Toyota,Yaris,blue
GR 17 ERN,Toyota,RAV-4,green
AB 40 ZXT,Dacia,Sandero,green
GR 95 USA,Toyota,Yaris,green
SJ 39 JTW,Dacia,Logan,blue
AB 93 WVL,Dacia,Sandero,black
TL 62 DZS,Dacia,Lodgy,green
IS 23 CUK,Dacia,Logan,blue
TR 99 JYB,Toyota,Corolla,green
TL 97 TRC,Toyota,Yaris,green
AB 19 SMK,Dacia,Logan,green
B 54 FNU,Dacia,Sandero,black
IS 27 WBE,Toyota,Yaris,green
@@ -0,0 +1,64 @@
class car:
"""
Add a new car to the car pool. Each car must have
-> a valid license plate number,
-> a make and model taken from a list of makes and models.
-> each car will have a color.
"""
def __init__(self, car_id: str, make: str, model: str, color: str):
self.__car_id = car_id
self.__make = make
self.__model = model
self.__color = color
@property
def car_id(self):
return self.__car_id
@car_id.setter
def car_id(self, new_value):
self.__car_id = new_value
@property
def make(self):
return self.__make
@property
def model(self):
return self.__model
@property
def color(self):
return self.__color
@color.setter
def color(self, new_value):
self.__color = new_value
def __str__(self):
return self.car_id + " -> " + self.make + " " + self.model + ", " + self.color
def test_car():
new_car = car("CJ 01 ABC", "Dacia", "Sandero", "red")
# assert new_car.get_id() == "CJ 01 ABC"
assert new_car.car_id == "CJ 01 ABC"
assert new_car.make == "Dacia"
assert new_car.model == "Sandero"
assert new_car.color == "red"
assert str(new_car) == "CJ 01 ABC -> Dacia Sandero, red"
# repaint it
# new_car.set_color("blue")
new_car.color = "blue"
assert new_car.color == "blue"
assert str(new_car) == "CJ 01 ABC -> Dacia Sandero, blue"
# change license plates
new_car.car_id = "CJ 99 XYZ"
assert str(new_car) == "CJ 99 XYZ -> Dacia Sandero, blue"
if __name__ == "__main__":
test_car()
@@ -0,0 +1,27 @@
from seminar.group_912.seminar_11.domain.exceptions import CarValidationException
class CarValidatorRO:
@staticmethod
def _is_license_valid(license):
# TODO Implement full validation
"""
Implement Romanian license plate validation
@param license:
@return: ...
"""
return len(license) > 2
# FIXME Duplicated code across validators, use inheritance to remove it
def validate(self, car):
errors = []
# V1 - All properties are non-empty
if not CarValidatorRO._is_license_valid(car.license_plate):
errors.append('Invalid license plate')
if len(car.make) < 2:
errors.append('Car make should have at least 3 letters')
if len(car.model) < 2:
errors.append('Car model should have at least 3 letters')
if len(errors) > 0:
raise CarValidationException(errors)
@@ -0,0 +1,42 @@
class Client(object):
def __init__(self, client_id, cnp, name):
self._client_id = client_id
self._cnp = cnp
self._name = name
@property
def id(self):
return self._client_id
@property
def cnp(self):
return self._cnp
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
def __eq__(self, z):
# don't compare apples to oranges
if type(z) != Client:
return False
# just look at the id field
return self.id == z.id
def __str__(self):
return "Id=" + str(self.id) + ", Name=" + str(self.name)
def __repr__(self):
return str(self)
if __name__ == "__main__":
c1 = Client(1000, "290010203445566", "Popescu Ana")
c2 = Client(1001, "290010203445566", "Popescu Ana")
# print(c1 == c2)
# print(repr(c1))
print({1000: c1, 1001: c2})
@@ -0,0 +1,5 @@
class ClientValidator:
# TODO Implement
def validate(self, client):
return True
@@ -0,0 +1,35 @@
class ValidatorException(Exception):
def __init__(self, message_list="Validation error!"):
self._message_list = message_list
@property
def messages(self):
return self._message_list
def __str__(self):
result = ""
for message in self.messages:
result += message
result += "\n"
return result
class CarException(Exception):
def __init__(self, msg):
self._msg = msg
def __str__(self):
return self._msg
class CarValidationException(CarException):
def __init__(self, error_list):
self._errors = error_list
def __str__(self):
result = ''
for er in self._errors:
result += er
result += '\n'
return result
@@ -0,0 +1,69 @@
from datetime import date, timedelta
class Rental:
def __init__(self, rental_id: int, start: date, end: date, client, car):
self._rentalId = rental_id
self._client = client
self._car = car
self._start = start
self._end = end
@property
def id(self):
return self._rentalId
@property
def client(self):
return self._client
@client.setter
def client(self, client):
self._client = client
@property
def car(self):
return self._car
@car.setter
def car(self, car):
self._car = car
@property
def start(self):
return self._start
@start.setter
def start(self, start):
self._start = start
@property
def end(self):
return self._end
@end.setter
def end(self, end):
self._end = end
# len(rental)
def __len__(self):
if self._end is not None:
return (self._end - self._start).days + 1
today = date.today()
return (today - self._start).days + 1
def __repr__(self):
return str(self)
def __str__(self):
return "Rental: " + str(self.id) + "\nCar: " + str(self.car) + "\nClient: " + str(
self.client) + "\nPeriod: " + self._start.strftime("%Y-%m-%d") + " to " + self._end.strftime("%Y-%m-%d")
if __name__ == "__main__":
d1 = date(2020, 12, 15)
d2 = date(2022, 12, 15)
td = timedelta(days=3)
print(type(d2 + td))
@@ -0,0 +1,19 @@
from datetime import date
from seminar.group_912.seminar_11.domain.exceptions import ValidatorException
from seminar.group_912.seminar_11.domain.rental import Rental
class RentalValidator:
def validate(self, rental):
if isinstance(rental, Rental) is False:
raise TypeError("Not a Rental")
_errorList = []
now = date(2000, 1, 1)
if rental.start < now:
_errorList.append("Rental starts in past;")
if len(rental) < 1:
_errorList.append("Rental must be at least 1 day;")
if len(_errorList) > 0:
raise ValidatorException(_errorList)
@@ -0,0 +1,222 @@
from seminar.group_912.seminar_11.domain.car import car
from random import choice, randint
import pickle
# RepoException inherits from Python's builtin Exception class
# RepoException "IS AN" exception
class RepoException(Exception):
pass
class car_repo(object):
def __init__(self):
# keys are car license numbers, values are car objects
self._data = {}
def add(self, new_car: car):
if new_car.car_id in self._data:
raise RepoException("Car already in repo")
self._data[new_car.car_id] = new_car
def get(self, car_id: str):
# If car cannot be found in repo, catch the dict's KeyError and
# re-raise it as RepoException
try:
return self._data[car_id]
except KeyError:
raise RepoException("Car is not in repo")
def get_all(self):
return list(self._data.values())
def __len__(self):
return len(self._data)
class car_repo_bin_file(car_repo):
def __init__(self, file_name="cars.bin"):
# call superclass constructor
super().__init__()
# remember the name of the file we're working with
self._file_name = file_name
# load the cars from the file
self._load_file()
def add(self, new_car: car):
# call the add() method on the super class
# we want to do everything the superclass add() already does
super().add(new_car)
# we also want to save all cars to a text file
self._save_file()
def _load_file(self):
# r - read, b - binary
obj = []
try:
fin = open(self._file_name, "rb")
obj = pickle.load(fin)
fin.close()
except FileNotFoundError:
pass
for c in obj:
super().add(c)
def _save_file(self):
# w - write mode (overwrite), b - binary mode
fout = open(self._file_name, "wb")
pickle.dump(self.get_all(), fout)
# NOTE Don't forget to close the file!
fout.close()
# just a plain old regular class :)
class car_repo_text_file(car_repo):
# this class inherits from car_repo
# => has all the mathods and attributes in car_repo
def __init__(self, file_name="cars.txt"):
# call superclass constructor
super().__init__()
# remember the name of the file we're working with
self._file_name = file_name
# load the cars from the file
self._load_file()
def _load_file(self):
"""
Load the cars from a text file
"""
# open a text file for reading
# t - text file mode, r - reading
lines = []
try:
fin = open(self._file_name, "rt")
# each car should be on its own line
lines = fin.readlines()
# close the file when done reading
fin.close()
except IOError:
# It's ok if we don't find the input file
pass
for line in lines:
current_line = line.split(",")
new_car = car(current_line[0].strip(), current_line[1].strip(), current_line[2].strip(),
current_line[3].strip())
# NOTE call super() so that we don't write the file we're reading from
super().add(new_car)
def _save_file(self):
"""
Save all cars to a text file
"""
# open a text file for writing
# t - text file mode, w - writing (rewrite the file every time)
fout = open(self._file_name, "wt")
# writes car_string into the text file
# fout.write(car_string)
for car in self.get_all():
car_string = str(car.car_id) + "," + str(car.make) + "," + str(car.model) + "," + str(car.color) + "\n"
fout.write(car_string)
# call close when done writing
fout.close()
def add(self, new_car: car):
# call the add() method on the super class
# we want to do everything the superclass add() already does
super().add(new_car)
# we also want to save all cars to a text file
self._save_file()
#
# def test_car_repo():
# repo = car_repo()
# # car repository is empty
# assert len(repo) == 0
#
# # add cars to the repo
# c1 = car("CJ 01 ABC", "Dacia", "Sandero", "red")
# repo.add(c1)
# c2 = car("CJ 01 XYZ", "Dacia", "Logdy", "white")
# repo.add(c2)
# assert len(repo) == 2
#
# # try to add the same car again
# try:
# repo.add(c1)
# assert False
# except RepoException:
# assert True
#
# # retrieve cars from repo
# assert repo.get("CJ 01 ABC") == c1
#
# # TODO Try to implement repo["CJ 01 XYZ"] == c2
# assert repo.get("CJ 01 XYZ") == c2
#
# # try to retrieve a non-existing car
# try:
# repo.get("SJ 04 RTY")
# assert False
# except RepoException:
# assert True
def generate_cars(n: int):
"""
Generates n car instances
:return: A list of n cars
"""
counties = ["AB", "SJ", "VS", "CJ", "B", "TL", "TR", "GL", "GR", "IS"]
make_model = {"Dacia": ["Logan", "Sandero", "Lodgy"], "Toyota": ["Corolla", "RAV-4", "Yaris"]}
colors = ["red", "blue", "green", "black"]
result = []
while n > 0:
letters = ""
for i in [0, 1, 2]:
letters += chr(randint(65, 90)) # A -> Z
car_id = choice(counties) + " " + str(randint(10, 99)) + " " + letters
make = choice(list(make_model.keys()))
model = choice(make_model[make])
color = choice(colors)
result.append(car(car_id, make, model, color))
n -= 1
return result
if __name__ == "__main__":
# repo = car_repo()
# repo_text = car_repo_text_file()
# # NOTE Save the generated cars to the file
# for c in generate_cars(10):
# print(str(c))
# # repo.add(c)
# repo_text.add(c)
# read the cars.bin input file
car_repo_bin = car_repo_bin_file()
car_list = generate_cars(20)
for c in car_list:
car_repo_bin.add(c)
print("Cars saved in cars.bin")
for c in car_repo_bin.get_all():
print(str(c))
# read the cars.txt file
# car_repo_text = car_repo_text_file()
# print("\n\nCars saved in cars.txt")
# for c in car_repo_text.get_all():
# print(str(c))
# NOTE Load the cars and display them again
# new_car_repo = car_repo_text_file()
# for c in new_car_repo.get_all():
# print(str(c))
@@ -0,0 +1,13 @@
from seminar.group_912.seminar_11.domain.client import Client
from seminar.group_912.seminar_11.repository.repository_exception import RepositoryException
class ClientRepo:
# TODO Finish implementation
def __init__(self):
self._clients = {}
def add(self, client):
if client.id in self._clients.keys():
raise RepositoryException("Duplicate Client id")
self._clients[client.id] = client
@@ -0,0 +1,23 @@
import datetime
from seminar.group_912.seminar_11.repository.repository_exception import RepositoryException
class RentalRepository:
# TODO Finish implementation
def __init__(self):
self._data = {}
def add(self, rental):
if rental.id in self._data.keys():
raise RepositoryException("Duplicate Rental ID")
self._data[rental.id] = rental
def remove(self, rental_id):
if rental_id in self._data.keys():
del self._data[rental_id]
else:
raise RepositoryException("Rental was not found")
def get_all(self):
return list(self._data.values())

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