From a7fd1c11a7ede79601d9490f0e3d45eeef630a97 Mon Sep 17 00:00:00 2001 From: Squidfishrl Date: Mon, 18 Oct 2021 04:37:02 +0300 Subject: [PATCH 1/6] Add check_circle homework --- .../12_Ivaylo_Genchev/01_check_circles.py | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 homeworks/12_Ivaylo_Genchev/01_check_circles.py diff --git a/homeworks/12_Ivaylo_Genchev/01_check_circles.py b/homeworks/12_Ivaylo_Genchev/01_check_circles.py new file mode 100644 index 0000000..4a6788d --- /dev/null +++ b/homeworks/12_Ivaylo_Genchev/01_check_circles.py @@ -0,0 +1,169 @@ +from __future__ import annotations + +from math import sqrt + +""" +Assignment: + +Given 2 circles in 2d space: +Params: center point cordinates(x, y), radius size +Task: Determine their state - matching, touching, nested, intersecting, unrelated. +""" + +class Circle: + def __init__(self, x: float, y: float, r: float) -> None: + self.cords = (x, y) + self.radius = r + + def get_distance(self, other_circle: Circle) -> float: + """Calculates the distance between 2 circle centers.""" + + # calculate distance between 2 points (Pythagorean theorem) + return sqrt((self.cords[0] - other_circle.cords[0])**2 + (self.cords[1] - other_circle.cords[1])**2) + + + def is_matching(self, other_circle: Circle) -> bool: + """ + Checks if 2 circles are matching. + Both circles must share all points. + """ + + # check if the 2 circles have matching cordinates and radius + if self.cords[0] == other_circle.cords[0] and self.cords[1] == other_circle.cords[1] and self.radius == other_circle.radius: + return True + + # the circles aren't matching + return False + + def is_touching(self, other_circle: Circle, _dist: None) -> bool: + """ + Checks if 2 circles are touching. + Both circles must share 1 point. + """ + + dist = _dist if _dist is not None else self.get_distance(other_circle) + + # check if the distance between the circles is equal to their combined radius + if dist == self.radius + other_circle.radius: + return True + + # the circles aren't touching + return False + + def is_nested(self, other_circle: Circle, _dist: None) -> bool: + """Checks if this->circle is fully consumed by another cirlce without the 2 circles touching.""" + + dist = _dist if _dist is not None else self.get_distance(other_circle) + + # check if the distance between the 2 circles + the firsts radius is less than the seconds radius + if dist + self.radius < other_circle.radius: + return True + + # this circle isn't nested in other_circe + return False + + def is_tangent(self, other_circle: Circle, _dist: None) -> bool: + """Checks if this->circle is fully consumed by another cirlce with the 2 circles touching.""" + + dist = _dist if _dist is not None else self.get_distance(other_circle) + + # check if the distance between the 2 circles + the firsts radius is the same as the second circles radius + if dist + self.radius == other_circle.radius: + return True + + # this circle isn't tangent to the other_circle + return False + + def is_intersecting(self, other_circle: Circle, _dist: None) -> bool: + """Checks if the 2 circles have more than 1 touching point.""" + + dist = _dist if _dist is not None else self.get_distance(other_circle) + + # check if the distance between the 2 circles is less than their combined radius + if dist < self.radius + other_circle.radius: + return True + + # the circles aren't intersecting + return False + + def is_unrelated(self, other_circle: Circle, _dist: None) -> bool: + """Checks if the 2 circles are unrelated (aren't in any other sate).""" + + dist = _dist if _dist is not None else self.get_distance(other_circle) + + # check if the distance between the 2 circles is greater than their combined radius + if dist > self.radius + other_circle.radius: + return True + + # the circles aren't unrelated + return False + + def get_state(self, other_circle: Circle) -> int: + """ + Returns an integer corresponding to the 2 circles state: + 0 - the 2 circles are matching. + 1 - the 2 circles are touching. + 2 - this circle is nested in other_circle. + 3 - this circle is tangent to other_circle. + 4 - the 2 circles are intersecting. + -1 - the 2 circles are unrelated. + """ + + if self.is_matching(other_circle): + print("The circles are matching") + return 0 + + # Calculate distance once (sqrt can be expensive) + dist = self.get_distance(other_circle) + + if self.is_touching(other_circle, dist): + print("The circles are touching") + return 1 + + if self.is_nested(other_circle, dist): + print("This circle is nested in the other cirlce") + return 2 + + if self.is_tangent(other_circle, dist): + print("This circle is tangent to the other circle") + return 3 + + if self.is_intersecting(other_circle, dist): + print("The circles are intersecting") + return 4 + + print("The circles are unrelated") + return -1 + + +"""Tests:""" + +# matching +a = Circle(2.6, 7, 2) +b = Circle(2.6, 7, 2) +print(a.get_state(b)) # 0 + +# touching +a = Circle(0, 0, 5) +b = Circle(12, 9, 10) +print(a.get_state(b)) # 1 + +# nested +b = Circle(0, 0.2, 100.3) +a = Circle(5, 5, 20) +print(a.get_state(b)) # 2 + +# tangent +b = Circle(0, 0, 100) +a = Circle(0, 80, 20) +print(a.get_state(b)) # 3 + +# intersecting +a = Circle(0, 0, 5) +b = Circle(3, 3, 3) +print(a.get_state(b)) # 4 + +# unrelated +a = Circle(0, 0, 1) +b = Circle(10, 10, 1) +print(a.get_state(b)) # -1 \ No newline at end of file From fcadbb77afd048fc3a793f86a8cfbc3d718b9dac Mon Sep 17 00:00:00 2001 From: Ivaylo Genchev <48411473+Squidfishrl@users.noreply.github.com> Date: Mon, 18 Oct 2021 11:05:35 +0300 Subject: [PATCH 2/6] Add custom exceptions --- .../12_Ivaylo_Genchev/01_check_circles.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/homeworks/12_Ivaylo_Genchev/01_check_circles.py b/homeworks/12_Ivaylo_Genchev/01_check_circles.py index 4a6788d..23d6403 100644 --- a/homeworks/12_Ivaylo_Genchev/01_check_circles.py +++ b/homeworks/12_Ivaylo_Genchev/01_check_circles.py @@ -10,8 +10,28 @@ Task: Determine their state - matching, touching, nested, intersecting, unrelated. """ +class NegativeRadiusError(Exception): + """Custom exception raised when circle radius is negative.""" + + def __init__(self) -> None: + super().__init__("Circle radius is negative.") + +class InvalidDataType(Exception): + """Custom exception raised when circle cordinates or radius aren't of type int or float""" + + def __init__(self) -> None: + super().__init__("Invalid data type. Expected integer or float.") + + class Circle: def __init__(self, x: float, y: float, r: float) -> None: + + if (type(x) != int and type(x) != float) or (type(y) != int and type(y) != float) or (type(r) != int and type(r) != float): + raise InvalidDataType() + + if r < 0: + raise NegativeRadiusError() + self.cords = (x, y) self.radius = r @@ -136,7 +156,7 @@ def get_state(self, other_circle: Circle) -> int: return -1 -"""Tests:""" +"""Tests: (don't catch custom exceptions for debug purposes)""" # matching a = Circle(2.6, 7, 2) @@ -166,4 +186,4 @@ def get_state(self, other_circle: Circle) -> int: # unrelated a = Circle(0, 0, 1) b = Circle(10, 10, 1) -print(a.get_state(b)) # -1 \ No newline at end of file +print(a.get_state(b)) # -1 From bc1423e1938e20328338a862d79d4f0dd75e9c17 Mon Sep 17 00:00:00 2001 From: Squidfishrl Date: Thu, 28 Oct 2021 15:02:37 +0300 Subject: [PATCH 3/6] Correct for PR comments --- .../12_Ivaylo_Genchev/01_check_circles.py | 229 +++++++----------- 1 file changed, 82 insertions(+), 147 deletions(-) diff --git a/homeworks/12_Ivaylo_Genchev/01_check_circles.py b/homeworks/12_Ivaylo_Genchev/01_check_circles.py index 23d6403..bd5dc5e 100644 --- a/homeworks/12_Ivaylo_Genchev/01_check_circles.py +++ b/homeworks/12_Ivaylo_Genchev/01_check_circles.py @@ -1,14 +1,5 @@ -from __future__ import annotations - from math import sqrt -""" -Assignment: - -Given 2 circles in 2d space: -Params: center point cordinates(x, y), radius size -Task: Determine their state - matching, touching, nested, intersecting, unrelated. -""" class NegativeRadiusError(Exception): """Custom exception raised when circle radius is negative.""" @@ -16,174 +7,118 @@ class NegativeRadiusError(Exception): def __init__(self) -> None: super().__init__("Circle radius is negative.") -class InvalidDataType(Exception): - """Custom exception raised when circle cordinates or radius aren't of type int or float""" - - def __init__(self) -> None: - super().__init__("Invalid data type. Expected integer or float.") - - -class Circle: - def __init__(self, x: float, y: float, r: float) -> None: - if (type(x) != int and type(x) != float) or (type(y) != int and type(y) != float) or (type(r) != int and type(r) != float): - raise InvalidDataType() - - if r < 0: - raise NegativeRadiusError() - - self.cords = (x, y) - self.radius = r - - def get_distance(self, other_circle: Circle) -> float: - """Calculates the distance between 2 circle centers.""" - - # calculate distance between 2 points (Pythagorean theorem) - return sqrt((self.cords[0] - other_circle.cords[0])**2 + (self.cords[1] - other_circle.cords[1])**2) - - - def is_matching(self, other_circle: Circle) -> bool: +class InvalidDataType(Exception): + """Custom exception raised when circle coordinates or radius aren't of type int or float.""" + + def __init__(self, message: str = "") -> None: + super().__init__(f"Invalid data type. {message}") + + +def get_distance( + c1_center: tuple[float | int, float | int], + c2_center: tuple[float | int, float | int], +) -> float: + """Calculates the distance between 2 circle centers.""" + + return sqrt((c1_center[0] - c2_center[0]) ** 2 + (c1_center[1] - c2_center[1]) ** 2) + + +def check_circles( + c1_center: tuple[float, float], + c1_radius: float, + c2_center: tuple[float, float], + c2_radius: float, +) -> str: + + # Handle incorrect data types + if type(c1_center) != tuple or type(c2_center) != tuple: + raise InvalidDataType(f"Expected tuple.") + if type(c1_center[0]) != int and type(c1_center[0]) != float: + raise InvalidDataType( + f"Expected int or float. Not {type(c1_center[0])}") + if type(c1_center[1]) != int and type(c1_center[1]) != float: + raise InvalidDataType( + f"Expected int or float. Not {type(c1_center[1])}") + if type(c2_center[0]) != int and type(c2_center[0]) != float: + raise InvalidDataType( + f"Expected int or float. Not {type(c2_center[0])}") + if type(c2_center[1]) != int and type(c2_center[1]) != float: + raise InvalidDataType( + f"Expected int or float. Not {type(c2_center[1])}") + if type(c1_radius) != int and type(c1_radius) != float: + raise InvalidDataType(f"Expected int or float. Not {type(c1_radius)}") + if type(c2_radius) != int and type(c2_radius) != float: + raise InvalidDataType(f"Expected int or float. Not {type(c2_radius)}") + + # Handle negative radius + if c1_radius < 0 or c2_radius < 0: + raise NegativeRadiusError + + def are_matching() -> bool: """ Checks if 2 circles are matching. Both circles must share all points. """ - # check if the 2 circles have matching cordinates and radius - if self.cords[0] == other_circle.cords[0] and self.cords[1] == other_circle.cords[1] and self.radius == other_circle.radius: + # check if the 2 circles have matching coordinates and radius + if c1_center == c2_center and c1_radius == c2_radius: return True # the circles aren't matching return False - def is_touching(self, other_circle: Circle, _dist: None) -> bool: + def are_nested() -> bool: + """Checks if one of the circles is fully consumed by the other.""" + + # check if the distance between the 2 circles + the firsts radius is less than the seconds radius + if circle_dist + c1_radius < c2_radius or circle_dist + c2_radius < c1_radius: + return True + + # the circles aren't nested + return False + + def are_touching() -> bool: """ Checks if 2 circles are touching. Both circles must share 1 point. """ - dist = _dist if _dist is not None else self.get_distance(other_circle) - # check if the distance between the circles is equal to their combined radius - if dist == self.radius + other_circle.radius: - return True - - # the circles aren't touching - return False - - def is_nested(self, other_circle: Circle, _dist: None) -> bool: - """Checks if this->circle is fully consumed by another cirlce without the 2 circles touching.""" - - dist = _dist if _dist is not None else self.get_distance(other_circle) - - # check if the distance between the 2 circles + the firsts radius is less than the seconds radius - if dist + self.radius < other_circle.radius: + if circle_dist == c1_radius + c2_radius: return True - - # this circle isn't nested in other_circe - return False - - def is_tangent(self, other_circle: Circle, _dist: None) -> bool: - """Checks if this->circle is fully consumed by another cirlce with the 2 circles touching.""" - - dist = _dist if _dist is not None else self.get_distance(other_circle) - # check if the distance between the 2 circles + the firsts radius is the same as the second circles radius - if dist + self.radius == other_circle.radius: - return True - - # this circle isn't tangent to the other_circle + # the circles aren't touching return False - def is_intersecting(self, other_circle: Circle, _dist: None) -> bool: + def are_intersecting() -> bool: """Checks if the 2 circles have more than 1 touching point.""" - dist = _dist if _dist is not None else self.get_distance(other_circle) - - # check if the distance between the 2 circles is less than their combined radius - if dist < self.radius + other_circle.radius: + # check if the distance between the 2 circles is less than their combined radius + if circle_dist < c1_radius + c2_radius: return True # the circles aren't intersecting return False - def is_unrelated(self, other_circle: Circle, _dist: None) -> bool: - """Checks if the 2 circles are unrelated (aren't in any other sate).""" - - dist = _dist if _dist is not None else self.get_distance(other_circle) - - # check if the distance between the 2 circles is greater than their combined radius - if dist > self.radius + other_circle.radius: - return True - - # the circles aren't unrelated - return False - - def get_state(self, other_circle: Circle) -> int: - """ - Returns an integer corresponding to the 2 circles state: - 0 - the 2 circles are matching. - 1 - the 2 circles are touching. - 2 - this circle is nested in other_circle. - 3 - this circle is tangent to other_circle. - 4 - the 2 circles are intersecting. - -1 - the 2 circles are unrelated. - """ - - if self.is_matching(other_circle): - print("The circles are matching") - return 0 - - # Calculate distance once (sqrt can be expensive) - dist = self.get_distance(other_circle) - - if self.is_touching(other_circle, dist): - print("The circles are touching") - return 1 - - if self.is_nested(other_circle, dist): - print("This circle is nested in the other cirlce") - return 2 - - if self.is_tangent(other_circle, dist): - print("This circle is tangent to the other circle") - return 3 - - if self.is_intersecting(other_circle, dist): - print("The circles are intersecting") - return 4 - - print("The circles are unrelated") - return -1 - - -"""Tests: (don't catch custom exceptions for debug purposes)""" - -# matching -a = Circle(2.6, 7, 2) -b = Circle(2.6, 7, 2) -print(a.get_state(b)) # 0 + circle_dist = get_distance(c1_center, c2_center) -# touching -a = Circle(0, 0, 5) -b = Circle(12, 9, 10) -print(a.get_state(b)) # 1 + if are_matching(): + return "MATCHING" + elif are_nested(): + return "CONTAINING" + elif are_intersecting(): + return "INTERSECTING" + elif are_touching(): + return "TOUCHING" -# nested -b = Circle(0, 0.2, 100.3) -a = Circle(5, 5, 20) -print(a.get_state(b)) # 2 + return "NO COMMON" -# tangent -b = Circle(0, 0, 100) -a = Circle(0, 80, 20) -print(a.get_state(b)) # 3 -# intersecting -a = Circle(0, 0, 5) -b = Circle(3, 3, 3) -print(a.get_state(b)) # 4 +if __name__ == "__main__": -# unrelated -a = Circle(0, 0, 1) -b = Circle(10, 10, 1) -print(a.get_state(b)) # -1 + print(check_circles((2.6, 7), 2, (2.6, 7), 2)) # MATCHING + print(check_circles((0, 0), 5, (12, 9), 10)) # TOUCHING + print(check_circles((0, 0.2), 100.3, (5, 5), 19.2)) # CONTAINING + print(check_circles((0, 0), 5, (3, 3), 3)) # INTERSECTING + print(check_circles((0, 0), 1, (10, 10), 1)) # NO COMMON From 2aa1b79d65a0288d7cfbba8b3eb50df4a58337c4 Mon Sep 17 00:00:00 2001 From: Squidfishrl Date: Wed, 12 Jan 2022 01:15:46 +0200 Subject: [PATCH 4/6] Fix hw folder class number --- .../{12_Ivaylo_Genchev => 13_Ivaylo_Genchev}/01_check_circles.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename homeworks/{12_Ivaylo_Genchev => 13_Ivaylo_Genchev}/01_check_circles.py (100%) diff --git a/homeworks/12_Ivaylo_Genchev/01_check_circles.py b/homeworks/13_Ivaylo_Genchev/01_check_circles.py similarity index 100% rename from homeworks/12_Ivaylo_Genchev/01_check_circles.py rename to homeworks/13_Ivaylo_Genchev/01_check_circles.py From e200781fe959dcc92253e8a0671fc0d989a517af Mon Sep 17 00:00:00 2001 From: Squidfishrl Date: Wed, 12 Jan 2022 01:39:33 +0200 Subject: [PATCH 5/6] Upload recursion homework (2) --- .../02_grayscale_matrix_areas.py | 150 ++++++++++++++++++ .../02_reach_ladder_position.py | 98 ++++++++++++ .../02_replace_collection_items.py | 55 +++++++ 3 files changed, 303 insertions(+) create mode 100644 homeworks/13_Ivaylo_Genchev/02_grayscale_matrix_areas.py create mode 100644 homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py create mode 100644 homeworks/13_Ivaylo_Genchev/02_replace_collection_items.py diff --git a/homeworks/13_Ivaylo_Genchev/02_grayscale_matrix_areas.py b/homeworks/13_Ivaylo_Genchev/02_grayscale_matrix_areas.py new file mode 100644 index 0000000..210f631 --- /dev/null +++ b/homeworks/13_Ivaylo_Genchev/02_grayscale_matrix_areas.py @@ -0,0 +1,150 @@ +from random import randint # generate random values in a matrix +from dataclasses import dataclass # cell class + +""" +Задача 1: +Графично изображение е представено с матрица от m реда и n колони. Клетките на +матрицата, във всяка от които е записана целочислена стойност от 0 до 255, съответстват на пикселите +в графичното изображение (формат grayscale). +Всяка клетка в матрицата има до 8 съседа — до 4 по диагонал, до два, разположени хоризонтално, +и до два – вертикално. +Област в изображението е непрекъсната последователност от съседни клетки с ненулеви стойности. +Черните елементи, представени със стойност 0, се считат за контури на областите. Така, една област се +определя от граница от нулеви елементи и границите на матрицата. +Дефинирайте функция, `avg_brightness` която получава като аргумент матрица от посочения вид и +извежда на стандартния изход средната яркост на всяка от областите, сортирани в низходящ +ред според яркостта. Средна яркост на дадената област се изчислява като средно-аритметично на +стойностите на всички клетки (пиксели), образуващи областта. За всяка област изведете координатите +на една произволна клетка от нея и средната яркост на областта. +""" + + +@dataclass +class Cell: + row: int + column: int + value: int + is_accessed: bool = False # prevent getting the same area twice + + def __int__(self): + return self.value + + +class Matrix: + def __init__(self, rows: int, columns: int, contour_chance: int): + """Generate a random grayscale matrix rowsXcolumns where figurations have a contour_chance to appear over any brightness.""" + if rows < 1: + raise ValueError( + f"Invalid value {rows}. 'rows' has to be a positive integer." + ) + + if columns < 1: + raise ValueError( + f"Invalid value {columns}. 'columns' has to be a positive integer." + ) + + if contour_chance < 1 or contour_chance > 100: + raise ValueError( + f"Invalid value {contour_chance}. 'contour_chance' has to be a positive integer." + ) + + self.grayscale_matrix: list[list[Cell]] = [ + [ + Cell(row=m, column=n, value=0) + if randint(1, 100) < contour_chance + else Cell(row=m, column=n, value=randint(0, 255)) + for n in range(0, columns) + ] + for m in range(0, rows) + ] + self.rows = rows + self.columns = columns + + def surrounding_cells(self, cell: Cell) -> list[Cell]: + """Get a list of cells surrounding a cell.""" + surrounding: list[Cell] = [] + + if cell.row != 0: # upper + surrounding.append(self.grayscale_matrix[cell.row - 1][cell.column]) + if cell.column != 0: # upper left + surrounding.append(self.grayscale_matrix[cell.row - 1][cell.column - 1]) + if cell.column < self.columns - 1: # upper right + surrounding.append(self.grayscale_matrix[cell.row - 1][cell.column + 1]) + + if cell.row < self.rows - 1: # bottom + surrounding.append(self.grayscale_matrix[cell.row + 1][cell.column]) + if cell.column != 0: # bottom left + surrounding.append(self.grayscale_matrix[cell.row + 1][cell.column - 1]) + if cell.column < self.columns - 1: # bottom right + surrounding.append(self.grayscale_matrix[cell.row + 1][cell.column + 1]) + + if cell.column != 0: # left + surrounding.append(self.grayscale_matrix[cell.row][cell.column - 1]) + + if cell.column < self.columns - 1: # right + surrounding.append(self.grayscale_matrix[cell.row][cell.column + 1]) + + return surrounding + + def get_area(self, cell: Cell, area: list[Cell], total_brightness: int = 0) -> int: + """ + Get a list of all cells surrounded by contours (cells with no brightness). + List is written in 'area'. + Function returns total brightness of all cells in the area. + """ + + if cell.is_accessed == False and cell.value != 0: + # append cell if it hasn't been accessed (avoid repeats) and isn't a contour + area.append(cell) + cell.is_accessed = True + total_brightness += cell.value + + for surrounding in self.surrounding_cells(cell): + total_brightness = self.get_area(surrounding, area, total_brightness) + + return total_brightness + + def avg_brightness(self): + """Calculate average brightness of all areas.""" + + areas: list[tuple[tuple[Cell], int]] = [] + + for row in self.grayscale_matrix: + for cell in row: + if cell.is_accessed == False and cell.value != 0: # redundant + area: list[Cell] = [] + total_brightness: int = self.get_area(cell, area) + + # append area tuple and avrg brightness + areas.append((tuple(area), total_brightness / len(area))) + + # sort new list based on average brightness in descending order + areas.sort(reverse=True, key=lambda x: x[1]) + + # print info + for area_info in areas: + print("-----------------------------------------------------------") + print(f"Cells in area: {len(area_info[0])}") + random_cell: Cell = area_info[0][randint(0, len(area_info[0]) - 1)] + print( + f"Random cell ({random_cell.value}): ({random_cell.row},{random_cell.column})" + ) + print(f"Average brightness: {round(area_info[1], 2)}") + + def __str__(self) -> str: + matrix_str: str = "" + most_number_digits: int = 3 + + for row in self.grayscale_matrix: + for cell in row: + + matrix_str += f" {cell.value}{(most_number_digits - len(str(cell.value)) + 1) * ' '}" + matrix_str += "\n\n\n" + + return matrix_str + + +if __name__ == "__main__": + matrix: Matrix = Matrix(rows=8, columns=8, contour_chance=70) + print(matrix) + matrix.avg_brightness() diff --git a/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py b/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py new file mode 100644 index 0000000..7cf5194 --- /dev/null +++ b/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py @@ -0,0 +1,98 @@ +from inspect import get_annotations # type check function arguments +from sys import ( + version_info, +) # check python version (differt ways to type check annotations) + +""" +Дадена е стълба с N >= 2 стъпала. Стоим в началото на стълбата и можем да качим 1 или 2 стъпала наведнъж. +Да се напише функция, `num_ways` която по подадено N връща броя на начини по които можем да изкачим стълбата - т.е. да стъпим на N-тото стъпало. +Пример: при N = 2 функцията връща 2 - можем да минем по всяко стъпало (начин 1) или да се качим директно на второто (начин 2) +при N=3 функцията връща 3 - можем да качим трите стъпала едно по едно (начин 1), да изкачим 1 стъпало и осналите наведнъж (начин 2) или да изкачим първите 2 стъпала наведнъж и после третото (начин 3) +""" + + +def num_ways(steps: int) -> int: + """ + Function to calculate the amount of different ways a ladder can be climbed to the nth step. + Acts as an error handler. + """ + + # N cannot be less than 2 (according to task requirement) + if steps < 2: + raise ValueError( + f"Invalid input '{steps}'. Amount of steps to climb must be greater than 1." + ) + + if version_info[1] < 10: # get minor python version (for type checking) + if ( + type(steps) != num_ways.__annotations__["steps"] + ): # PEP compliant way to access annotations before 3.10 + raise TypeError(f"Argument 'steps' cannot be of type {type(steps)}.") + else: # 3.10+ + if ( + type(steps) != get_annotations(num_ways)["steps"] + ): # PEP compliant way to access annotatinos (3.10+) + raise TypeError(f"Argument 'steps' cannot be of type {type(steps)}.") + + def calculate_ways(remaining_steps: int, ways_amount: int = 0) -> int: + """Recursively calculate the amount of ways N steps can be climbed.""" + + # exit condition + if ( + remaining_steps < 0 + ): # don't change ways_amount due invalid movement (1 step was remaining but 2 were climbed) + return ways_amount + elif remaining_steps == 0: + return ways_amount + 1 + + # try to climb steps 1 and 2 at a time + ways_amount = calculate_ways(remaining_steps - 1, ways_amount) + ways_amount = calculate_ways(remaining_steps - 2, ways_amount) + + return ways_amount + + return calculate_ways(steps) + + +def fibonacci(n: int) -> int: + """ + Calculate the next fibonacci number. + Could be faster if a precomputed dict of the fibonacci sequence is used. + """ + + # type check + if version_info[1] < 10: # get minor python version (for type checking) + if ( + type(n) != fibonacci.__annotations__["n"] + ): # PEP compliant way to access annotations before 3.10 + raise TypeError(f"Argument 'n' cannot be of type {type(n)}.") + else: # 3.10+ + if ( + type(n) != get_annotations(fibonacci)["n"] + ): # PEP compliant way to access annotations (3.10+) + raise TypeError(f"Argument 'n' cannot be of type {type(n)}.") + + first: int = 0 + second: int = 1 + + if n < 1: + raise ValueError(f"Nth fibonacci number cannot be {n}. Has to be > 1!") + elif n == 1: + return first + elif n == 2: + return second + else: + for i in range(1, n): + result: int = first + second + first = second + second = result + + return second + + +if __name__ == "__main__": + step: int = 5 + print(f"Standard recursion sollution: {num_ways(step)}") + print( + f"Using fibonacci sequence (better performance): {fibonacci(step+1)}" + ) # step+1 because first fibonacci number is 0, essentially callibrating the function diff --git a/homeworks/13_Ivaylo_Genchev/02_replace_collection_items.py b/homeworks/13_Ivaylo_Genchev/02_replace_collection_items.py new file mode 100644 index 0000000..58d2b82 --- /dev/null +++ b/homeworks/13_Ivaylo_Genchev/02_replace_collection_items.py @@ -0,0 +1,55 @@ +""" +Задача 3: +Даден е Python списък от елементи - низове, числа, списъци (вкл. като този), наредени н-торки. Да се напише функция `replace` с 3 аргумента - `list`, `find`, `replace`, където list e писък от типа по-горе, a find и replace са низове. Функцията връща нова версия на `list` в която всяко срещане на `find` e заменено с `replace`. +Пример: +list = [ 'a', 1, [ ['a', 'b'], 1], ([1, 3, 'a'], 'b')] +res = replace(list, 'a', 'c') +print(res) # => [ 'c', 1, [ ['c', 'b'], 1], ([1, 3, 'c'], 'b')] +""" + + +def replace( + collection: list[str | int | list | tuple] | tuple[str | int | list | tuple], + find: str | int, + new: str | int, +) -> list[str | int | list | tuple] | tuple[str | int | list | tuple]: + """Replace all occurances of 'find' in 'collection' with 'new'.""" + + # copy list (preserve original as immutable) + if isinstance(collection, list): + new_collection = collection.copy() + elif isinstance(collection, tuple): + new_collection = list(collection) # make tuple mutable + else: + raise ValueError(f"unsupported type: {type(collection)}") + + # iterate over all elements inside the list + for i, element in enumerate(new_collection): + # replace if element value is equal to find + if element == find: + new_collection[i] = new + # if element is iterable, iterate over it the same way + # since strings are iterable, they could cause infinite recursion + elif hasattr(element, "__iter__") and not isinstance(element, str): + if isinstance(element, tuple) or isinstance(collection, tuple): + # convert element to tuple (if it has been converted to list) + new_collection[i] = tuple(replace(element, find, new)) + else: + new_collection[i] = replace(element, find, new) + + return new_collection + + +if __name__ == "__main__": + collection: list[str | int | list | tuple] = [ + "a", + 1, + [["a", "b"], 1], + ([1, 3, "a"], "b"), + ] + find: str = "a" + new: str = "c" + + res = replace(collection, find, new) + print(f"Unchanged collection: {collection}") + print(f"Having replaced '{find}' with '{new}': {res}") # => [ 'c', 1, [ ['c', 'b'], 1], ([1, 3, 'c'], 'b')] From 8353a196074d8eedc992e1f1f1aaa0a558e6a99b Mon Sep 17 00:00:00 2001 From: Squidfishrl Date: Thu, 13 Jan 2022 01:05:33 +0200 Subject: [PATCH 6/6] refactor comments --- .../02_reach_ladder_position.py | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py b/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py index 7cf5194..1e3a4d6 100644 --- a/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py +++ b/homeworks/13_Ivaylo_Genchev/02_reach_ladder_position.py @@ -1,7 +1,7 @@ from inspect import get_annotations # type check function arguments -from sys import ( - version_info, -) # check python version (differt ways to type check annotations) + +# check python version (differt ways to type check annotations) +from sys import version_info """ Дадена е стълба с N >= 2 стъпала. Стоим в началото на стълбата и можем да качим 1 или 2 стъпала наведнъж. @@ -24,23 +24,20 @@ def num_ways(steps: int) -> int: ) if version_info[1] < 10: # get minor python version (for type checking) - if ( - type(steps) != num_ways.__annotations__["steps"] - ): # PEP compliant way to access annotations before 3.10 + # PEP compliant way to access annotations before 3.10 + if type(steps) != num_ways.__annotations__["steps"]: raise TypeError(f"Argument 'steps' cannot be of type {type(steps)}.") else: # 3.10+ - if ( - type(steps) != get_annotations(num_ways)["steps"] - ): # PEP compliant way to access annotatinos (3.10+) + # PEP compliant way to access annotatinos (3.10+) + if type(steps) != get_annotations(num_ways)["steps"]: raise TypeError(f"Argument 'steps' cannot be of type {type(steps)}.") def calculate_ways(remaining_steps: int, ways_amount: int = 0) -> int: """Recursively calculate the amount of ways N steps can be climbed.""" # exit condition - if ( - remaining_steps < 0 - ): # don't change ways_amount due invalid movement (1 step was remaining but 2 were climbed) + if remaining_steps < 0: + # don't change ways_amount due invalid movement (1 step was remaining but 2 were climbed) return ways_amount elif remaining_steps == 0: return ways_amount + 1 @@ -62,14 +59,12 @@ def fibonacci(n: int) -> int: # type check if version_info[1] < 10: # get minor python version (for type checking) - if ( - type(n) != fibonacci.__annotations__["n"] - ): # PEP compliant way to access annotations before 3.10 + if type(n) != fibonacci.__annotations__["n"]: + # PEP compliant way to access annotations before 3.10 raise TypeError(f"Argument 'n' cannot be of type {type(n)}.") else: # 3.10+ - if ( - type(n) != get_annotations(fibonacci)["n"] - ): # PEP compliant way to access annotations (3.10+) + if type(n) != get_annotations(fibonacci)["n"]: + # PEP compliant way to access annotations (3.10+) raise TypeError(f"Argument 'n' cannot be of type {type(n)}.") first: int = 0