diff --git a/.gitignore b/.gitignore index 4ce6fdd..f9b46c4 100644 --- a/.gitignore +++ b/.gitignore @@ -337,4 +337,8 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb \ No newline at end of file +healthchecksdb + +# visual studio project +*.pyproj +*.sln \ No newline at end of file diff --git a/clean_code/algorithms/__init__.py b/clean_code/algorithms/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/clean_code/algorithms/__init__.py @@ -0,0 +1 @@ + diff --git a/clean_code/algorithms/creation_algorithms.py b/clean_code/algorithms/creation_algorithms.py new file mode 100644 index 0000000..b8d21c5 --- /dev/null +++ b/clean_code/algorithms/creation_algorithms.py @@ -0,0 +1,70 @@ +from math import factorial + +"""This module supplies fast algorithms for pattern-related creations: + all permutations of a staring, duplicate-deletion.""" + +def create_trimmed_string(base_string): + """deletes duplicates in a string. example: aaabbaac -> abac""" + trimmed_elements = [] + trimmed_elements.append(base_string[:1]) + for index in range(1, len(base_string)): + if base_string[index] != base_string[index-1]: + trimmed_elements.append(base_string[index]) + return ''.join(trimmed_elements) + + +def generate_permutations_iterably(elements): + """returns all the permutations of a string. the function is non-recursive""" + permutations = set() + final_set = set() + expectd_permutations_number = factorial(len(elements)) + + new_combinations = _first_cell_permutations(elements) + permutations.update(new_combinations) + + while len(final_set) < expectd_permutations_number: + permutations_copy = permutations.copy() + permutations = set() + + for permutation in permutations_copy: + new_combinations = _first_cell_permutations(permutation) + # the first permutation is the same as "permutation", so there + # is no need to add it again + + permutations.update(new_combinations[1:]) + + final_set.update(permutations) + + return final_set + + +def _first_cell_permutations(base_state): + new_permutations = [] + for index in range(len(base_state)): + splitted_base_state = list(base_state) + _switch_cells(splitted_base_state, 0, index) + new_permutations.append(''.join(splitted_base_state)) + + return new_permutations + + +def _switch_cells(elements, first, second): + elements[first], elements[second] = elements[second], elements[first] + + +def generate_permutations_recursively(base_string): + """returns all the permutations of a string. the function is recursive""" + return _generate_permutations_recursively(list(base_string)) + + +def _generate_permutations_recursively(elements): + permutations = [] + if len(elements) == 1: + return elements + + for permutation in _first_cell_permutations(elements): + for combination in _generate_permutations_recursively(permutation[1:]): + new_permutation = permutation[0] + combination + permutations.append(new_permutation) + + return permutations \ No newline at end of file diff --git a/clean_code/algorithms/pattern_algorithms.py b/clean_code/algorithms/pattern_algorithms.py new file mode 100644 index 0000000..f112c76 --- /dev/null +++ b/clean_code/algorithms/pattern_algorithms.py @@ -0,0 +1,57 @@ +from bisect import insort + + +"""This module supplies fast algorithms for pattern-related searches: + longest palindrome, biggest elements in a list.""" + +def find_n_biggest_numbers(numbers, amount=20): + """return the nth biggest elements in the list""" + if len(numbers) <= amount: + return numbers + biggest_numbers = sorted(numbers[:amount]) + + for number in numbers: + if number > biggest_numbers[0]: + insort(biggest_numbers, number) + del biggest_numbers[0] + + return biggest_numbers + + +def find_longest_palindrome(base_string): + """returns the longest palindrome in a string""" + longest_length = 0 + start_index = 0 + + for index in range(len(base_string) - 1): + for check_even in [True, False]: + current_length = _palindrome_length(base_string, index, check_even) + longest_length, start_index = max((longest_length, start_index), (current_length, index)) + + + return _rebuild_palindrome(base_string, start_index, longest_length) + + +def _palindrome_length(base_string, middle, check_even): + function_offset = 1 if check_even else 0 + + distance_from_middle = 1 + right_end = distance_from_middle + middle + function_offset + left_end = middle - distance_from_middle + + while right_end < len(base_string) and left_end >= 0 and base_string[right_end] == base_string[left_end]: + distance_from_middle += 1 + right_end = distance_from_middle + middle + function_offset + left_end = middle - distance_from_middle + + if not check_even: + return (distance_from_middle * 2) - 1 + return (distance_from_middle * 2) + + +def _rebuild_palindrome(base_string, start_index, length): + begining = start_index - int(length/2) + if length % 2 == 0: + begining += 1 + end = start_index + int(length / 2) + 1 + return base_string[begining:end] \ No newline at end of file diff --git a/clean_code/bfs_search.py b/clean_code/bfs_search.py new file mode 100644 index 0000000..1db2be2 --- /dev/null +++ b/clean_code/bfs_search.py @@ -0,0 +1,65 @@ +from exceptions import BoardIntegrityError + + + +class BFS_search(): + """Implements BFS algorithm to find the shortest path in the maze. + for more inforamtion visit: + https://en.wikipedia.org/wiki/Breadth-first_search.""" + + def __init__(self, graph): + self._graph = graph + self._finish_junction = self._graph.get_finish_junction() + self._start_junction = self._graph.get_start_junction() + + def best_route_in_positions(self): + self._map_best_route() + best_route = self._create_best_route() + positions = self._graph.convert_junctions_to_positions(best_route) + return positions + + def _map_best_route(self): + """adds all nodes connected to the current node to a queue. + repeats the process for every node in the queue + until it reaches the desired node (end of maze)""" + sequence_number = 0 + nodes = [self._start_junction] + + self._start_junction.set_discovered(sequence_number) + + while not len(nodes) == 0: + current_junction = nodes[0] + del nodes[0] + + neighbours = current_junction.get_connections() + for neighbour in neighbours: + if neighbour.is_finish(): + neighbour.set_discovered(sequence_number) + return + + if not neighbour.is_discovered(): + nodes.append(neighbour) + sequence_number += 1 + neighbour.set_discovered(sequence_number) + + + # if the algorithm checked all nodes without finding the finish node + # it means there is no solution + raise BoardIntegrityError("no solution!") + + def _create_best_route(self): + best_route = [] + current_junction = self._finish_junction + + best_route.append(current_junction) + + while not current_junction.is_start(): + next_junction = self._get_smallest_sequence_number_neighbour(current_junction) + best_route.append(next_junction) + current_junction = next_junction + + return best_route + + def _get_smallest_sequence_number_neighbour(self, current_junction): + visited_junctions = filter(lambda connection: connection.is_discovered(), current_junction.get_connections()) + return min(visited_junctions, key=lambda junction: junction.get_sequence_number()) \ No newline at end of file diff --git a/clean_code/cell.py b/clean_code/cell.py new file mode 100644 index 0000000..58dddf5 --- /dev/null +++ b/clean_code/cell.py @@ -0,0 +1,19 @@ +class Cell(): + """Represents a square in the maze that can be a wall or a path.""" + def __init__(self): + self._blocked = False + self._mark = None + + def __str__(self): + if self._blocked: + return 'X' + return self._mark or ' ' + + def set_mark(self, _mark): + self._mark = _mark + + def set_blocked(self): + self._blocked = True + + def is_blocked(self): + return self._blocked \ No newline at end of file diff --git a/clean_code/exceptions.py b/clean_code/exceptions.py new file mode 100644 index 0000000..517240d --- /dev/null +++ b/clean_code/exceptions.py @@ -0,0 +1,3 @@ + +class BoardIntegrityError(Exception): + """An exception to indicate an illegal board""" diff --git a/clean_code/graph.py b/clean_code/graph.py new file mode 100644 index 0000000..65185e8 --- /dev/null +++ b/clean_code/graph.py @@ -0,0 +1,78 @@ +from junction import Junction +from position import Position + + +class Graph(): + """Stores the junction structure in graph form + that a search algorithm can process.""" + def __init__(self, start, finish, maze): + self._start = start + self._finish = finish + self._maze = maze + self._graph = [] + + self._create_graph() + self._update_junctions() + + self._set_start() + self._set_finish() + + def _create_graph(self): + for row in range(self._maze.get_rows()): + self._graph.append([]) + for column in range(self._maze.get_columns()): + if self._maze.get_board()[row][column].is_blocked(): + self._graph[row].append(None) + else: + current_junction = Junction(Position(row, column)) + self._graph[row].append(current_junction) + + def _update_junctions(self): + junctions = filter(None, [node for row in self._graph for node in row]) + for junction in junctions: + position = junction.get_position() + + # by only connecting two at a time, no duplicate calls to + # get_junction_relative_to_position are made + upper_junction = self._get_junction_relative_to_position(position, -1, 0) + left_junction = self._get_junction_relative_to_position(position, 0, -1) + + if upper_junction is not None: + junction.add_connection(upper_junction) + upper_junction.add_connection(junction) + + if left_junction is not None: + junction.add_connection(left_junction) + left_junction.add_connection(junction) + + def _get_junction_relative_to_position(self, position, row_offset=0, column_offset=0): + try: + return self._graph[position.get_row()+row_offset][position.get_column()+column_offset] + except IndexError: + return None + + + def __str__(self): + display = "" + for row in self._graph: + for node in row: + display += str(node) if node else ' ' + display += '\n' + return display + + def _set_start(self): + designated_start = self.get_start_junction() + designated_start.set_is_start() + + def _set_finish(self): + designated_finish = self.get_finish_junction() + designated_finish.set_is_finish() + + def get_finish_junction(self): + return self._get_junction_relative_to_position(self._finish) + + def get_start_junction(self): + return self._get_junction_relative_to_position(self._start) + + def convert_junctions_to_positions(self, junctions): + return [junction.get_position() for junction in junctions] \ No newline at end of file diff --git a/clean_code/junction.py b/clean_code/junction.py new file mode 100644 index 0000000..a669fea --- /dev/null +++ b/clean_code/junction.py @@ -0,0 +1,57 @@ +from position import Position + +class Junction(): + """Represents a path square in the maze. + connected to all the path-squares that are adjacent to it.""" + def __init__(self, position): + self._connections = [] + self._position = position + self._discovered = False + self._finish = False + self._start = False + self._sequence_number = None + + def __str__(self): + if self._finish: + return ' f ' + if self._start: + return ' s ' + return ' ' + str(self._sequence_number) + ' ' + + def get_position(self): + return Position(self._position.get_row(), self._position.get_column()) + + def get_connections(self): + return self._connections + + def get_sequence_number(self): + return self._sequence_number + + def set_postion(self, position): + self._position.set_row(position.get_row()) + self._position.set_column(position.get_column()) + + def set_discovered(self, sequence_number): + self._discovered = True + self._sequence_number = sequence_number + + def set_is_finish(self): + self._finish = True + + def set_is_start(self): + self._start = True + + def has_connections(self): + return len(self._connections) > 0 + + def add_connection(self, connection): + self._connections.append(connection) + + def is_start(self): + return self._start + + def is_finish(self): + return self._finish + + def is_discovered(self): + return self._discovered \ No newline at end of file diff --git a/clean_code/maze.py b/clean_code/maze.py new file mode 100644 index 0000000..561149e --- /dev/null +++ b/clean_code/maze.py @@ -0,0 +1,74 @@ +from cell import Cell +from exceptions import BoardIntegrityError +from functools import reduce +from numpy import reshape + + +class Maze(): + """User-printable maze (walls and paths). + converts user input into a maze.""" + + WALL_INDICATOR = '1' + PATH_MARK = '+' + + def __init__(self, cells_per_row, maze_raw_form): + self._board = [] + self._columns = cells_per_row + self._rows = int(len(maze_raw_form) / self._columns) + self._maze_raw_form = maze_raw_form + + self._create_board() + self._validate_maze_is_legal() + + def __str__(self): + display = "" + for row in self._board: + for cell in row: + display += str(cell) + display += "\n" + return display + + def get_rows(self): + return self._rows + + def get_columns(self): + return self._columns + + def get_board(self): + return self._board + + def _create_board(self): + cells = self._create_cells() + self._board = reshape(cells, (self._rows, self._columns)) + + def _create_cells(self): + cells = [] + for indicator in self._maze_raw_form: + current_cell = Cell() + if indicator == Maze.WALL_INDICATOR: + current_cell.set_blocked() + cells.append(current_cell) + + return cells + + def _validate_maze_is_legal(self): + first_row = self._check_row_consistancy(0) + last_row = self._check_row_consistancy(self._rows - 1) + left_column = self._check_column_consistancy(0) + right_column = self._check_column_consistancy(self._columns - 1) + + if not(first_row and last_row and left_column and right_column): + raise BoardIntegrityError("not all borders are blocked") + + + def _check_row_consistancy(self, row): + return reduce(lambda x, y: x and y, map(lambda cell: cell.is_blocked(), self._board[row])) + + + + def _check_column_consistancy(self, column): + return reduce(lambda x, y: x and y, map(lambda row: row[column].is_blocked(), self._board)) + + def mark_cells_at_positions(self, positions): + for position in positions: + self._board[position.get_row()][position.get_column()].set_mark(Maze.PATH_MARK) \ No newline at end of file diff --git a/clean_code/missing_number.py b/clean_code/missing_number.py new file mode 100644 index 0000000..c9f048c --- /dev/null +++ b/clean_code/missing_number.py @@ -0,0 +1,15 @@ + +"""Recieves a list of consecutive numbers when only one is missing. + returns the missing number by summing-up the 'complete' arithmetic + progress and subtracting from it the sum of the given list. + the difference is the deleted number""" + +def sum_arithmetic_progress(number_of_elements, difference=1, first_element=0): + total = (number_of_elements * (2 * first_element + difference * (number_of_elements - 1))) / 2 + return total + + +def find_missing_number(numbers): + arithmetic_progress_sum = sum_arithmetic_progress(len(numbers)+1) + numbers_sum = sum(numbers) + return int(arithmetic_progress_sum - numbers_sum) \ No newline at end of file diff --git a/clean_code/position.py b/clean_code/position.py new file mode 100644 index 0000000..249b35a --- /dev/null +++ b/clean_code/position.py @@ -0,0 +1,24 @@ + +class Position(): + """X and Y index of a node in the maze matrix.""" + def __init__(self, row, column): + self._row = row + self._column = column + + def __str__(self): + return "[{0}, {1}]".format(self._row, self._column) + + def __eq__(self, position): + return self._row == position.get_row() and self._column == position.get_column() + + def get_row(self): + return self._row + + def get_column(self): + return self._column + + def set_row(self, row): + self._row = row + + def set_column(self, column): + self._column = column \ No newline at end of file diff --git a/clean_code/test_algorithms.py b/clean_code/test_algorithms.py new file mode 100644 index 0000000..31bdde3 --- /dev/null +++ b/clean_code/test_algorithms.py @@ -0,0 +1,47 @@ +import algorithms.creation_algorithms as creation_algorithms +import algorithms.pattern_algorithms as pattern_algorithms +from random import choice, randint +from string import digits +from time import time + +def random_string_of_size(n): + return ''.join(choice(digits) for _ in range(n)) + + +def random_binary_string_of_size(n): + return ''.join(choice(['0', '1']) for _ in range(n)) + + +def random_int_list_of_size(n): + return list(map(lambda _: randint(0, n), range(n))) + + +def is_unique(elements): + return len(elements) == len(set(elements)) + + +def run_and_check_time(function, *args, **kwargs): + start = time() + function_return_value = function(*args, ** kwargs) + elapsed = time() - start + print("{} took {} seconds".format(function.__name__, elapsed)) + return function_return_value + + +def main(): + random_string = random_string_of_size(1000000) + random_numbers = random_int_list_of_size(1000000) + random_binary_string = random_binary_string_of_size(1000000) + small_random_string = random_string_of_size(8) + while not is_unique(small_random_string): + small_random_string = random_string_of_size(8) + + run_and_check_time(creation_algorithms.generate_permutations_recursively, small_random_string) + run_and_check_time(creation_algorithms.generate_permutations_iterably, small_random_string) + run_and_check_time(creation_algorithms.create_trimmed_string, random_string) + run_and_check_time(pattern_algorithms.find_n_biggest_numbers, random_numbers) + run_and_check_time(pattern_algorithms.find_longest_palindrome, random_binary_string) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/clean_code/test_maze.py b/clean_code/test_maze.py new file mode 100644 index 0000000..472568f --- /dev/null +++ b/clean_code/test_maze.py @@ -0,0 +1,30 @@ +from maze import Maze +from position import Position +from graph import Graph +from bfs_search import BFS_search + +def main(): + # 1 = wall, 0 = empty + maze_structure = ("1111111111" + "1010001001" + "1000100101" + "1010001011" + "1000100001" + "1111111111") + + maze = Maze(10, maze_structure) + print(maze) + + start = Position(1, 1) + finish = Position(4, 8) + board_as_graph = Graph(start, finish, maze) + + find = BFS_search(board_as_graph) + positions = find.best_route_in_positions() + maze.mark_cells_at_positions(positions) + + print(maze) + print("number of steps needed: ", len(positions), "\n") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/clean_code/test_missing_number.py b/clean_code/test_missing_number.py new file mode 100644 index 0000000..a32d126 --- /dev/null +++ b/clean_code/test_missing_number.py @@ -0,0 +1,34 @@ +from missing_number import find_missing_number +from random import shuffle, choice + +"""Gets a number from the user, creates a list of length Number with the numbers + 0-Number and then randomly removes a number. then finds which number + was removed.""" + +def get_number_from_user(): + number = input("enter a whole number to be used as generator of a list 0-number: ") + try: + number = int(number) + except ValueError: + raise ValueError("input must be an integer") + if number < 0: + raise ValueError("number must be greater or equal to 0") + + return number + + +def create_list_with_missing_number(max_value): + numbers = list(range(max_value+1)) + shuffle(numbers) + numbers.remove(choice(numbers)) + return numbers + + +def main(): + chosen_number = get_number_from_user() + numbers = create_list_with_missing_number(chosen_number) + deleted_number = find_missing_number(numbers) + print(deleted_number) + +if __name__ == "__main__": + main() \ No newline at end of file