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,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())
@@ -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,14 @@
from seminar.group_912.seminar_11.domain.car_validators import CarValidatorRO
class CarService:
def __init__(self, repo, validator: CarValidatorRO):
self._repo = repo
self._validator = validator
def get(self, car_id):
return self._repo.get(car_id)
# NOTE convenience for rental repo
def get_all(self):
return self._repo.get_all()
@@ -0,0 +1,68 @@
from datetime import date
from seminar.group_912.seminar_10.car import Car
from seminar.group_912.seminar_11.domain.client import Client
from seminar.group_912.seminar_11.domain.rental import Rental
from seminar.group_912.seminar_11.domain.rental_validators import RentalValidator
from seminar.group_912.seminar_11.repository.rental_repo import RentalRepository
from seminar.group_912.seminar_11.services.car_service import CarService
class CarRentalDaysDTO:
# Data Transfer Object between service and UI layer
def __init__(self, car, rental_days):
self._car = car
self._rental_days = rental_days
@property
def car(self):
return self._car
@property
def days(self):
return self._rental_days
def __le__(self):
# NOTE for sorting
pass
def __str__(self):
return str(self.days) + " for car " + str(self.car)
class RentalService:
# To do its job, the RentalService needs the rental repo, a car service
# and a way to validate car instances
def __init__(self, repo: RentalRepository, car_service: CarService, rental_validator: RentalValidator):
self._repo = repo
self._car_service = car_service
self._validator = rental_validator
# We assume we read fields in the UI and create the object here
def add(self, rental_id: int, start_date: date, end_date: date, client: Client, car: Car):
# 1. Create the object
rent = Rental(rental_id, start_date, end_date, client, car)
# 2. Validate it -> exception if something is wrong
self._validator.validate(rent)
# 3. Add to rentals repo
self._repo.add(rent)
def statistic_cars_by_rental_days(self):
# NOTE keys are car license plates, values are total rental days
rental_dict = {}
for rental in self._repo.get_all():
if rental.car.car_id not in rental_dict:
rental_dict[rental.car.car_id] = len(rental)
else:
rental_dict[rental.car.car_id] += len(rental)
# TODO add all cars that were never rented
result = []
for key in rental_dict:
car = self._car_service.get(key)
result.append(CarRentalDaysDTO(car, rental_dict[key]))
# sort by number of rental days
result.sort(key=lambda x: x.days, reverse=True)
return result
@@ -0,0 +1,87 @@
"""
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.
"""
from seminar.group_912.seminar_11.domain.car import car
from seminar.group_912.seminar_11.domain.car_validators import CarValidatorRO
from seminar.group_912.seminar_11.domain.client import Client
from seminar.group_912.seminar_11.domain.rental_validators import RentalValidator
from seminar.group_912.seminar_11.repository.car_repo import car_repo_text_file
from seminar.group_912.seminar_11.repository.rental_repo import RentalRepository
# Assemble the layers bottom up
from seminar.group_912.seminar_11.services.car_service import CarService
from seminar.group_912.seminar_11.services.rental_service import RentalService
import random
from datetime import date, timedelta
def generate_rentals(n: int, rental_service):
# NOTE generate n rentals here
# this client rents all cars
c1 = Client(1000, "290010203445566", "Popescu Ana")
# car_repo holds the cars that will be rented out
car_repo = car_repo_text_file()
rental_id = 1000
rnd = random.randint
while n > 0:
car = random.choice(car_repo.get_all())
start = date(rnd(2021, 2022), rnd(1, 12), rnd(1, 28))
end = start + timedelta(days=rnd(1, 20))
rental_service.add(rental_id, start, end, c1, car)
rental_id += 1
n -= 1
car_repo = car_repo_text_file()
rental_repo = RentalRepository()
# NOTE You can switch the repo and validator you use without changing
# the service class source code
# NOTE this is named "dependency injection"
car_service = CarService(car_repo, CarValidatorRO())
rental_service = RentalService(rental_repo, car_service, RentalValidator())
generate_rentals(100, rental_service)
statistic_result = rental_service.statistic_cars_by_rental_days()
for sr in statistic_result:
print(sr)
# for s in statistic_result:
# print(s)
# ui = UI( < pass all services here >)
# ui.start()
# for car in generate_cars(20):
# car_repo.add(car)
@@ -0,0 +1,7 @@
class UI:
def __init__(self, car_service, client_service, rent_service):
pass
def start(self):
# NOTE start the UI here
pass
@@ -0,0 +1,91 @@
from seminar.group_912.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_912.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_912.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,37 @@
from seminar.group_912.seminar_12.domain.car import Car
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)
'''
2. Delete its rentals
NB! This implementation is not transactional, i.e. the two delete operations are performed separately
'''
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,53 @@
from seminar.group_912.seminar_12.domain.client import Client
from seminar.group_912.seminar_12.service.undo_service import call, operation, cascaded_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)
undo = call(self.create, client.id, client.cnp, client.name)
redo = call(self.delete, client.id)
# self._undo_service.record(operation(undo, redo))
# undo/redo for client
undo_op = [operation(undo, redo)]
'''
2. Delete their rentals
NB! This implementation is not transactional, i.e. the two delete operations are performed separately
'''
rentals = self._rental_service.filter_rentals(client, None)
for rent in rentals:
self._rental_service.delete_rental(rent.getId(), False)
undo_call = call(self._rental_service.create_rental, rent.id, rent.client, rent.car, rent.start, rent.end)
redo_call = call(self._rental_service.delete_rental, rent.id)
undo_op.append(operation(undo_call, redo_call))
# record the cascaded operation for undo/redo
self._undo_service.record(cascaded_operation(*undo_op))
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_912.seminar_12.domain.car_rental_exception import CarRentalException
from seminar.group_912.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,104 @@
"""
ways to implement undo/redo
1. copy the repository(ies) at each undo-able step
-> deep copy wastes (a lot of) memory
-> Memento design pattern https://refactoring.guru/design-patterns/memento
2. remember the operation itself and do its opposite (undo) or do it again (redo)
-> does not waster memory
-> Command design pattern https://refactoring.guru/design-patterns/command
3. state-diffing
-> the difference between versions of the repository
"""
class UndoRedoError(Exception):
pass
class call():
def __init__(self, function_name, *function_params):
self._function_name = function_name
self._function_params = function_params
def run(self):
# () -- call operator
return self._function_name(*self._function_params)
def __call__(self, *args, **kwargs):
return self.run()
class operation:
def __init__(self, undo_call: call, redo_call: call):
self._undo_call = undo_call
self._redo_call = redo_call
def undo(self):
# self._undo_call.run()
self._undo_call()
def redo(self):
self._redo_call()
class cascaded_operation():
def __init__(self, *operations):
self._operations = operations
def undo(self):
for op in self._operations:
op.undo()
def redo(self):
for op in self._operations:
op.redo()
class UndoService:
def __init__(self):
self._history = []
self._index = 0
self._undo_redo_flag = True
def record(self, op: operation):
if self._undo_redo_flag is False:
return
self._history = self._history[:self._index]
self._history.append(op)
self._index = len(self._history)
def undo(self):
if self._index == 0:
raise UndoRedoError("No more undos")
# NOTE Don't record anything for undo/redo, as wel are already
# undoing things
self._undo_redo_flag = False
self._history[self._index - 1].undo()
self._undo_redo_flag = True
self._index -= 1
def redo(self):
if self._index == len(self._history):
raise UndoRedoError("No more redos")
self._undo_redo_flag = False
self._history[self._index].redo()
self._undo_redo_flag = True
self._index += 1
if __name__ == "__main__":
def a(x, y, z, t):
return x + y + z + t
print(a(1, 2, 3, 4))
call_a = call(a, 10, 20, 30, 40)
# print(call_a.run())
print(call_a())
@@ -0,0 +1,83 @@
"""
Created on Nov 17, 2018
@author: Arthur
"""
from seminar.group_912.seminar_12.domain.car import CarValidator
from seminar.group_912.seminar_12.domain.client import ClientValidator
from seminar.group_912.seminar_12.domain.rental import RentalValidator
from seminar.group_912.seminar_12.repository.repository import Repository
from seminar.group_912.seminar_12.service.car_service import CarService
from seminar.group_912.seminar_12.service.client_service import ClientService
from seminar.group_912.seminar_12.service.rental_service import RentalService
from seminar.group_912.seminar_12.service.undo_service import UndoService
from seminar.group_912.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_912.seminar_12.domain.car import CarValidator
from seminar.group_912.seminar_12.domain.client import ClientValidator
from seminar.group_912.seminar_12.domain.rental import RentalValidator
from seminar.group_912.seminar_12.repository.repository import Repository
from seminar.group_912.seminar_12.service.car_service import CarService
from seminar.group_912.seminar_12.service.client_service import ClientService
from seminar.group_912.seminar_12.service.rental_service import RentalService
from seminar.group_912.seminar_12.service.undo_service import UndoService
from seminar.group_912.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_912.seminar_12.domain.car import CarValidator
from seminar.group_912.seminar_12.domain.client import ClientValidator
from seminar.group_912.seminar_12.domain.rental import RentalValidator
from seminar.group_912.seminar_12.repository.repository import Repository
from seminar.group_912.seminar_12.service.car_service import CarService
from seminar.group_912.seminar_12.service.client_service import ClientService
from seminar.group_912.seminar_12.service.rental_service import RentalService
from seminar.group_912.seminar_12.service.undo_service import UndoService
from seminar.group_912.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,80 @@
import random
from texttable import Texttable
class Minefield:
def __init__(self, rows, cols, mines: int):
self._rows = rows
self._cols = cols
self._mines = mines
'''
Data representation
None -> empty square
'''
# TODO Border all the matrices !!
self._data = [0] * ((self._rows + 2) * (self._cols + 2))
self._mines_laid = False
def reveal(self, row, col: int):
if not self._mines_laid:
# the first call of this method
self._lay_mines(row, col)
self._mines_laid = True
pass
def _set_value(self, row, col, value):
"""
Transform row, col coordinates to padded matrix coordinates
:param value: Set this value in the matrix
"""
pass
def _get_value(self, row, col) -> int:
"""
Return the value at row,col from the padded matrix
:param row:
:param col:
:return: Square value
"""
pass
def _lay_mines(self, empty_row, empty_col):
"""
Lay the mines
:param empty_row: there is no mine here
:param empty_col: there is no mine here
"""
locations = []
for i in range(1, self._rows + 1):
for j in range(1, self._cols + 1):
# if i != (empty_row + 1) or j != (empty_col + 1):
locations.append((i, j))
# random.shuffle(locations)
for i in range(self._mines):
x, y = locations.pop(0)
print(x, y)
# self._data[x * self._cols + y] = 'X'
self._set_value(x, y, 'X')
def __str__(self):
t = Texttable()
t.set_max_width(0)
# FIXME Separate into another function so we don't recompute header
# at each __str__ call
header = ['/']
for i in range(0, self._cols):
header.append(chr(ord('A') + i))
t.header(header)
for index in range(self._cols + 1, self._rows * (self._cols + 2), self._cols + 2):
t.add_row([index // (self._cols + 1)] + self._data[index:index + self._cols])
return t.draw()
field = Minefield(5, 5, 25)
field.reveal(0, 0)
print(field)
@@ -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,71 @@
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. Implement the Flight class
-- required attributes
-- __str__
-- use private/protected attributes and properties :)
2. Implement the TextFlightsRepo class
-- __init__()
-- _load_file()
-- get_all()
3. Implement the TestFlightsRepo(unittest.TestCase) class
-- use PyUnit test framework
-- self.assert*(...)
-- put tests in the src/tests package
4. Implement the FlightServices class
-- __init__ (receive the repository instance)
-- get_all() (move flights to UI for display)
5. Implement the UI class
-- __init__ (receive the services class it is working with)
-- display the main menu:
a. Display all flights
x. Exit
---= Iteration 2 -> Add flight =---
2. Implement the TextFlightsRepo class
-- add_flight(...) # + specification
-- _save_file(...) # called from add_flight(...)
3. Implement the TestFlightsRepo(unittest.TestCase) class
-- add test case for add_flight
4. Implement FlightValidator class
-- validate(...) method -> raise Exception if problems
5. Implement the FlightServices class
-- __init__ (receive the repository and validator instances)
-- add_flight(...) method # + specification
-- calls FlightValidator.validate
-- call repo.add_flight()
6. Implement the UI class
-- display the main menu:
a. Display all flights
b. Add flight
x. Exit