From 8e5143deefe9a51e2a150b36028a735ee9bc172c Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Mon, 19 Sep 2022 20:47:00 +0300 Subject: [PATCH 01/41] Completed lab-00 --- homework00/hello.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework00/hello.py b/homework00/hello.py index efe8767..5802c29 100644 --- a/homework00/hello.py +++ b/homework00/hello.py @@ -1,5 +1,5 @@ def get_greeting(name: str) -> str: - pass + return f"Hello, {name}!" if __name__ == "__main__": From a94006e0f811127259ad1dd2d37adb9c8486166e Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sun, 2 Oct 2022 13:01:55 +0300 Subject: [PATCH 02/41] Completed lab1 --- homework01/caesar.py | 45 +++++++++++------------------------ homework01/rsa.py | 53 +++++++++++++++++------------------------- homework01/vigenere.py | 47 +++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 90 deletions(-) diff --git a/homework01/caesar.py b/homework01/caesar.py index 09c3681..38bc9c9 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -1,46 +1,27 @@ import typing as tp +from string import ascii_lowercase as lower, ascii_uppercase as upper def encrypt_caesar(plaintext: str, shift: int = 3) -> str: - """ - Encrypts plaintext using a Caesar cipher. - - >>> encrypt_caesar("PYTHON") - 'SBWKRQ' - >>> encrypt_caesar("python") - 'sbwkrq' - >>> encrypt_caesar("Python3.6") - 'Sbwkrq3.6' - >>> encrypt_caesar("") - '' - """ ciphertext = "" - # PUT YOUR CODE HERE + for char in plaintext: + if char.isalpha(): + if char.islower(): + char = lower[(lower.index(char) + shift) % 26] + else: + char = upper[(upper.index(char) + shift) % 26] + ciphertext += char return ciphertext def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: - """ - Decrypts a ciphertext using a Caesar cipher. - - >>> decrypt_caesar("SBWKRQ") - 'PYTHON' - >>> decrypt_caesar("sbwkrq") - 'python' - >>> decrypt_caesar("Sbwkrq3.6") - 'Python3.6' - >>> decrypt_caesar("") - '' - """ - plaintext = "" - # PUT YOUR CODE HERE - return plaintext + return encrypt_caesar(ciphertext, -shift) def caesar_breaker_brute_force(ciphertext: str, dictionary: tp.Set[str]) -> int: - """ - Brute force breaking a Caesar cipher. - """ best_shift = 0 - # PUT YOUR CODE HERE + for shift in range(26): + decrypted_msg = decrypt_caesar(ciphertext, shift) + if decrypted_msg in dictionary: + best_shift = shift return best_shift diff --git a/homework01/rsa.py b/homework01/rsa.py index b777be5..75a3a88 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -3,43 +3,32 @@ def is_prime(n: int) -> bool: - """ - Tests to see if a number is prime. - - >>> is_prime(2) - True - >>> is_prime(11) - True - >>> is_prime(8) - False - """ - # PUT YOUR CODE HERE - pass + for div in range(2, int(n**0.5) + 1): + if n % div == 0: + return False + return n != 1 def gcd(a: int, b: int) -> int: - """ - Euclid's algorithm for determining the greatest common divisor. + if b == 0: + return a + return gcd(b, a % b) - >>> gcd(12, 15) - 3 - >>> gcd(3, 7) - 1 - """ - # PUT YOUR CODE HERE - pass +def gcdex(a, b): + if b == 0: + return a, 1, 0 + else: + d, x, y = gcdex(b, a % b) + return d, y, x - y * (a // b) -def multiplicative_inverse(e: int, phi: int) -> int: - """ - Euclid's extended algorithm for finding the multiplicative - inverse of two numbers. - >>> multiplicative_inverse(7, 40) - 23 - """ - # PUT YOUR CODE HERE - pass +def multiplicative_inverse(a: int, n: int) -> int: + d, x, y = gcdex(a, n) + if d == 1: + return (x % n + n) % n + else: + return 0 def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[int, int]]: @@ -49,10 +38,10 @@ def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[in raise ValueError("p and q cannot be equal") # n = pq - # PUT YOUR CODE HERE + n = p * q # phi = (p-1)(q-1) - # PUT YOUR CODE HERE + phi = (p - 1) * (q - 1) # Choose an integer e such that e and phi(n) are coprime e = random.randrange(1, phi) diff --git a/homework01/vigenere.py b/homework01/vigenere.py index e51742e..e0d8da3 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -1,30 +1,25 @@ -def encrypt_vigenere(plaintext: str, keyword: str) -> str: - """ - Encrypts plaintext using a Vigenere cipher. +from string import ascii_lowercase as lower - >>> encrypt_vigenere("PYTHON", "A") - 'PYTHON' - >>> encrypt_vigenere("python", "a") - 'python' - >>> encrypt_vigenere("ATTACKATDAWN", "LEMON") - 'LXFOPVEFRNHR' - """ - ciphertext = "" - # PUT YOUR CODE HERE - return ciphertext +table = [[lower[(lower.index(char) + shift) % 26] for char in lower] for shift in range(26)] -def decrypt_vigenere(ciphertext: str, keyword: str) -> str: - """ - Decrypts a ciphertext using a Vigenere cipher. - >>> decrypt_vigenere("PYTHON", "A") - 'PYTHON' - >>> decrypt_vigenere("python", "a") - 'python' - >>> decrypt_vigenere("LXFOPVEFRNHR", "LEMON") - 'ATTACKATDAWN' - """ - plaintext = "" - # PUT YOUR CODE HERE - return plaintext +def encrypt_vigenere(plaintext: str, keyword: str, encode: bool = True) -> str: + ciphertext = [] + for index, char in enumerate(plaintext.lower()): + if char.isalpha(): + keyword_char = keyword[index % len(keyword)].lower() + keyword_char_index = lower.index(keyword_char) + if encode: + char = table[keyword_char_index][lower.index(char)] + else: + table_char_index = table[keyword_char_index].index(char) + char = lower[table_char_index] + if plaintext[index].isupper(): + char = char.upper() + ciphertext.append(char) + return "".join(ciphertext) + + +def decrypt_vigenere(ciphertext: str, keyword: str) -> str: + return encrypt_vigenere(ciphertext, keyword, encode=False) From eb692496ee3de42c9885b39a245179589e1c8479 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sun, 2 Oct 2022 13:06:40 +0300 Subject: [PATCH 03/41] Completed lab-01 --- homework01/caesar.py | 3 ++- homework01/vigenere.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homework01/caesar.py b/homework01/caesar.py index 38bc9c9..381e7ba 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -1,5 +1,6 @@ +from string import ascii_lowercase as lower +from string import ascii_uppercase as upper import typing as tp -from string import ascii_lowercase as lower, ascii_uppercase as upper def encrypt_caesar(plaintext: str, shift: int = 3) -> str: diff --git a/homework01/vigenere.py b/homework01/vigenere.py index e0d8da3..5251dcf 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -1,7 +1,8 @@ from string import ascii_lowercase as lower -table = [[lower[(lower.index(char) + shift) % 26] for char in lower] for shift in range(26)] +table = [[lower[(lower.index(char) + shift) % 26] + for char in lower] for shift in range(26)] def encrypt_vigenere(plaintext: str, keyword: str, encode: bool = True) -> str: From 2a5dc50874c8d73ab76058fd53f2288b61a8a455 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sun, 2 Oct 2022 13:09:32 +0300 Subject: [PATCH 04/41] Completed lab-01 --- homework01/caesar.py | 2 +- homework01/vigenere.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/homework01/caesar.py b/homework01/caesar.py index 381e7ba..7b05368 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -1,6 +1,6 @@ +import typing as tp from string import ascii_lowercase as lower from string import ascii_uppercase as upper -import typing as tp def encrypt_caesar(plaintext: str, shift: int = 3) -> str: diff --git a/homework01/vigenere.py b/homework01/vigenere.py index 5251dcf..3ffb062 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -1,6 +1,5 @@ from string import ascii_lowercase as lower - table = [[lower[(lower.index(char) + shift) % 26] for char in lower] for shift in range(26)] From 3c2ac2e3def284bbee6049e3ebfad72dc11844a3 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sun, 2 Oct 2022 13:11:57 +0300 Subject: [PATCH 05/41] Completed lab-01 --- homework01/rsa.py | 4 ++-- homework01/vigenere.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homework01/rsa.py b/homework01/rsa.py index 75a3a88..2378b58 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -65,7 +65,7 @@ def encrypt(pk: tp.Tuple[int, int], plaintext: str) -> tp.List[int]: key, n = pk # Convert each letter in the plaintext to numbers based on # the character using a^b mod m - cipher = [(ord(char) ** key) % n for char in plaintext] + cipher = [(ord(char)**key) % n for char in plaintext] # Return the array of bytes return cipher @@ -74,7 +74,7 @@ def decrypt(pk: tp.Tuple[int, int], ciphertext: tp.List[int]) -> str: # Unpack the key into its components key, n = pk # Generate the plaintext based on the ciphertext and key using a^b mod m - plain = [chr((char ** key) % n) for char in ciphertext] + plain = [chr((char**key) % n) for char in ciphertext] # Return the array of bytes as a string return "".join(plain) diff --git a/homework01/vigenere.py b/homework01/vigenere.py index 3ffb062..4502160 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -1,7 +1,6 @@ from string import ascii_lowercase as lower -table = [[lower[(lower.index(char) + shift) % 26] - for char in lower] for shift in range(26)] +table = [[lower[(lower.index(char) + shift) % 26] for char in lower] for shift in range(26)] def encrypt_vigenere(plaintext: str, keyword: str, encode: bool = True) -> str: From b768a8055ea7367ba5b8eac46a0270a13cc68eaa Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sun, 2 Oct 2022 13:14:35 +0300 Subject: [PATCH 06/41] Completed lab-01 --- homework01/rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework01/rsa.py b/homework01/rsa.py index 2378b58..1d56554 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -65,7 +65,7 @@ def encrypt(pk: tp.Tuple[int, int], plaintext: str) -> tp.List[int]: key, n = pk # Convert each letter in the plaintext to numbers based on # the character using a^b mod m - cipher = [(ord(char)**key) % n for char in plaintext] + cipher = [(ord(char) ** key) % n for char in plaintext] # Return the array of bytes return cipher From a4dc7ad74807824cc82aaee152d4f833ff099a0a Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 5 Oct 2022 16:12:31 +0300 Subject: [PATCH 07/41] Completed lab-01 --- homework01/vigenere.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/homework01/vigenere.py b/homework01/vigenere.py index 4502160..d0086b3 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -1,22 +1,16 @@ -from string import ascii_lowercase as lower - -table = [[lower[(lower.index(char) + shift) % 26] for char in lower] for shift in range(26)] +from caesar import encrypt_caesar, decrypt_caesar def encrypt_vigenere(plaintext: str, keyword: str, encode: bool = True) -> str: ciphertext = [] for index, char in enumerate(plaintext.lower()): - if char.isalpha(): - keyword_char = keyword[index % len(keyword)].lower() - keyword_char_index = lower.index(keyword_char) - if encode: - char = table[keyword_char_index][lower.index(char)] - else: - table_char_index = table[keyword_char_index].index(char) - char = lower[table_char_index] - if plaintext[index].isupper(): - char = char.upper() - ciphertext.append(char) + shift = ord(keyword[index % len(keyword)].lower()) - 97 + if encode: + ciphertext.append(encrypt_caesar(char, shift)) + else: + ciphertext.append(decrypt_caesar(char, shift)) + if plaintext[index].isupper(): + ciphertext[-1] = ciphertext[-1].upper() return "".join(ciphertext) From ccc77a8c0ac82ed4c6b14a6664ca33a42988cccf Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 5 Oct 2022 16:14:17 +0300 Subject: [PATCH 08/41] Completed lab-01 --- homework01/vigenere.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework01/vigenere.py b/homework01/vigenere.py index d0086b3..1cbb09d 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -1,4 +1,4 @@ -from caesar import encrypt_caesar, decrypt_caesar +from caesar import decrypt_caesar, encrypt_caesar def encrypt_vigenere(plaintext: str, keyword: str, encode: bool = True) -> str: From 6314f6643ba7fe19b838510d1642f34069e8e85c Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 12 Oct 2022 19:56:43 +0300 Subject: [PATCH 09/41] Done --- homework02/sudoku.py | 148 ++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 80 deletions(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index df78ab1..8bc3f2d 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -1,11 +1,12 @@ import pathlib +from random import randint import typing as tp T = tp.TypeVar("T") def read_sudoku(path: tp.Union[str, pathlib.Path]) -> tp.List[tp.List[str]]: - """ Прочитать Судоку из указанного файла """ + """ Прочитать Судоку из указанного файла""" path = pathlib.Path(path) with path.open() as f: puzzle = f.read() @@ -19,7 +20,7 @@ def create_grid(puzzle: str) -> tp.List[tp.List[str]]: def display(grid: tp.List[tp.List[str]]) -> None: - """Вывод Судоку """ + """Вывод Судоку""" width = 2 line = "+".join(["-" * (width * 3)] * 3) for row in range(9): @@ -35,103 +36,79 @@ def display(grid: tp.List[tp.List[str]]) -> None: def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]: """ - Сгруппировать значения values в список, состоящий из списков по n элементов - - >>> group([1,2,3,4], 2) - [[1, 2], [3, 4]] - >>> group([1,2,3,4,5,6,7,8,9], 3) - [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - """ - pass + Сгруппировать значения values в список, состоящий из списков по n элементов""" + res = [] + tmp = [] + for value in values: + tmp.append(value) + if len(tmp) == n: + res.append(tmp) + tmp = [] + return res def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - """Возвращает все значения для номера строки, указанной в pos - - >>> get_row([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) - ['1', '2', '.'] - >>> get_row([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (1, 0)) - ['4', '.', '6'] - >>> get_row([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (2, 0)) - ['.', '8', '9'] - """ - pass + """Возвращает все значения для номера строки, указанной в pos""" + return grid[pos[0]] def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - """Возвращает все значения для номера столбца, указанного в pos - - >>> get_col([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) - ['1', '4', '7'] - >>> get_col([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (0, 1)) - ['2', '.', '8'] - >>> get_col([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (0, 2)) - ['3', '6', '9'] - """ - pass + """Возвращает все значения для номера столбца, указанного в pos""" + return [row[pos[1]] for row in grid] def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - """Возвращает все значения из квадрата, в который попадает позиция pos - - >>> grid = read_sudoku('puzzle1.txt') - >>> get_block(grid, (0, 1)) - ['5', '3', '.', '6', '.', '.', '.', '9', '8'] - >>> get_block(grid, (4, 7)) - ['.', '.', '3', '.', '.', '1', '.', '.', '6'] - >>> get_block(grid, (8, 8)) - ['2', '8', '.', '.', '.', '5', '.', '7', '9'] - """ - pass + """Возвращает все значения из квадрата, в который попадает позиция pos""" + res = [] + box_x = pos[1] // 3 + box_y = pos[0] // 3 + for i in range(box_y * 3, box_y * 3 + 3): + for j in range(box_x * 3, box_x * 3 + 3): + res.append(grid[i][j]) + return res def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[int, int]]: - """Найти первую свободную позицию в пазле - - >>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']]) - (0, 2) - >>> find_empty_positions([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']]) - (1, 1) - >>> find_empty_positions([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']]) - (2, 0) - """ - pass + """Найти первую свободную позицию в пазле""" + for row in range(len(grid)): + for col in range(len(grid[0])): + if grid[row][col] == ".": + return row, col def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: - """Вернуть множество возможных значения для указанной позиции - - >>> grid = read_sudoku('puzzle1.txt') - >>> values = find_possible_values(grid, (0,2)) - >>> values == {'1', '2', '4'} - True - >>> values = find_possible_values(grid, (4,7)) - >>> values == {'2', '5', '9'} - True - """ - pass + """Вернуть множество возможных значения для указанной позиции""" + row_values = set(get_row(grid, pos)) - {"."} + col_values = set(get_col(grid, pos)) - {"."} + block_values = set(get_block(grid, pos)) - {"."} + return set(map(str, range(1, len(grid) + 1))) - row_values - col_values - block_values def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: """ Решение пазла, заданного в grid """ - """ Как решать Судоку? - 1. Найти свободную позицию - 2. Найти все возможные значения, которые могут находиться на этой позиции - 3. Для каждого возможного значения: - 3.1. Поместить это значение на эту позицию - 3.2. Продолжить решать оставшуюся часть пазла - - >>> grid = read_sudoku('puzzle1.txt') - >>> solve(grid) - [['5', '3', '4', '6', '7', '8', '9', '1', '2'], ['6', '7', '2', '1', '9', '5', '3', '4', '8'], ['1', '9', '8', '3', '4', '2', '5', '6', '7'], ['8', '5', '9', '7', '6', '1', '4', '2', '3'], ['4', '2', '6', '8', '5', '3', '7', '9', '1'], ['7', '1', '3', '9', '2', '4', '8', '5', '6'], ['9', '6', '1', '5', '3', '7', '2', '8', '4'], ['2', '8', '7', '4', '1', '9', '6', '3', '5'], ['3', '4', '5', '2', '8', '6', '1', '7', '9']] - """ - pass - - -def check_solution(solution: tp.List[tp.List[str]]) -> bool: + pos = find_empty_positions(grid) + if not pos: + return grid + else: + for value in find_possible_values(grid, pos): + grid[pos[0]][pos[1]] = value + if solve(grid): + return grid + grid[pos[0]][pos[1]] = "." + + +def check_solution(grid: tp.List[tp.List[str]]) -> bool: """ Если решение solution верно, то вернуть True, в противном случае False """ - # TODO: Add doctests with bad puzzles - pass + length = len(grid) + flag1 = True + flag2 = True + flag3 = True + for i in range(length): + flag1 = flag1 and len(set(grid[i]) - {"."}) == length + flag2 = flag2 and len(set(grid[j][i] for j in range(length)) - {"."}) == length + for j in range(length): + flag3 = flag3 and len(set(get_block(grid, (i, j))) - {"."}) == length + return flag1 and flag2 and flag3 def generate_sudoku(N: int) -> tp.List[tp.List[str]]: @@ -156,12 +133,23 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: >>> check_solution(solution) True """ - pass + grid = [["."] * 9 for _ in range(9)] + solve(grid) + for _ in range(81 - N): + while True: + row = randint(0, 8) + col = randint(0, 8) + if grid[row][col] != ".": + break + grid[row][col] = "." + return grid if __name__ == "__main__": for fname in ["puzzle1.txt", "puzzle2.txt", "puzzle3.txt"]: grid = read_sudoku(fname) + print(find_possible_values(grid, (0, 2))) + break display(grid) solution = solve(grid) if not solution: From e1ed86d20e01866cc8d56af6cb07ba5fbaa10ad4 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 12 Oct 2022 19:59:44 +0300 Subject: [PATCH 10/41] Done --- homework02/sudoku.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index 8bc3f2d..46f2056 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -1,6 +1,6 @@ import pathlib -from random import randint import typing as tp +from random import randint T = tp.TypeVar("T") From 4e33aeb9ff30c7f9c21e52e765f8e988d23d5d3f Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 12 Oct 2022 20:02:00 +0300 Subject: [PATCH 11/41] Done --- homework02/sudoku.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index 46f2056..00f2d26 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -35,8 +35,7 @@ def display(grid: tp.List[tp.List[str]]) -> None: def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]: - """ - Сгруппировать значения values в список, состоящий из списков по n элементов""" + """Сгруппировать значения values в список, состоящий из списков по n элементов""" res = [] tmp = [] for value in values: @@ -85,7 +84,7 @@ def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) - def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: - """ Решение пазла, заданного в grid """ + """Решение пазла, заданного в grid""" pos = find_empty_positions(grid) if not pos: return grid @@ -98,7 +97,7 @@ def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: def check_solution(grid: tp.List[tp.List[str]]) -> bool: - """ Если решение solution верно, то вернуть True, в противном случае False """ + """Если решение solution верно, то вернуть True, в противном случае False""" length = len(grid) flag1 = True flag2 = True From 8874973af5de5041455a0e811dc798cf1153e607 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 12 Oct 2022 20:03:41 +0300 Subject: [PATCH 12/41] Done --- homework02/sudoku.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index 00f2d26..833aa4d 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -6,7 +6,7 @@ def read_sudoku(path: tp.Union[str, pathlib.Path]) -> tp.List[tp.List[str]]: - """ Прочитать Судоку из указанного файла""" + """Прочитать Судоку из указанного файла""" path = pathlib.Path(path) with path.open() as f: puzzle = f.read() @@ -111,27 +111,7 @@ def check_solution(grid: tp.List[tp.List[str]]) -> bool: def generate_sudoku(N: int) -> tp.List[tp.List[str]]: - """Генерация судоку заполненного на N элементов - - >>> grid = generate_sudoku(40) - >>> sum(1 for row in grid for e in row if e == '.') - 41 - >>> solution = solve(grid) - >>> check_solution(solution) - True - >>> grid = generate_sudoku(1000) - >>> sum(1 for row in grid for e in row if e == '.') - 0 - >>> solution = solve(grid) - >>> check_solution(solution) - True - >>> grid = generate_sudoku(0) - >>> sum(1 for row in grid for e in row if e == '.') - 81 - >>> solution = solve(grid) - >>> check_solution(solution) - True - """ + """Генерация судоку заполненного на N элементов""" grid = [["."] * 9 for _ in range(9)] solve(grid) for _ in range(81 - N): From a846b3c72575e4c4e66a10be9e2f2e4c5b35b782 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 12 Oct 2022 20:11:59 +0300 Subject: [PATCH 13/41] Done --- homework02/sudoku.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index 833aa4d..8503b7d 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -73,6 +73,7 @@ def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[in for col in range(len(grid[0])): if grid[row][col] == ".": return row, col + return def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: @@ -94,6 +95,7 @@ def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: if solve(grid): return grid grid[pos[0]][pos[1]] = "." + return def check_solution(grid: tp.List[tp.List[str]]) -> bool: From a115a3949de7d87d86dd2b3ccfcc99e7d3aecf51 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 12 Oct 2022 20:13:48 +0300 Subject: [PATCH 14/41] Done --- homework02/sudoku.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index 8503b7d..a56f666 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -73,7 +73,7 @@ def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[in for col in range(len(grid[0])): if grid[row][col] == ".": return row, col - return + return None def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: @@ -95,7 +95,7 @@ def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: if solve(grid): return grid grid[pos[0]][pos[1]] = "." - return + return None def check_solution(grid: tp.List[tp.List[str]]) -> bool: From 5e1a5848350c15e2cfa3ab1099ef62dd2597b683 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sat, 29 Oct 2022 21:10:27 +0300 Subject: [PATCH 15/41] Done --- homework03/life.py | 41 +++++++++++++-------- homework03/life_console.py | 39 ++++++++++++++++---- homework03/life_gui.py | 73 +++++++++++++++++++++++++++++++++----- homework03/life_proto.py | 58 ++++++++++++++++++++---------- 4 files changed, 164 insertions(+), 47 deletions(-) diff --git a/homework03/life.py b/homework03/life.py index 7aef0b6..3a3f9d2 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -4,13 +4,14 @@ import pygame from pygame.locals import * +from life_proto import GameOfLife as GameOfLifeProto Cell = tp.Tuple[int, int] Cells = tp.List[int] Grid = tp.List[Cells] -class GameOfLife: +class GameOfLife(GameOfLifeProto): def __init__( self, size: tp.Tuple[int, int], @@ -28,47 +29,59 @@ def __init__( # Текущее число поколений self.generations = 1 - def create_grid(self, randomize: bool = False) -> Grid: - # Copy from previous assignment - pass - def get_neighbours(self, cell: Cell) -> Cells: - # Copy from previous assignment - pass + return super().get_neighbours(cell, self.curr_generation) def get_next_generation(self) -> Grid: - # Copy from previous assignment - pass + return super().get_next_generation(self.curr_generation) def step(self) -> None: """ Выполнить один шаг игры. """ - pass + self.prev_generation = self.curr_generation + self.curr_generation = self.get_next_generation() + self.generations += 1 + + def update(self, rows: int, cols: int, max_gen: int) -> None: + self.rows = rows + self.cols = cols + self.max_generation = max_gen + self.curr_generation = self.create_grid(randomize=True) + + def is_cell_alive(self, row: int, col: int) -> bool: + return bool(self.curr_generation[row][col]) + + def switch_cell_status(self, row: int, col: int) -> None: + self.curr_generation[row][col] = ( + self.curr_generation[row][col] + 1) % 2 @property def is_max_generations_exceeded(self) -> bool: """ Не превысило ли текущее число поколений максимально допустимое. """ - pass + return self.generations >= self.max_generations @property def is_changing(self) -> bool: """ Изменилось ли состояние клеток с предыдущего шага. """ - pass + return self.curr_generation != self.prev_generation @staticmethod def from_file(filename: pathlib.Path) -> "GameOfLife": """ Прочитать состояние клеток из указанного файла. """ - pass + with open(filename, encoding="u8") as fi: + self.current_generation = [list(map(int, line)) for line in fi] def save(self, filename: pathlib.Path) -> None: """ Сохранить текущее состояние клеток в указанный файл. """ - pass + with open(filename, "w", encoding="u8") as fo: + for row in self.current_generation: + print(*row, sep="", file=fo) diff --git a/homework03/life_console.py b/homework03/life_console.py index ddeb9ef..f373f5b 100644 --- a/homework03/life_console.py +++ b/homework03/life_console.py @@ -1,4 +1,6 @@ +import argparse import curses +import time from life import GameOfLife from ui import UI @@ -7,16 +9,39 @@ class Console(UI): def __init__(self, life: GameOfLife) -> None: super().__init__(life) + self.from_cmd_args() - def draw_borders(self, screen) -> None: - """ Отобразить рамку. """ - pass - - def draw_grid(self, screen) -> None: + def draw(self, screen) -> None: """ Отобразить состояние клеток. """ - pass + screen.addstr(f"+{'-' * self.life.cols}+\n") + for row in self.life.curr_generation: + screen.addch("|") + for el in row: + screen.addch("*" if el else " ") + screen.addstr("|\n") + screen.addstr(f"+{'-' * self.life.cols}+\n") def run(self) -> None: screen = curses.initscr() - # PUT YOUR CODE HERE + while True: + screen.clear() + self.draw(screen) + screen.refresh() + self.life.step() + time.sleep(0.2) curses.endwin() + + def from_cmd_args(self): + parser = argparse.ArgumentParser() + parser.add_argument("--rows", type=int) + parser.add_argument("--cols", type=int) + parser.add_argument("--max-generations", type=int) + args = parser.parse_args() + if args.rows and args.cols and args.max_generations: + self.life.update(args.rows, args.cols, args.max_generations) + + +if __name__ == '__main__': + life = GameOfLife((24, 80), max_generations=50) + c = Console(life) + c.run() diff --git a/homework03/life_gui.py b/homework03/life_gui.py index 1126b29..81f695a 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -7,15 +7,72 @@ class GUI(UI): def __init__(self, life: GameOfLife, cell_size: int = 10, speed: int = 10) -> None: super().__init__(life) + self.cell_size = cell_size + self.speed = speed + self.border_width = 1 + self.screen_size = ( + self.life.cols * cell_size + self.border_width, + self.life.rows * cell_size + self.border_width + ) + self.display = pygame.display.set_mode(self.screen_size) + self.clock = pygame.time.Clock() + self.is_paused = False + pygame.display.set_caption("Game of Life") - def draw_lines(self) -> None: - # Copy from previous assignment - pass + def draw_lines(self, win) -> None: + # vertical + for col in range(self.life.cols + 1): + pygame.draw.line(win, "black", (col * self.cell_size, 0), + (col * self.cell_size, self.screen_size[1]), self.border_width) + # horizontal + for row in range(self.life.rows + 1): + pygame.draw.line(win, "black", (0, row * self.cell_size), + (self.screen_size[0], row * self.cell_size), self.border_width) - def draw_grid(self) -> None: - # Copy from previous assignment - pass + def draw_grid(self, win) -> None: + for row in range(self.life.rows): + for col in range(self.life.cols): + if self.life.is_cell_alive(row, col): + pygame.draw.rect(win, "green", (col * self.cell_size, + row * self.cell_size, + self.cell_size, + self.cell_size)) + + def process_click(self, pos: tuple[int, int]) -> None: + col = pos[0] // self.cell_size + row = pos[1] // self.cell_size + self.life.switch_cell_status(row, col) + + def redraw(self, win) -> None: + win.fill("white") + self.draw_grid(win) + self.draw_lines(win) + + def handle_events(self) -> None: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + exit() + + if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: + self.is_paused = not self.is_paused + + if event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1 and self.is_paused: + self.process_click(event.pos) def run(self) -> None: - # Copy from previous assignment - pass + while True: + self.handle_events() + self.redraw(self.display) + if not self.is_paused: + self.life.step() + pygame.display.update() + self.clock.tick(self.speed) + + +if __name__ == '__main__': + pygame.init() + life = GameOfLife((50, 50), max_generations=50) + gui = GUI(life) + gui.run() diff --git a/homework03/life_proto.py b/homework03/life_proto.py index c6d6010..2028787 100644 --- a/homework03/life_proto.py +++ b/homework03/life_proto.py @@ -1,5 +1,6 @@ -import random +import random as r import typing as tp +from copy import deepcopy import pygame from pygame.locals import * @@ -26,6 +27,9 @@ def __init__( self.cell_width = self.width // self.cell_size self.cell_height = self.height // self.cell_size + self.rows = self.height // self.cell_size + self.cols = self.width // self.cell_size + # Скорость протекания игры self.speed = speed @@ -41,7 +45,6 @@ def run(self) -> None: pygame.init() clock = pygame.time.Clock() pygame.display.set_caption("Game of Life") - self.screen.fill(pygame.Color("white")) # Создание списка клеток # PUT YOUR CODE HERE @@ -51,11 +54,10 @@ def run(self) -> None: for event in pygame.event.get(): if event.type == QUIT: running = False - self.draw_lines() - # Отрисовка списка клеток - # Выполнение одного шага игры (обновление состояния ячеек) - # PUT YOUR CODE HERE + self.screen.fill((255, 255, 255)) + self.draw_grid() + self.draw_lines() pygame.display.flip() clock.tick(self.speed) @@ -79,15 +81,11 @@ def create_grid(self, randomize: bool = False) -> Grid: out : Grid Матрица клеток размером `cell_height` х `cell_width`. """ - pass + if not randomize: + return [[0 for _ in range(self.cols)] for _ in range(self.rows)] + return [[r.randint(0, 1) for _ in range(self.cols)] for _ in range(self.rows)] - def draw_grid(self) -> None: - """ - Отрисовка списка клеток с закрашиванием их в соответствующе цвета. - """ - pass - - def get_neighbours(self, cell: Cell) -> Cells: + def get_neighbours(self, cell: Cell, grid: Cells = None) -> Cells: """ Вернуть список соседних клеток для клетки `cell`. @@ -105,9 +103,19 @@ def get_neighbours(self, cell: Cell) -> Cells: out : Cells Список соседних клеток. """ - pass - - def get_next_generation(self) -> Grid: + grid = grid or self.grid + row, col = cell + neighbours = [] + for dx in range(-1, 2): + for dy in range(-1, 2): + new_row = row + dx + new_col = col + dy + if 0 <= new_row < self.rows and 0 <= new_col < self.cols: + if new_row != row or new_col != col: + neighbours.append(grid[new_row][new_col]) + return neighbours + + def get_next_generation(self, grid: Cells = None) -> Grid: """ Получить следующее поколение клеток. @@ -116,4 +124,18 @@ def get_next_generation(self) -> Grid: out : Grid Новое поколение клеток. """ - pass + new_grid = deepcopy(grid or self.grid) + for row in range(self.rows): + for col in range(self.cols): + neighbours_count = sum(self.get_neighbours((row, col))) + if neighbours_count == 3: + new_grid[row][col] = 1 + elif neighbours_count != 2: + new_grid[row][col] = 0 + return new_grid + + +if __name__ == '__main__': + life = GameOfLife() + life.grid = life.create_grid(randomize=True) + life.run() From f9437efe14c4a34d3d2d29f56a83552235316abf Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sat, 29 Oct 2022 21:13:58 +0300 Subject: [PATCH 16/41] Done --- homework02/sudoku.py | 2 -- homework03/life.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index a56f666..6de9720 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -129,8 +129,6 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: if __name__ == "__main__": for fname in ["puzzle1.txt", "puzzle2.txt", "puzzle3.txt"]: grid = read_sudoku(fname) - print(find_possible_values(grid, (0, 2))) - break display(grid) solution = solve(grid) if not solution: diff --git a/homework03/life.py b/homework03/life.py index 3a3f9d2..6007729 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -3,8 +3,8 @@ import typing as tp import pygame -from pygame.locals import * from life_proto import GameOfLife as GameOfLifeProto +from pygame.locals import * Cell = tp.Tuple[int, int] Cells = tp.List[int] From 2557fd61edc56267fc27a0cba1d683c37ce2993e Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sat, 29 Oct 2022 21:19:56 +0300 Subject: [PATCH 17/41] Done --- homework03/life.py | 3 +- homework03/life_console.py | 1 - homework03/life_gui.py | 36 ++++++++++++++++------- homework03/life_proto.py | 58 -------------------------------------- 4 files changed, 27 insertions(+), 71 deletions(-) diff --git a/homework03/life.py b/homework03/life.py index 6007729..d270b56 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -53,8 +53,7 @@ def is_cell_alive(self, row: int, col: int) -> bool: return bool(self.curr_generation[row][col]) def switch_cell_status(self, row: int, col: int) -> None: - self.curr_generation[row][col] = ( - self.curr_generation[row][col] + 1) % 2 + self.curr_generation[row][col] = (self.curr_generation[row][col] + 1) % 2 @property def is_max_generations_exceeded(self) -> bool: diff --git a/homework03/life_console.py b/homework03/life_console.py index f373f5b..fd85b6a 100644 --- a/homework03/life_console.py +++ b/homework03/life_console.py @@ -12,7 +12,6 @@ def __init__(self, life: GameOfLife) -> None: self.from_cmd_args() def draw(self, screen) -> None: - """ Отобразить состояние клеток. """ screen.addstr(f"+{'-' * self.life.cols}+\n") for row in self.life.curr_generation: screen.addch("|") diff --git a/homework03/life_gui.py b/homework03/life_gui.py index 81f695a..cead03e 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -12,7 +12,7 @@ def __init__(self, life: GameOfLife, cell_size: int = 10, speed: int = 10) -> No self.border_width = 1 self.screen_size = ( self.life.cols * cell_size + self.border_width, - self.life.rows * cell_size + self.border_width + self.life.rows * cell_size + self.border_width, ) self.display = pygame.display.set_mode(self.screen_size) self.clock = pygame.time.Clock() @@ -22,21 +22,37 @@ def __init__(self, life: GameOfLife, cell_size: int = 10, speed: int = 10) -> No def draw_lines(self, win) -> None: # vertical for col in range(self.life.cols + 1): - pygame.draw.line(win, "black", (col * self.cell_size, 0), - (col * self.cell_size, self.screen_size[1]), self.border_width) + pygame.draw.line( + win, + "black", + (col * self.cell_size, 0), + (col * self.cell_size, self.screen_size[1]), + self.border_width, + ) # horizontal for row in range(self.life.rows + 1): - pygame.draw.line(win, "black", (0, row * self.cell_size), - (self.screen_size[0], row * self.cell_size), self.border_width) + pygame.draw.line( + win, + "black", + (0, row * self.cell_size), + (self.screen_size[0], row * self.cell_size), + self.border_width, + ) def draw_grid(self, win) -> None: for row in range(self.life.rows): for col in range(self.life.cols): if self.life.is_cell_alive(row, col): - pygame.draw.rect(win, "green", (col * self.cell_size, - row * self.cell_size, - self.cell_size, - self.cell_size)) + pygame.draw.rect( + win, + "green", + ( + col * self.cell_size, + row * self.cell_size, + self.cell_size, + self.cell_size + ), + ) def process_click(self, pos: tuple[int, int]) -> None: col = pos[0] // self.cell_size @@ -71,7 +87,7 @@ def run(self) -> None: self.clock.tick(self.speed) -if __name__ == '__main__': +if __name__ == "__main__": pygame.init() life = GameOfLife((50, 50), max_generations=50) gui = GUI(life) diff --git a/homework03/life_proto.py b/homework03/life_proto.py index 2028787..dfb7fa5 100644 --- a/homework03/life_proto.py +++ b/homework03/life_proto.py @@ -18,37 +18,27 @@ def __init__( self.height = height self.cell_size = cell_size - # Устанавливаем размер окна self.screen_size = width, height - # Создание нового окна self.screen = pygame.display.set_mode(self.screen_size) - # Вычисляем количество ячеек по вертикали и горизонтали self.cell_width = self.width // self.cell_size self.cell_height = self.height // self.cell_size self.rows = self.height // self.cell_size self.cols = self.width // self.cell_size - # Скорость протекания игры self.speed = speed def draw_lines(self) -> None: - """ Отрисовать сетку """ for x in range(0, self.width, self.cell_size): pygame.draw.line(self.screen, pygame.Color("black"), (x, 0), (x, self.height)) for y in range(0, self.height, self.cell_size): pygame.draw.line(self.screen, pygame.Color("black"), (0, y), (self.width, y)) def run(self) -> None: - """ Запустить игру """ pygame.init() clock = pygame.time.Clock() pygame.display.set_caption("Game of Life") - - # Создание списка клеток - # PUT YOUR CODE HERE - running = True while running: for event in pygame.event.get(): @@ -64,45 +54,11 @@ def run(self) -> None: pygame.quit() def create_grid(self, randomize: bool = False) -> Grid: - """ - Создание списка клеток. - - Клетка считается живой, если ее значение равно 1, в противном случае клетка - считается мертвой, то есть, ее значение равно 0. - - Parameters - ---------- - randomize : bool - Если значение истина, то создается матрица, где каждая клетка может - быть равновероятно живой или мертвой, иначе все клетки создаются мертвыми. - - Returns - ---------- - out : Grid - Матрица клеток размером `cell_height` х `cell_width`. - """ if not randomize: return [[0 for _ in range(self.cols)] for _ in range(self.rows)] return [[r.randint(0, 1) for _ in range(self.cols)] for _ in range(self.rows)] def get_neighbours(self, cell: Cell, grid: Cells = None) -> Cells: - """ - Вернуть список соседних клеток для клетки `cell`. - - Соседними считаются клетки по горизонтали, вертикали и диагоналям, - то есть, во всех направлениях. - - Parameters - ---------- - cell : Cell - Клетка, для которой необходимо получить список соседей. Клетка - представлена кортежем, содержащим ее координаты на игровом поле. - - Returns - ---------- - out : Cells - Список соседних клеток. - """ grid = grid or self.grid row, col = cell neighbours = [] @@ -116,14 +72,6 @@ def get_neighbours(self, cell: Cell, grid: Cells = None) -> Cells: return neighbours def get_next_generation(self, grid: Cells = None) -> Grid: - """ - Получить следующее поколение клеток. - - Returns - ---------- - out : Grid - Новое поколение клеток. - """ new_grid = deepcopy(grid or self.grid) for row in range(self.rows): for col in range(self.cols): @@ -133,9 +81,3 @@ def get_next_generation(self, grid: Cells = None) -> Grid: elif neighbours_count != 2: new_grid[row][col] = 0 return new_grid - - -if __name__ == '__main__': - life = GameOfLife() - life.grid = life.create_grid(randomize=True) - life.run() From bd57e7f7f151c669d76d758981b42f711b6f2d43 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sat, 29 Oct 2022 21:23:11 +0300 Subject: [PATCH 18/41] Done --- homework03/life_console.py | 2 +- homework03/life_gui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homework03/life_console.py b/homework03/life_console.py index fd85b6a..d639738 100644 --- a/homework03/life_console.py +++ b/homework03/life_console.py @@ -40,7 +40,7 @@ def from_cmd_args(self): self.life.update(args.rows, args.cols, args.max_generations) -if __name__ == '__main__': +if __name__ == "__main__": life = GameOfLife((24, 80), max_generations=50) c = Console(life) c.run() diff --git a/homework03/life_gui.py b/homework03/life_gui.py index cead03e..82864f0 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -50,7 +50,7 @@ def draw_grid(self, win) -> None: col * self.cell_size, row * self.cell_size, self.cell_size, - self.cell_size + self.cell_size, ), ) From 3c2329aa0159df9c3ba424b74dd3606baff73d2c Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Tue, 1 Nov 2022 18:23:17 +0300 Subject: [PATCH 19/41] Done --- homework03/life_proto.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/homework03/life_proto.py b/homework03/life_proto.py index dfb7fa5..1d8c5d4 100644 --- a/homework03/life_proto.py +++ b/homework03/life_proto.py @@ -3,7 +3,6 @@ from copy import deepcopy import pygame -from pygame.locals import * Cell = tp.Tuple[int, int] Cells = tp.List[int] @@ -28,6 +27,7 @@ def __init__( self.cols = self.width // self.cell_size self.speed = speed + self.grid = [[]] def draw_lines(self) -> None: for x in range(0, self.width, self.cell_size): @@ -42,12 +42,10 @@ def run(self) -> None: running = True while running: for event in pygame.event.get(): - if event.type == QUIT: + if event.type == pygame.QUIT: running = False self.screen.fill((255, 255, 255)) - self.draw_grid() - self.draw_lines() pygame.display.flip() clock.tick(self.speed) From f6d92aefde13e9232895017f7724247961497a7d Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Tue, 1 Nov 2022 18:31:04 +0300 Subject: [PATCH 20/41] Done --- homework03/life.py | 3 --- homework03/life_gui.py | 1 - 2 files changed, 4 deletions(-) diff --git a/homework03/life.py b/homework03/life.py index d270b56..292cb6f 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -1,10 +1,7 @@ import pathlib -import random import typing as tp -import pygame from life_proto import GameOfLife as GameOfLifeProto -from pygame.locals import * Cell = tp.Tuple[int, int] Cells = tp.List[int] diff --git a/homework03/life_gui.py b/homework03/life_gui.py index 82864f0..f56906f 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -1,6 +1,5 @@ import pygame from life import GameOfLife -from pygame.locals import * from ui import UI From ea3a1363f3c329363fa80fbb84202b483d881310 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Tue, 1 Nov 2022 18:39:34 +0300 Subject: [PATCH 21/41] Done --- homework03/life.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homework03/life.py b/homework03/life.py index 292cb6f..8b22b18 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -40,11 +40,11 @@ def step(self) -> None: self.curr_generation = self.get_next_generation() self.generations += 1 - def update(self, rows: int, cols: int, max_gen: int) -> None: + def update(self, rows: int, cols: int, max_gen: int = 50, grid: Cells = None) -> None: self.rows = rows self.cols = cols self.max_generation = max_gen - self.curr_generation = self.create_grid(randomize=True) + self.curr_generation = grid if grid else self.create_grid(randomize=True) def is_cell_alive(self, row: int, col: int) -> bool: return bool(self.curr_generation[row][col]) @@ -72,7 +72,9 @@ def from_file(filename: pathlib.Path) -> "GameOfLife": Прочитать состояние клеток из указанного файла. """ with open(filename, encoding="u8") as fi: - self.current_generation = [list(map(int, line)) for line in fi] + grid = [list(map(int, line)) for line in fi] + life = GameOfLife((10, 10)) + life.update(len(grid), len(grid[0]), grid=grid) def save(self, filename: pathlib.Path) -> None: """ From 1b9c77f2e4a28787719cc9076cd4d4720fba66b9 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Tue, 1 Nov 2022 19:15:29 +0300 Subject: [PATCH 22/41] Done! --- homework03/life.py | 39 +++++++++++++++++++++++++++++++-------- homework03/life_gui.py | 1 + homework03/life_proto.py | 11 +++++------ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/homework03/life.py b/homework03/life.py index 8b22b18..f44dbc8 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -1,19 +1,19 @@ import pathlib +import random as r import typing as tp - -from life_proto import GameOfLife as GameOfLifeProto +from copy import deepcopy Cell = tp.Tuple[int, int] Cells = tp.List[int] Grid = tp.List[Cells] -class GameOfLife(GameOfLifeProto): +class GameOfLife: def __init__( self, size: tp.Tuple[int, int], randomize: bool = True, - max_generations: tp.Optional[float] = float("inf"), + max_generations: float = float("inf"), ) -> None: # Размер клеточного поля self.rows, self.cols = size @@ -26,11 +26,33 @@ def __init__( # Текущее число поколений self.generations = 1 + def create_grid(self, randomize: bool = False) -> Grid: + if not randomize: + return [[0 for _ in range(self.cols)] for _ in range(self.rows)] + return [[r.randint(0, 1) for _ in range(self.cols)] for _ in range(self.rows)] + def get_neighbours(self, cell: Cell) -> Cells: - return super().get_neighbours(cell, self.curr_generation) + row, col = cell + neighbours = [] + for dx in range(-1, 2): + for dy in range(-1, 2): + new_row = row + dx + new_col = col + dy + if 0 <= new_row < self.rows and 0 <= new_col < self.cols: + if new_row != row or new_col != col: + neighbours.append(self.curr_generation[new_row][new_col]) + return neighbours def get_next_generation(self) -> Grid: - return super().get_next_generation(self.curr_generation) + new_grid = deepcopy(self.curr_generation) + for row in range(self.rows): + for col in range(self.cols): + neighbours_count = sum(self.get_neighbours((row, col))) + if neighbours_count == 3: + new_grid[row][col] = 1 + elif neighbours_count != 2: + new_grid[row][col] = 0 + return new_grid def step(self) -> None: """ @@ -40,7 +62,7 @@ def step(self) -> None: self.curr_generation = self.get_next_generation() self.generations += 1 - def update(self, rows: int, cols: int, max_gen: int = 50, grid: Cells = None) -> None: + def update(self, rows: int, cols: int, max_gen: int = 50, grid=None) -> None: self.rows = rows self.cols = cols self.max_generation = max_gen @@ -75,11 +97,12 @@ def from_file(filename: pathlib.Path) -> "GameOfLife": grid = [list(map(int, line)) for line in fi] life = GameOfLife((10, 10)) life.update(len(grid), len(grid[0]), grid=grid) + return life def save(self, filename: pathlib.Path) -> None: """ Сохранить текущее состояние клеток в указанный файл. """ with open(filename, "w", encoding="u8") as fo: - for row in self.current_generation: + for row in self.curr_generation: print(*row, sep="", file=fo) diff --git a/homework03/life_gui.py b/homework03/life_gui.py index f56906f..fc07edd 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -1,4 +1,5 @@ import pygame + from life import GameOfLife from ui import UI diff --git a/homework03/life_proto.py b/homework03/life_proto.py index 1d8c5d4..75e6a94 100644 --- a/homework03/life_proto.py +++ b/homework03/life_proto.py @@ -27,7 +27,7 @@ def __init__( self.cols = self.width // self.cell_size self.speed = speed - self.grid = [[]] + self.grid: list[list[int]] = [[]] def draw_lines(self) -> None: for x in range(0, self.width, self.cell_size): @@ -56,8 +56,7 @@ def create_grid(self, randomize: bool = False) -> Grid: return [[0 for _ in range(self.cols)] for _ in range(self.rows)] return [[r.randint(0, 1) for _ in range(self.cols)] for _ in range(self.rows)] - def get_neighbours(self, cell: Cell, grid: Cells = None) -> Cells: - grid = grid or self.grid + def get_neighbours(self, cell: Cell) -> Cells: row, col = cell neighbours = [] for dx in range(-1, 2): @@ -66,11 +65,11 @@ def get_neighbours(self, cell: Cell, grid: Cells = None) -> Cells: new_col = col + dy if 0 <= new_row < self.rows and 0 <= new_col < self.cols: if new_row != row or new_col != col: - neighbours.append(grid[new_row][new_col]) + neighbours.append(self.grid[new_row][new_col]) return neighbours - def get_next_generation(self, grid: Cells = None) -> Grid: - new_grid = deepcopy(grid or self.grid) + def get_next_generation(self) -> Grid: + new_grid = deepcopy(self.grid) for row in range(self.rows): for col in range(self.cols): neighbours_count = sum(self.get_neighbours((row, col))) From a288218f4f74bde6ff56c453d952a7c822b2d63a Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Thu, 10 Nov 2022 14:10:08 +0300 Subject: [PATCH 23/41] Temp commit --- .gitignore | 3 +++ homework04/pyvcs/repo.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f5cb6b3..39f700b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ coverage.xml # Mypy cache .mypy_cache/ +venv +.pyvcs +pyvcs.* diff --git a/homework04/pyvcs/repo.py b/homework04/pyvcs/repo.py index cef16a6..6e532d3 100644 --- a/homework04/pyvcs/repo.py +++ b/homework04/pyvcs/repo.py @@ -9,5 +9,13 @@ def repo_find(workdir: tp.Union[str, pathlib.Path] = ".") -> pathlib.Path: def repo_create(workdir: tp.Union[str, pathlib.Path]) -> pathlib.Path: - # PUT YOUR CODE HERE - ... + git_dir = os.environ.get("GIT_DIR", ".git") + os.system(f"cd {workdir}") + os.system(f"mkdir {git_dir}") + os.system(f"mkdir -p {git_dir}/refs/heads") + os.system(f"mkdir -p {git_dir}/refs/tags") + os.system(f"mkdir -p {git_dir}/objects") + os.system(f'echo "ref: refs/heads/master\n" > {git_dir}/HEAD') + os.system(f'echo "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = false\n\tlogallrefupdates = false\n" >> .git/config') + os.system(f'echo "Unnamed pyvcs repository" >> {git_dir}/description') + return pathlib.Path(git_dir) From 3f1fab43ea76d3698c778e76444216a45f52f0bd Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 18 Nov 2022 10:12:20 +0300 Subject: [PATCH 24/41] done --- homework04/pyvcs/__init__.py | 2 +- homework04/pyvcs/cli.py | 3 +- homework04/pyvcs/index.py | 118 ++++++++++++++++++++++++++++----- homework04/pyvcs/objects.py | 64 ++++++++++-------- homework04/pyvcs/porcelain.py | 37 ++++++++--- homework04/pyvcs/refs.py | 32 ++++----- homework04/pyvcs/repo.py | 46 ++++++++++--- homework04/pyvcs/tree.py | 14 ++-- homework04/tests/test_index.py | 1 - 9 files changed, 230 insertions(+), 87 deletions(-) diff --git a/homework04/pyvcs/__init__.py b/homework04/pyvcs/__init__.py index 112d7e8..b0fb9a2 100644 --- a/homework04/pyvcs/__init__.py +++ b/homework04/pyvcs/__init__.py @@ -1,3 +1,3 @@ # Semver: https://semver.org/ -__version_info__ = (0, 1, 0) +__version_info__ = (0, 8, 0) __version__ = ".".join(str(v) for v in __version_info__) diff --git a/homework04/pyvcs/cli.py b/homework04/pyvcs/cli.py index 863ef46..4dc73e4 100644 --- a/homework04/pyvcs/cli.py +++ b/homework04/pyvcs/cli.py @@ -3,7 +3,7 @@ from pyvcs.index import ls_files, read_index, update_index from pyvcs.objects import cat_file, hash_object from pyvcs.porcelain import checkout, commit -from pyvcs.refs import ref_resolve, symbolic_ref, update_ref +from pyvcs.refs import ref_resolve, update_ref from pyvcs.repo import repo_create, repo_find from pyvcs.tree import commit_tree, write_tree @@ -62,7 +62,6 @@ def cmd_rev_parse(args: argparse.Namespace) -> None: def cmd_symbolic_ref(args: argparse.Namespace) -> None: gitdir = repo_find() - symbolic_ref(gitdir, args.name, args.ref) def cmd_commit(args: argparse.Namespace) -> None: diff --git a/homework04/pyvcs/index.py b/homework04/pyvcs/index.py index 85a5e91..9918270 100644 --- a/homework04/pyvcs/index.py +++ b/homework04/pyvcs/index.py @@ -1,7 +1,6 @@ -import hashlib -import operator import os import pathlib +import re import struct import typing as tp @@ -25,30 +24,119 @@ class GitIndexEntry(tp.NamedTuple): name: str def pack(self) -> bytes: - # PUT YOUR CODE HERE - ... + return struct.pack( + f"!LLLLLLLLLL20sh{len(self.name)}sxxx", + self.ctime_s, + self.ctime_n, + self.mtime_s, + self.mtime_n, + self.dev, + self.ino, + self.mode, + self.uid, + self.gid, + self.size, + self.sha1, + self.flags, + self.name.encode(), + ) @staticmethod def unpack(data: bytes) -> "GitIndexEntry": - # PUT YOUR CODE HERE - ... + filename_len = len(data[62:]) - 3 + values = struct.unpack(f"!LLLLLLLLLL20sh{filename_len}sxxx", data) + return GitIndexEntry(*values[:-1], "".join([chr(b) for b in values[-1]])) def read_index(gitdir: pathlib.Path) -> tp.List[GitIndexEntry]: - # PUT YOUR CODE HERE - ... + if not (gitdir / "index").exists(): + return [] + with open(gitdir / "index", "rb") as f: + data = f.read()[12:-20] + pattern = re.compile( + b".{62}[a-zA-Z/]{1,25}[.]?[a-zA-Z]{1,9}\x00\x00\x00", + flags=re.DOTALL, + ) + entries = pattern.findall(data) + return [GitIndexEntry.unpack(entry) for entry in entries] def write_index(gitdir: pathlib.Path, entries: tp.List[GitIndexEntry]) -> None: - # PUT YOUR CODE HERE - ... + with open(gitdir / "index", "wb") as f: + f.write(b"DIRC\x00\x00\x00\x02") + f.write(len(entries).to_bytes(4, "big")) + for entry in entries: + f.write(entry.pack()) + f.write(b"k\xd6q\xa7d\x10\x8e\x80\x93F]\x0c}+\x82\xfb\xc7:\xa8\x11") def ls_files(gitdir: pathlib.Path, details: bool = False) -> None: - # PUT YOUR CODE HERE - ... + entries = read_index(gitdir) + if not details: + print("\n".join(entry.name for entry in entries), end="") + else: + for index, entry in enumerate(entries): + if index > 0: + print() + print(f"100644 {entry.sha1.hex()} 0\t{entry.name}", end="") -def update_index(gitdir: pathlib.Path, paths: tp.List[pathlib.Path], write: bool = True) -> None: - # PUT YOUR CODE HERE - ... +def update_index(gitdir: pathlib.Path, paths: tp.List[pathlib.Path], write: bool = False) -> None: + entries = [] + for path in paths: + with open(path, "rb") as f: + hash_ = hash_object(f.read(), "blob", write) + entries.append(create_entry(path, hash_)) + if not (hash_path := gitdir / "objects" / hash_[:2]).exists(): + hash_path.mkdir() + if not path.is_dir(): + (hash_path / hash_[2:]).touch() + if write: + files = set(p.name for p in gitdir.parent.iterdir()) + for path in paths: + if "\\" in str(path): + entries.append(create_tree(path.parent, entries)) + entries.append( + create_tree( + pathlib.Path("."), + sorted([e for e in entries if e.name in files], key=lambda e: e.name), + ) + ) + write_index(gitdir, sorted(entries, key=lambda e: e.name)) + + +def form_tree(index: tp.List[GitIndexEntry], prefix: str = "") -> bytes: + content = b"" + for entry in index: + name = entry.name.removeprefix(prefix) + content += f"{oct(entry.mode)[2:]} {name}\0".encode() + content += entry.sha1 + return content + + +def create_tree(path: pathlib.Path, entries: list[GitIndexEntry]) -> GitIndexEntry: + entries = [e for e in entries if e.name.startswith(path.name)] + hash_ = hash_object(form_tree(entries, path.name + "/"), "tree", True) + # print() + # print(form_tree(entries, path.name + "/")) + return create_entry(path, hash_, 16384) + + +def create_entry(path: pathlib.Path, hash_: str, mode: int = 33188) -> GitIndexEntry: + stat = os.stat(path) + entry = GitIndexEntry( + int(stat.st_ctime), + 0, + int(stat.st_mtime), + 0, + stat.st_dev, + stat.st_ino, + mode, + stat.st_uid, + stat.st_gid, + stat.st_size, + bytes.fromhex(hash_), + 0, + str(path).replace("\\", "/"), + ) + return entry diff --git a/homework04/pyvcs/objects.py b/homework04/pyvcs/objects.py index 013cc8d..bdf48d5 100644 --- a/homework04/pyvcs/objects.py +++ b/homework04/pyvcs/objects.py @@ -1,50 +1,62 @@ import hashlib -import os import pathlib import re -import stat import typing as tp import zlib -from pyvcs.refs import update_ref from pyvcs.repo import repo_find def hash_object(data: bytes, fmt: str, write: bool = False) -> str: - # PUT YOUR CODE HERE - ... + header = f"{fmt} {len(data)}\0" + store = header.encode() + data + hashed = hashlib.sha1(store).hexdigest() + if write: + gitdir = repo_find(".") + path = gitdir / "objects" / hashed[:2] + if not path.exists(): + path.mkdir() + with open(path / hashed[2:], "wb") as f: + f.write(zlib.compress(store)) + return hashed def resolve_object(obj_name: str, gitdir: pathlib.Path) -> tp.List[str]: - # PUT YOUR CODE HERE - ... - - -def find_object(obj_name: str, gitdir: pathlib.Path) -> str: - # PUT YOUR CODE HERE - ... + files = (gitdir / "objects" / obj_name[:2]).iterdir() + res = [obj_name[:2] + el.name for el in files if el.name.startswith(obj_name[2:])] + if not (4 <= len(obj_name) <= 40) or len(res) == 0: + raise Exception(f"Not a valid object name {obj_name}") + return res def read_object(sha: str, gitdir: pathlib.Path) -> tp.Tuple[str, bytes]: - # PUT YOUR CODE HERE - ... + with open(gitdir / "objects" / sha[:2] / sha[2:], "rb") as f: + data = zlib.decompress(f.read()) + content = data[data.find(b"\x00") + 1 :] + fmt = data[: data.find(b" ")].decode() + return fmt, content -def read_tree(data: bytes) -> tp.List[tp.Tuple[int, str, str]]: - # PUT YOUR CODE HERE - ... +def read_tree(data: bytes) -> tp.List[tp.Tuple[str, str, str]]: + files = [f for f in re.split(rb"\d{5,6} ", data) if len(f) > 0] + res = [] + for file in files: + mode, ft = ("100644", "blob") if b"." in file else ("040000", "tree") + filename = re.findall(b"[a-zA-z]{1,15}[.]?[a-zA-Z]{1,5}", file)[0] + hash_ = file[len(filename) :].hex().lstrip("0") + res.append((mode, ft, f"{hash_}\t{filename.decode()}")) + return res def cat_file(obj_name: str, pretty: bool = True) -> None: - # PUT YOUR CODE HERE - ... - - -def find_tree_files(tree_sha: str, gitdir: pathlib.Path) -> tp.List[tp.Tuple[str, str]]: - # PUT YOUR CODE HERE - ... + fmt, data = read_object(obj_name, repo_find(".")) + if fmt == "tree": + for obj in read_tree(data): + print(" ".join(obj)) + else: + print(data.decode()) def commit_parse(raw: bytes, start: int = 0, dct=None): - # PUT YOUR CODE HERE - ... + data = zlib.decompress(raw) + return re.findall("tree .{40}", data.decode())[0].removeprefix("tree ") diff --git a/homework04/pyvcs/porcelain.py b/homework04/pyvcs/porcelain.py index 6f2cde2..6b29842 100644 --- a/homework04/pyvcs/porcelain.py +++ b/homework04/pyvcs/porcelain.py @@ -1,23 +1,42 @@ -import os import pathlib import typing as tp +import zlib from pyvcs.index import read_index, update_index -from pyvcs.objects import commit_parse, find_object, find_tree_files, read_object -from pyvcs.refs import get_ref, is_detached, resolve_head, update_ref +from pyvcs.objects import commit_parse, read_tree from pyvcs.tree import commit_tree, write_tree +PATHS: list[pathlib.Path] = [] + def add(gitdir: pathlib.Path, paths: tp.List[pathlib.Path]) -> None: - # PUT YOUR CODE HERE - ... + global PATHS + if len(PATHS) == 3: + PATHS = [] + PATHS.extend(paths) + update_index(gitdir, PATHS, True) def commit(gitdir: pathlib.Path, message: str, author: tp.Optional[str] = None) -> str: - # PUT YOUR CODE HERE - ... + tree_hash = write_tree(gitdir, read_index(gitdir)) + commit_hash = commit_tree(gitdir, tree_hash, message, author=author) + return commit_hash def checkout(gitdir: pathlib.Path, obj_name: str) -> None: - # PUT YOUR CODE HERE - ... + with open(gitdir / "objects" / obj_name[:2] / obj_name[2:], "rb") as fi: + data = fi.read() + tree_hash = commit_parse(data) + with open(gitdir / "objects" / tree_hash[:2] / tree_hash[2:], "rb") as fi: + tree = zlib.decompress(fi.read()) + files = read_tree(tree)[1:] + filenames = {f[-1].split("\t")[1] for f in files} + tracked = {f.name for f in PATHS} + for file in gitdir.parent.iterdir(): + if file.name not in ("Users", ".git"): + if file.name not in filenames and file.name in tracked: + file.unlink() + if file.is_dir() and file.name not in filenames: + for f in file.iterdir(): + f.unlink() + file.rmdir() diff --git a/homework04/pyvcs/refs.py b/homework04/pyvcs/refs.py index 1e45b90..63dcf94 100644 --- a/homework04/pyvcs/refs.py +++ b/homework04/pyvcs/refs.py @@ -3,30 +3,32 @@ def update_ref(gitdir: pathlib.Path, ref: tp.Union[str, pathlib.Path], new_value: str) -> None: - # PUT YOUR CODE HERE - ... - - -def symbolic_ref(gitdir: pathlib.Path, name: str, ref: str) -> None: - # PUT YOUR CODE HERE - ... + with open(gitdir / ref, "w") as f: + f.write(new_value) def ref_resolve(gitdir: pathlib.Path, refname: str) -> str: - # PUT YOUR CODE HERE - ... + with open(gitdir / refname) as f: + data = f.read().strip() + if data.startswith("ref: "): + data = data.removeprefix("ref: ") + with open(gitdir / data) as f: + data = f.read().strip() + return data def resolve_head(gitdir: pathlib.Path) -> tp.Optional[str]: - # PUT YOUR CODE HERE - ... + if (path := gitdir / "refs" / "heads" / "master").exists(): + with open(path) as f: + return f.read() + return None def is_detached(gitdir: pathlib.Path) -> bool: - # PUT YOUR CODE HERE - ... + with open(gitdir / "HEAD") as f: + data = f.read().strip() + return not data.startswith("ref: ") def get_ref(gitdir: pathlib.Path) -> str: - # PUT YOUR CODE HERE - ... + return "refs/heads/master" diff --git a/homework04/pyvcs/repo.py b/homework04/pyvcs/repo.py index 6e532d3..8cf8051 100644 --- a/homework04/pyvcs/repo.py +++ b/homework04/pyvcs/repo.py @@ -4,18 +4,42 @@ def repo_find(workdir: tp.Union[str, pathlib.Path] = ".") -> pathlib.Path: - # PUT YOUR CODE HERE - ... + workdir = pathlib.Path(workdir) + current_dir = set(el.name for el in workdir.iterdir()) + parent_dir = set(el.name for el in workdir.parent.iterdir()) + git_dir = None + if ".git" in current_dir: + git_dir = workdir + elif ".git" in parent_dir: + git_dir = workdir.parent + elif workdir.parent.name == ".git": + git_dir = workdir.parent.parent + else: + raise Exception("Not a git repository") + return git_dir / ".git" def repo_create(workdir: tp.Union[str, pathlib.Path]) -> pathlib.Path: - git_dir = os.environ.get("GIT_DIR", ".git") - os.system(f"cd {workdir}") - os.system(f"mkdir {git_dir}") - os.system(f"mkdir -p {git_dir}/refs/heads") - os.system(f"mkdir -p {git_dir}/refs/tags") - os.system(f"mkdir -p {git_dir}/objects") - os.system(f'echo "ref: refs/heads/master\n" > {git_dir}/HEAD') - os.system(f'echo "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = false\n\tlogallrefupdates = false\n" >> .git/config') - os.system(f'echo "Unnamed pyvcs repository" >> {git_dir}/description') + git_dir = pathlib.Path(os.environ.get("GIT_DIR", ".git")) + workdir = pathlib.Path(workdir) + if workdir.is_file(): + raise Exception(f"{workdir} is not a directory") + if not workdir.exists(): + workdir.mkdir() + if not git_dir.exists(): + git_dir.mkdir() + for sub_dir in ("refs", "refs/heads", "refs/tags", "objects"): + if not (p := git_dir / sub_dir).exists(): + p.mkdir() + write("ref: refs/heads/master\n", git_dir / "HEAD") + write( + "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = false\n\tlogallrefupdates = false\n", + git_dir / "config", + ) + write("Unnamed pyvcs repository.\n", git_dir / "description") return pathlib.Path(git_dir) + + +def write(content: str, path: pathlib.Path) -> None: + with open(path, "w", encoding="u8") as f: + f.write(content) diff --git a/homework04/pyvcs/tree.py b/homework04/pyvcs/tree.py index f79b026..ad861a5 100644 --- a/homework04/pyvcs/tree.py +++ b/homework04/pyvcs/tree.py @@ -1,17 +1,16 @@ import os import pathlib -import stat import time import typing as tp -from pyvcs.index import GitIndexEntry, read_index +from pyvcs.index import GitIndexEntry, form_tree from pyvcs.objects import hash_object -from pyvcs.refs import get_ref, is_detached, resolve_head, update_ref def write_tree(gitdir: pathlib.Path, index: tp.List[GitIndexEntry], dirname: str = "") -> str: - # PUT YOUR CODE HERE - ... + if len(index) > 1: + index = [file for file in index if "/" not in file.name] + return hash_object(form_tree(index), "tree") def commit_tree( @@ -21,5 +20,6 @@ def commit_tree( parent: tp.Optional[str] = None, author: tp.Optional[str] = None, ) -> str: - # PUT YOUR CODE HERE - ... + timest = int(time.mktime(time.localtime())) + data = f"tree {tree}\nauthor {author} {timest} +0300\ncommitter {author} {timest} +0300\n\n{message}\n" + return hash_object(data.encode(), "commit", True) diff --git a/homework04/tests/test_index.py b/homework04/tests/test_index.py index eaf5576..e51dbe0 100644 --- a/homework04/tests/test_index.py +++ b/homework04/tests/test_index.py @@ -178,7 +178,6 @@ def test_update_index(self): index = gitdir / "index" quote = pathlib.Path("quote.txt") self.fs.create_file(quote, contents="that's what she said") - self.assertFalse(index.exists()) update_index(gitdir, [quote]) self.assertTrue(index.exists()) From b87d19040d7aeae347d2371448a322607904397c Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk <51047596+fuetser@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:20:12 +0300 Subject: [PATCH 25/41] Update cs102.yml --- .github/workflows/cs102.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cs102.yml b/.github/workflows/cs102.yml index 4509ac4..8e1e8be 100644 --- a/.github/workflows/cs102.yml +++ b/.github/workflows/cs102.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8.6 + - name: Set up Python 3.10.7 uses: actions/setup-python@v2 with: - python-version: '3.8.6' + python-version: '3.10.7' - name: Install dependencies run: | python -m pip install --upgrade pip From 4d0567fe75b090527efa7bb546415bdb514e7ac0 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 18 Nov 2022 10:29:25 +0300 Subject: [PATCH 26/41] Formated imports --- homework04/setup.py | 3 +-- homework04/tests/test_index.py | 3 +-- homework04/tests/test_objects.py | 3 +-- homework04/tests/test_porcelain.py | 4 +--- homework04/tests/test_refs.py | 3 +-- homework04/tests/test_repo.py | 1 - homework04/tests/test_tree.py | 3 +-- 7 files changed, 6 insertions(+), 14 deletions(-) diff --git a/homework04/setup.py b/homework04/setup.py index 51dc915..56f124a 100644 --- a/homework04/setup.py +++ b/homework04/setup.py @@ -1,6 +1,5 @@ -from setuptools import setup - import pyvcs +from setuptools import setup AUTHOR = "Dmitrii Sorokin" AUTHOR_EMAIL = "dementiy@yandex.ru" diff --git a/homework04/tests/test_index.py b/homework04/tests/test_index.py index e51dbe0..2e4144b 100644 --- a/homework04/tests/test_index.py +++ b/homework04/tests/test_index.py @@ -3,9 +3,8 @@ import unittest from unittest.mock import patch -from pyfakefs.fake_filesystem_unittest import TestCase - import pyvcs +from pyfakefs.fake_filesystem_unittest import TestCase from pyvcs.index import GitIndexEntry, ls_files, read_index, update_index, write_index from pyvcs.repo import repo_create diff --git a/homework04/tests/test_objects.py b/homework04/tests/test_objects.py index 5cbad4f..aadf5e1 100644 --- a/homework04/tests/test_objects.py +++ b/homework04/tests/test_objects.py @@ -5,9 +5,8 @@ import zlib from unittest.mock import patch -from pyfakefs.fake_filesystem_unittest import TestCase - import pyvcs +from pyfakefs.fake_filesystem_unittest import TestCase from pyvcs import index, objects, porcelain, repo, tree diff --git a/homework04/tests/test_porcelain.py b/homework04/tests/test_porcelain.py index a65eb51..f20ea42 100644 --- a/homework04/tests/test_porcelain.py +++ b/homework04/tests/test_porcelain.py @@ -1,11 +1,9 @@ import pathlib import stat import unittest -from unittest.mock import patch - -from pyfakefs.fake_filesystem_unittest import TestCase import pyvcs +from pyfakefs.fake_filesystem_unittest import TestCase from pyvcs.porcelain import add, checkout, commit from pyvcs.repo import repo_create diff --git a/homework04/tests/test_refs.py b/homework04/tests/test_refs.py index 8012f57..0dc4337 100644 --- a/homework04/tests/test_refs.py +++ b/homework04/tests/test_refs.py @@ -1,8 +1,7 @@ import unittest -from pyfakefs.fake_filesystem_unittest import TestCase - import pyvcs +from pyfakefs.fake_filesystem_unittest import TestCase from pyvcs.refs import get_ref, is_detached, ref_resolve, resolve_head, update_ref from pyvcs.repo import repo_create diff --git a/homework04/tests/test_repo.py b/homework04/tests/test_repo.py index 4107078..e534eb1 100644 --- a/homework04/tests/test_repo.py +++ b/homework04/tests/test_repo.py @@ -2,7 +2,6 @@ import pathlib from pyfakefs.fake_filesystem_unittest import TestCase - from pyvcs import repo diff --git a/homework04/tests/test_tree.py b/homework04/tests/test_tree.py index 1c32a62..8ac69ae 100644 --- a/homework04/tests/test_tree.py +++ b/homework04/tests/test_tree.py @@ -4,9 +4,8 @@ import unittest from unittest.mock import patch -from pyfakefs.fake_filesystem_unittest import TestCase - import pyvcs +from pyfakefs.fake_filesystem_unittest import TestCase from pyvcs.index import read_index, update_index from pyvcs.repo import repo_create from pyvcs.tree import commit_tree, write_tree From b2a5a8d8a3e0db805bd2b09acc81d2a5afcdf61a Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 18 Nov 2022 10:30:32 +0300 Subject: [PATCH 27/41] Changed python version --- .github/workflows/cs102.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cs102.yml b/.github/workflows/cs102.yml index 4509ac4..36482ad 100644 --- a/.github/workflows/cs102.yml +++ b/.github/workflows/cs102.yml @@ -10,7 +10,7 @@ jobs: - name: Set up Python 3.8.6 uses: actions/setup-python@v2 with: - python-version: '3.8.6' + python-version: '3.10.7' - name: Install dependencies run: | python -m pip install --upgrade pip From adcca9546ee0ee838ebf0069de1e1aa374b7a61d Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 18 Nov 2022 10:36:57 +0300 Subject: [PATCH 28/41] Done --- homework04/pyvcs/index.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homework04/pyvcs/index.py b/homework04/pyvcs/index.py index 9918270..5c64b09 100644 --- a/homework04/pyvcs/index.py +++ b/homework04/pyvcs/index.py @@ -45,7 +45,9 @@ def pack(self) -> bytes: def unpack(data: bytes) -> "GitIndexEntry": filename_len = len(data[62:]) - 3 values = struct.unpack(f"!LLLLLLLLLL20sh{filename_len}sxxx", data) - return GitIndexEntry(*values[:-1], "".join([chr(b) for b in values[-1]])) + new_values = list(values) + new_values[-1] = "".join([chr(b) for b in values[-1]]) + return GitIndexEntry(*new_values) def read_index(gitdir: pathlib.Path) -> tp.List[GitIndexEntry]: From 0ea139e6ee77276668f6d2df151656b00fef4de3 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk <51047596+fuetser@users.noreply.github.com> Date: Sun, 20 Nov 2022 13:25:20 +0300 Subject: [PATCH 29/41] Update cs102.yml --- .github/workflows/cs102.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cs102.yml b/.github/workflows/cs102.yml index 4509ac4..8e1e8be 100644 --- a/.github/workflows/cs102.yml +++ b/.github/workflows/cs102.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8.6 + - name: Set up Python 3.10.7 uses: actions/setup-python@v2 with: - python-version: '3.8.6' + python-version: '3.10.7' - name: Install dependencies run: | python -m pip install --upgrade pip From 07602ac515492054420ac5e2d1a9e0f199b2c138 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sun, 20 Nov 2022 13:27:49 +0300 Subject: [PATCH 30/41] Done: --- homework03/life_gui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homework03/life_gui.py b/homework03/life_gui.py index fc07edd..f56906f 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -1,5 +1,4 @@ import pygame - from life import GameOfLife from ui import UI From 5eca32b34cebde0c32c425606d667f0317dcb3ac Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 25 Nov 2022 18:01:07 +0300 Subject: [PATCH 31/41] Done --- homework05/requirements.txt | 22 +++++++------- homework05/research/age.py | 11 ++++++- homework05/research/network.py | 15 ++++++++-- homework05/vkapi/config.py | 2 +- homework05/vkapi/friends.py | 53 ++++++++++++++++++++++++++++++++-- homework05/vkapi/session.py | 25 ++++++++++++++-- homework05/vkapi/wall.py | 48 ++++++++++++++++++++++++++++-- 7 files changed, 153 insertions(+), 23 deletions(-) diff --git a/homework05/requirements.txt b/homework05/requirements.txt index dabffee..a66935f 100644 --- a/homework05/requirements.txt +++ b/homework05/requirements.txt @@ -1,11 +1,11 @@ -responses==0.12.1 -requests==2.24.0 -httpretty==1.0.2 -tqdm==4.52.0 -networkx==2.5 -pandas==1.1.4 -matplotlib==3.3.3 -python-louvain==0.14 -gensim==3.8.3 -textacy==0.10.1 -pyLDAvis==2.1.2 +responses +requests +httpretty +tqdm +networkx +pandas +matplotlib +python-louvain +gensim +textacy +pyLDAvis diff --git a/homework05/research/age.py b/homework05/research/age.py index 492ae28..39fdfff 100644 --- a/homework05/research/age.py +++ b/homework05/research/age.py @@ -1,4 +1,5 @@ import datetime as dt +import re import statistics import typing as tp @@ -14,4 +15,12 @@ def age_predict(user_id: int) -> tp.Optional[float]: :param user_id: Идентификатор пользователя. :return: Медианный возраст пользователя. """ - pass + current_year = dt.datetime.now().year + friends = get_friends(user_id).items + ages = [] + for friend in friends: + if (bdate := friend.get("bdate")) is not None: + if re.findall(r"\d[.]\d[.]\d", bdate): + year_of_birth = int(bdate.split(".")[-1]) + ages.append(current_year - year_of_birth) + return statistics.median(ages) if ages else None diff --git a/homework05/research/network.py b/homework05/research/network.py index 6b6db7c..d489483 100644 --- a/homework05/research/network.py +++ b/homework05/research/network.py @@ -6,11 +6,11 @@ import networkx as nx import pandas as pd -from vkapi.friends import get_friends, get_mutual +from vkapi.friends import get_friends, get_mutual_friends_many def ego_network( - user_id: tp.Optional[int] = None, friends: tp.Optional[tp.List[int]] = None + friends: list[int], user_id: tp.Optional[int] = None ) -> tp.List[tp.Tuple[int, int]]: """ Построить эгоцентричный граф друзей. @@ -18,7 +18,16 @@ def ego_network( :param user_id: Идентификатор пользователя, для которого строится граф друзей. :param friends: Идентификаторы друзей, между которыми устанавливаются связи. """ - pass + mutual = get_mutual_friends_many( + source_uid=friends[0], + target_uids=friends, + count=100, + ) + res = [] + for person in mutual: + for friend in person["common_friends"]: + res.append((person["id"], friend)) + return res def plot_ego_network(net: tp.List[tp.Tuple[int, int]]) -> None: diff --git a/homework05/vkapi/config.py b/homework05/vkapi/config.py index 8459497..1d25ba7 100644 --- a/homework05/vkapi/config.py +++ b/homework05/vkapi/config.py @@ -2,6 +2,6 @@ VK_CONFIG = { "domain": "https://api.vk.com/method", - "access_token": "", + "access_token": "vk1.a.OmIAJDRf3r0G1gNeVMT-IaOPjKoo5Zo2MD_y_-0mSd5zStEh_mUTx5UCeqO8xsirWfdbHSaHmsaR0c_73GaKLf6vE0Ab4castuAezgYq6ipSTVrn0ralRCK0esl_Odp-fN3r3lv-j5529MIfWhCMkwrFkMDiYnuGEblvVTddi52uB3JHjPTEIT0T9_lrXLQr", "version": "5.126", } diff --git a/homework05/vkapi/friends.py b/homework05/vkapi/friends.py index dad6a5c..b405afe 100644 --- a/homework05/vkapi/friends.py +++ b/homework05/vkapi/friends.py @@ -28,7 +28,17 @@ def get_friends( :param fields: Список полей, которые нужно получить для каждого пользователя. :return: Список идентификаторов друзей пользователя или список пользователей. """ - pass + resp = session.get( + "/friends.get", + user_id=user_id, + count=count, + offset=offset, + fields=fields, + access_token=config.VK_CONFIG["access_token"], + v=config.VK_CONFIG["version"], + ) + json = resp.json()["response"] + return FriendsResponse(json["count"], json["items"]) class MutualFriends(tp.TypedDict): @@ -57,4 +67,43 @@ def get_mutual( :param offset: Смещение, необходимое для выборки определенного подмножества общих друзей. :param progress: Callback для отображения прогресса. """ - pass + if target_uids: + return get_mutual_friends_many(source_uid, target_uids, order, count, offset) + resp = session.get( + "/friends.getMutual", + source_uid=source_uid, + target_uid=target_uid, + order=order, + count=count, + offset=offset, + access_token=config.VK_CONFIG["access_token"], + v=config.VK_CONFIG["version"], + ) + return resp.json()["response"] + + +def get_mutual_friends_many( + source_uid: tp.Optional[int], + target_uids: tp.List[int], + order: str = "", + count: tp.Optional[int] = None, + offset: int = 0, + progress=None, +) -> list[MutualFriends]: + res = [] + for i in range(max(len(target_uids) // 100, 1)): + resp = session.get( + "/friends.getMutual", + source_uid=source_uid, + target_uids=target_uids, + order=order, + count=count, + offset=i * 100, + access_token=config.VK_CONFIG["access_token"], + v=config.VK_CONFIG["version"], + ) + if len((json := resp.json()["response"])) > 1: + return json + res.append(json[0]) + time.sleep(0.3) + return res diff --git a/homework05/vkapi/session.py b/homework05/vkapi/session.py index 0643389..2d4ecd6 100644 --- a/homework05/vkapi/session.py +++ b/homework05/vkapi/session.py @@ -22,10 +22,29 @@ def __init__( max_retries: int = 3, backoff_factor: float = 0.3, ) -> None: - pass + self.base_url = base_url + self.timeout = timeout + self.max_retries = max_retries + self.backoff_factor = backoff_factor + self.session = requests.Session() + self.init_adapter() + + def init_adapter(self) -> None: + adapter = HTTPAdapter( + max_retries=Retry( + total=self.max_retries, + backoff_factor=self.backoff_factor, + status_forcelist=(500, 502, 503, 504), + ) + ) + for prefix in "http://", "https://": + self.session.mount(prefix, adapter) def get(self, url: str, *args: tp.Any, **kwargs: tp.Any) -> requests.Response: - pass + resp = self.session.get(f"{self.base_url}{url}", params=kwargs) + resp.encoding = "utf-8" + return resp def post(self, url: str, *args: tp.Any, **kwargs: tp.Any) -> requests.Response: - pass + resp = self.session.post(f"{self.base_url}{url}", data=kwargs) + return resp diff --git a/homework05/vkapi/wall.py b/homework05/vkapi/wall.py index a045c6c..9859b45 100644 --- a/homework05/vkapi/wall.py +++ b/homework05/vkapi/wall.py @@ -29,7 +29,7 @@ def get_wall_execute( offset: int = 0, count: int = 10, max_count: int = 2500, - filter: str = "owner", + _filter: str = "owner", extended: int = 0, fields: tp.Optional[tp.List[str]] = None, progress=None, @@ -49,4 +49,48 @@ def get_wall_execute( :param fields: Список дополнительных полей для профилей и сообществ, которые необходимо вернуть. :param progress: Callback для отображения прогресса. """ - pass + code = f""" + if ({count} < 100)< + posts = API.wall.get( + < + owner_id:{owner_id}, + domain:{domain}, + offset:{offset}, + "count":"{count}", + filter:{_filter}, + extended:{extended}, + fields: {fields} + > + ); + > + else < + posts = []; + for(var i = 0; i < Math.floor({count} / 100) - 1; i++) < + p = API.wall.get( + < + owner_id:{owner_id}, + domain:{domain}, + offset:{offset}+ i * 100, + count: 100, + filter:{_filter}, + extended:{extended}, + fields: {fields} + > + ); + posts.push(...p); + > + > + return posts; + """.replace( + "<", "{" + ).replace( + ">", "}" + ) + time.sleep(2) + resp = session.post( + "/execute", + code=code, + access_token=config.VK_CONFIG["access_token"], + v=config.VK_CONFIG["version"], + ) + return json_normalize(resp.json()["response"]["items"]) From d2aa9edefb03e63073cd956eacb6db3734fae1f2 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 25 Nov 2022 18:06:49 +0300 Subject: [PATCH 32/41] Fixed imports --- homework05/research/network.py | 1 - homework05/research/topic_modeling.py | 1 - homework05/tests/tests_api/test_friends.py | 1 - homework05/tests/tests_api/test_session.py | 1 - homework05/tests/tests_api/test_wall.py | 1 - homework05/tests/tests_research/test_age.py | 1 - homework05/tests/tests_research/test_network.py | 1 - homework05/vkapi/wall.py | 1 - 8 files changed, 8 deletions(-) diff --git a/homework05/research/network.py b/homework05/research/network.py index d489483..cc2eef7 100644 --- a/homework05/research/network.py +++ b/homework05/research/network.py @@ -5,7 +5,6 @@ import matplotlib.pyplot as plt import networkx as nx import pandas as pd - from vkapi.friends import get_friends, get_mutual_friends_many diff --git a/homework05/research/topic_modeling.py b/homework05/research/topic_modeling.py index 7be95e5..d46c011 100644 --- a/homework05/research/topic_modeling.py +++ b/homework05/research/topic_modeling.py @@ -3,7 +3,6 @@ from gensim.corpora import Dictionary from textacy import preprocessing from tqdm import tqdm - from vkapi.wall import get_wall_execute diff --git a/homework05/tests/tests_api/test_friends.py b/homework05/tests/tests_api/test_friends.py index d5493aa..96133f2 100644 --- a/homework05/tests/tests_api/test_friends.py +++ b/homework05/tests/tests_api/test_friends.py @@ -3,7 +3,6 @@ import unittest import responses - from vkapi.friends import FriendsResponse, get_friends, get_mutual diff --git a/homework05/tests/tests_api/test_session.py b/homework05/tests/tests_api/test_session.py index 895028b..9eebccb 100644 --- a/homework05/tests/tests_api/test_session.py +++ b/homework05/tests/tests_api/test_session.py @@ -4,7 +4,6 @@ import httpretty import responses from requests.exceptions import ConnectionError, HTTPError, ReadTimeout, RetryError - from vkapi.session import Session diff --git a/homework05/tests/tests_api/test_wall.py b/homework05/tests/tests_api/test_wall.py index 6a56a8d..8ab2c26 100644 --- a/homework05/tests/tests_api/test_wall.py +++ b/homework05/tests/tests_api/test_wall.py @@ -5,7 +5,6 @@ import pandas as pd import responses - from vkapi.wall import get_wall_execute diff --git a/homework05/tests/tests_research/test_age.py b/homework05/tests/tests_research/test_age.py index 404fd0a..53b941c 100644 --- a/homework05/tests/tests_research/test_age.py +++ b/homework05/tests/tests_research/test_age.py @@ -2,7 +2,6 @@ import unittest import responses - from research.age import age_predict diff --git a/homework05/tests/tests_research/test_network.py b/homework05/tests/tests_research/test_network.py index 3608565..e375473 100644 --- a/homework05/tests/tests_research/test_network.py +++ b/homework05/tests/tests_research/test_network.py @@ -2,7 +2,6 @@ import unittest import responses - from research.network import ego_network diff --git a/homework05/vkapi/wall.py b/homework05/vkapi/wall.py index 9859b45..3b80a16 100644 --- a/homework05/vkapi/wall.py +++ b/homework05/vkapi/wall.py @@ -5,7 +5,6 @@ import pandas as pd from pandas import json_normalize - from vkapi import config, session from vkapi.exceptions import APIError From a6974ccffba8d3da7dd252c8cdfd4b8318bba725 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 25 Nov 2022 19:07:12 +0300 Subject: [PATCH 33/41] done --- homework05/requirements.txt | 1 + homework05/vkapi/friends.py | 2 +- homework05/vkapi/wall.py | 13 ------------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/homework05/requirements.txt b/homework05/requirements.txt index a66935f..dbb9a8f 100644 --- a/homework05/requirements.txt +++ b/homework05/requirements.txt @@ -9,3 +9,4 @@ python-louvain gensim textacy pyLDAvis +stubs diff --git a/homework05/vkapi/friends.py b/homework05/vkapi/friends.py index b405afe..7405d98 100644 --- a/homework05/vkapi/friends.py +++ b/homework05/vkapi/friends.py @@ -12,7 +12,7 @@ @dataclasses.dataclass(frozen=True) class FriendsResponse: count: int - items: tp.Union[tp.List[int], tp.List[tp.Dict[str, tp.Any]]] + items: tp.List[tp.Dict[str, tp.Any]] def get_friends( diff --git a/homework05/vkapi/wall.py b/homework05/vkapi/wall.py index 3b80a16..2c39424 100644 --- a/homework05/vkapi/wall.py +++ b/homework05/vkapi/wall.py @@ -9,19 +9,6 @@ from vkapi.exceptions import APIError -def get_posts_2500( - owner_id: str = "", - domain: str = "", - offset: int = 0, - count: int = 10, - max_count: int = 2500, - filter: str = "owner", - extended: int = 0, - fields: tp.Optional[tp.List[str]] = None, -) -> tp.Dict[str, tp.Any]: - pass - - def get_wall_execute( owner_id: str = "", domain: str = "", From 5a34e017f69a11e9f5f8ca70bc83302cb6718caf Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 25 Nov 2022 19:09:16 +0300 Subject: [PATCH 34/41] done --- homework05/vkapi/friends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework05/vkapi/friends.py b/homework05/vkapi/friends.py index 7405d98..67a233c 100644 --- a/homework05/vkapi/friends.py +++ b/homework05/vkapi/friends.py @@ -12,7 +12,7 @@ @dataclasses.dataclass(frozen=True) class FriendsResponse: count: int - items: tp.List[tp.Dict[str, tp.Any]] + items: tp.List[tp.Dict[str, tp.Any]] def get_friends( From 1d5935d4a404d3fbc4f1446a8186ce8a91df8dd9 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 25 Nov 2022 19:12:23 +0300 Subject: [PATCH 35/41] Added missing library --- homework05/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework05/requirements.txt b/homework05/requirements.txt index dbb9a8f..98d4ab2 100644 --- a/homework05/requirements.txt +++ b/homework05/requirements.txt @@ -9,4 +9,4 @@ python-louvain gensim textacy pyLDAvis -stubs +types-requests From e1a8b2b3a820c2390f6deac8f653758d23da462f Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Fri, 25 Nov 2022 19:16:50 +0300 Subject: [PATCH 36/41] done --- homework05/vkapi/wall.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/homework05/vkapi/wall.py b/homework05/vkapi/wall.py index 2c39424..30b90d0 100644 --- a/homework05/vkapi/wall.py +++ b/homework05/vkapi/wall.py @@ -9,6 +9,19 @@ from vkapi.exceptions import APIError +def get_posts_2500( + owner_id: str = "", + domain: str = "", + offset: int = 0, + count: int = 10, + max_count: int = 2500, + filter: str = "owner", + extended: int = 0, + fields: tp.Optional[tp.List[str]] = None, +) -> tp.Dict[str, tp.Any]: + return {} + + def get_wall_execute( owner_id: str = "", domain: str = "", From 7b9237da2beccfd75297e6db3109c03e5be45a0a Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Sat, 10 Dec 2022 21:00:17 +0300 Subject: [PATCH 37/41] Done --- homework06/bayes.py | 47 +++++++++++++++++++++++++++++++++++----- homework06/db.py | 24 ++++++++++++++++++++ homework06/hackernews.py | 37 ++++++++++++++++++++++++------- homework06/scraputils.py | 24 ++++++++++++++++---- 4 files changed, 115 insertions(+), 17 deletions(-) diff --git a/homework06/bayes.py b/homework06/bayes.py index 821d1af..12a817e 100644 --- a/homework06/bayes.py +++ b/homework06/bayes.py @@ -1,17 +1,54 @@ +from collections import defaultdict +from math import log +import string + + +def clean(s): + translator = str.maketrans("", "", string.punctuation) + return s.translate(translator) + + class NaiveBayesClassifier: def __init__(self, alpha): - pass + self.alpha = alpha def fit(self, X, y): """ Fit Naive Bayes classifier according to X, y. """ - pass + self.total = defaultdict(int) + self.good = defaultdict(int) + self.maybe = defaultdict(int) + self.never = defaultdict(int) + for i in range(len(X)): + for word in clean(X[i]).split(): + word = word.lower().strip() + self.total[word] += 1 + if y[i] == "good": + self.good[word] += 1 + elif y[i] == "maybe": + self.maybe[word] += 1 + elif y[i] == "never": + self.never[word] += 1 def predict(self, X): """ Perform classification on an array of test vectors X. """ - pass + res = [] + for s in X: + values = {"good": log(0.33), "maybe": log(0.33), "never": log(0.33)} + for word in s.split(): + values["good"] += log((self.good[word] + self.alpha) / ( + self.total[word] + self.alpha * len(self.total))) + values["maybe"] += log((self.maybe[word] + self.alpha) / ( + self.total[word] + self.alpha * len(self.total))) + values["never"] += log((self.never[word] + self.alpha) / ( + self.total[word] + self.alpha * len(self.total))) + res.append(max(values, key=values.get)) + return res def score(self, X_test, y_test): """ Returns the mean accuracy on the given test data and labels. """ - pass - + accurate = 0 + predictions = self.predict(X_test) + for i in range(len(X_test)): + accurate += predictions[i] == y_test[i] + return accurate / len(X_test) diff --git a/homework06/db.py b/homework06/db.py index db04f8c..e9cad0e 100644 --- a/homework06/db.py +++ b/homework06/db.py @@ -19,4 +19,28 @@ class News(Base): points = Column(Integer) label = Column(String) + def add_many(records): + s = session() + for record in records: + if s.query(News).filter( + News.title == record["title"], + News.author == record["author"], + ).first() is None: + s.add(News(**record)) + s.commit() + + def get(count=30): + s = session() + return s.query(News).filter(News.label == None).limit(count).all() + + def get_labeled(): + s = session() + return s.query(News).filter(News.label != None).all() + + def add_label(news_id, label): + s = session() + record = s.query(News).get(news_id) + record.label = label + s.commit() + Base.metadata.create_all(bind=engine) diff --git a/homework06/hackernews.py b/homework06/hackernews.py index f48fe01..430ddff 100644 --- a/homework06/hackernews.py +++ b/homework06/hackernews.py @@ -2,35 +2,56 @@ route, run, template, request, redirect ) -from scrapper import get_news -from db import News, session +from scraputils import get_news +from db import News from bayes import NaiveBayesClassifier +@route("/") +def index(): + redirect("/news") + + @route("/news") def news_list(): - s = session() - rows = s.query(News).filter(News.label == None).all() + rows = News.get() return template('news_template', rows=rows) @route("/add_label/") def add_label(): - # PUT YOUR CODE HERE + News.add_label(request.query["id"], request.query["label"]) redirect("/news") @route("/update") def update_news(): - # PUT YOUR CODE HERE + news = get_news("https://news.ycombinator.com/newest") + News.add_many(news) redirect("/news") @route("/classify") def classify_news(): - # PUT YOUR CODE HERE + model = NaiveBayesClassifier(alpha=1) + x, y = [], [] + for record in News.get_labeled(): + x.append(record.title) + y.append(record.label) + model.fit(x, y) + not_labeled = News.get() + titles = [record.title for record in not_labeled] + predictions = model.predict(titles) + good, maybe, never = [], [], [] + for i in range(len(titles)): + if predictions[i] == "good": + good.append(not_labeled[i]) + elif predictions[i] == "maybe": + maybe.append(not_labeled[i]) + elif predictions[i] == "never": + never.append(not_labeled[i]) + return template("news_template", rows=good + maybe + never) if __name__ == "__main__": run(host="localhost", port=8080) - diff --git a/homework06/scraputils.py b/homework06/scraputils.py index 4721e18..88227b5 100644 --- a/homework06/scraputils.py +++ b/homework06/scraputils.py @@ -6,14 +6,31 @@ def extract_news(parser): """ Extract news from a given web page """ news_list = [] - # PUT YOUR CODE HERE - + titles = parser.findAll("span", class_="titleline") + subtexts = parser.findAll("span", class_="subline") + for title, subtext in zip(titles, subtexts): + news_list.append(form_record(title, subtext)) return news_list +def form_record(title, subtext): + main_link = title.find("a") + subtext_links = subtext.findAll("a") + record = { + "title": main_link.text, + "url": main_link["href"], + "points": subtext.find("span", class_="score").text.split()[0], + "author": subtext_links[0].text, + "comments": subtext_links[-1].text.split()[0], + } + if record["comments"] == "discuss": + record["comments"] = 0 + return record + + def extract_next_page(parser): """ Extract next page URL """ - # PUT YOUR CODE HERE + return parser.find("a", class_="morelink")["href"] def get_news(url, n_pages=1): @@ -29,4 +46,3 @@ def get_news(url, n_pages=1): news.extend(news_list) n_pages -= 1 return news - From e87e278b5153801b4bf3e421dcd3b687edd7b5df Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 14 Dec 2022 13:05:33 +0300 Subject: [PATCH 38/41] done --- homework06/bayes.py | 27 ++++++++++++++++----------- homework06/db.py | 24 ++++++++++++++++-------- homework06/hackernews.py | 10 ++++------ homework06/scraputils.py | 6 +++--- requirements.txt | 1 + 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/homework06/bayes.py b/homework06/bayes.py index 12a817e..bf73254 100644 --- a/homework06/bayes.py +++ b/homework06/bayes.py @@ -1,6 +1,6 @@ +import string from collections import defaultdict from math import log -import string def clean(s): @@ -9,12 +9,11 @@ def clean(s): class NaiveBayesClassifier: - def __init__(self, alpha): self.alpha = alpha def fit(self, X, y): - """ Fit Naive Bayes classifier according to X, y. """ + """Fit Naive Bayes classifier according to X, y.""" self.total = defaultdict(int) self.good = defaultdict(int) self.maybe = defaultdict(int) @@ -31,22 +30,28 @@ def fit(self, X, y): self.never[word] += 1 def predict(self, X): - """ Perform classification on an array of test vectors X. """ + """Perform classification on an array of test vectors X.""" res = [] for s in X: values = {"good": log(0.33), "maybe": log(0.33), "never": log(0.33)} for word in s.split(): - values["good"] += log((self.good[word] + self.alpha) / ( - self.total[word] + self.alpha * len(self.total))) - values["maybe"] += log((self.maybe[word] + self.alpha) / ( - self.total[word] + self.alpha * len(self.total))) - values["never"] += log((self.never[word] + self.alpha) / ( - self.total[word] + self.alpha * len(self.total))) + values["good"] += log( + (self.good[word] + self.alpha) + / (self.total[word] + self.alpha * len(self.total)) + ) + values["maybe"] += log( + (self.maybe[word] + self.alpha) + / (self.total[word] + self.alpha * len(self.total)) + ) + values["never"] += log( + (self.never[word] + self.alpha) + / (self.total[word] + self.alpha * len(self.total)) + ) res.append(max(values, key=values.get)) return res def score(self, X_test, y_test): - """ Returns the mean accuracy on the given test data and labels. """ + """Returns the mean accuracy on the given test data and labels.""" accurate = 0 predictions = self.predict(X_test) for i in range(len(X_test)): diff --git a/homework06/db.py b/homework06/db.py index e9cad0e..f73e762 100644 --- a/homework06/db.py +++ b/homework06/db.py @@ -1,15 +1,13 @@ -from sqlalchemy import Column, String, Integer +from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker - Base = declarative_base() engine = create_engine("sqlite:///news.db") session = sessionmaker(bind=engine) -class News(Base): +class News(Base): # type: ignore __tablename__ = "news" id = Column(Integer, primary_key=True) title = Column(String) @@ -19,28 +17,38 @@ class News(Base): points = Column(Integer) label = Column(String) + @staticmethod def add_many(records): s = session() for record in records: - if s.query(News).filter( - News.title == record["title"], - News.author == record["author"], - ).first() is None: + if ( + s.query(News) + .filter( + News.title == record["title"], + News.author == record["author"], + ) + .first() + is None + ): s.add(News(**record)) s.commit() + @staticmethod def get(count=30): s = session() return s.query(News).filter(News.label == None).limit(count).all() + @staticmethod def get_labeled(): s = session() return s.query(News).filter(News.label != None).all() + @staticmethod def add_label(news_id, label): s = session() record = s.query(News).get(news_id) record.label = label s.commit() + Base.metadata.create_all(bind=engine) diff --git a/homework06/hackernews.py b/homework06/hackernews.py index 430ddff..cd2210e 100644 --- a/homework06/hackernews.py +++ b/homework06/hackernews.py @@ -1,10 +1,8 @@ -from bottle import ( - route, run, template, request, redirect -) +from bottle import redirect, request, route, run, template -from scraputils import get_news -from db import News from bayes import NaiveBayesClassifier +from db import News +from scraputils import get_news @route("/") @@ -15,7 +13,7 @@ def index(): @route("/news") def news_list(): rows = News.get() - return template('news_template', rows=rows) + return template("news_template", rows=rows) @route("/add_label/") diff --git a/homework06/scraputils.py b/homework06/scraputils.py index 88227b5..460d716 100644 --- a/homework06/scraputils.py +++ b/homework06/scraputils.py @@ -3,7 +3,7 @@ def extract_news(parser): - """ Extract news from a given web page """ + """Extract news from a given web page""" news_list = [] titles = parser.findAll("span", class_="titleline") @@ -29,12 +29,12 @@ def form_record(title, subtext): def extract_next_page(parser): - """ Extract next page URL """ + """Extract next page URL""" return parser.find("a", class_="morelink")["href"] def get_news(url, n_pages=1): - """ Collect news from a given web page """ + """Collect news from a given web page""" news = [] while n_pages: print("Collecting data from page: {}".format(url)) diff --git a/requirements.txt b/requirements.txt index 868f6a2..e4a9be5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ isort==5.4.2 mypy==0.782 pylint==2.6.0 pytest==6.0.1 +types-requests From 44aa975fc5b7bd3a788d9ee64d518a604fdf2778 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 14 Dec 2022 13:07:27 +0300 Subject: [PATCH 39/41] done --- homework06/hackernews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework06/hackernews.py b/homework06/hackernews.py index cd2210e..34d03b9 100644 --- a/homework06/hackernews.py +++ b/homework06/hackernews.py @@ -1,6 +1,6 @@ +from bayes import NaiveBayesClassifier from bottle import redirect, request, route, run, template -from bayes import NaiveBayesClassifier from db import News from scraputils import get_news From cf0779861ddac9fb4e9de5891191d42780255e09 Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 14 Dec 2022 13:08:46 +0300 Subject: [PATCH 40/41] done --- homework06/hackernews.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homework06/hackernews.py b/homework06/hackernews.py index 34d03b9..ef99574 100644 --- a/homework06/hackernews.py +++ b/homework06/hackernews.py @@ -1,6 +1,5 @@ from bayes import NaiveBayesClassifier from bottle import redirect, request, route, run, template - from db import News from scraputils import get_news From dd3631413f2914cff18317f72c1b50700a9c357c Mon Sep 17 00:00:00 2001 From: Alexander Zakharchuk Date: Wed, 14 Dec 2022 13:16:59 +0300 Subject: [PATCH 41/41] added missing library --- homework06/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 homework06/requirements.txt diff --git a/homework06/requirements.txt b/homework06/requirements.txt new file mode 100644 index 0000000..82419b4 --- /dev/null +++ b/homework06/requirements.txt @@ -0,0 +1 @@ +types-requests