diff --git "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_1.py" "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_1.py" index 55755352..f164be21 100644 --- "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_1.py" +++ "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_1.py" @@ -10,3 +10,216 @@ то реализуйте ф-цию-декоратор и пусть она считает время И примените ее к двум своим функциям. """ +from functools import wraps +from time import perf_counter +import sys + + +def timer(fun): + @wraps(fun) + def wrapper_timer(*args): + t0 = perf_counter() + ret = fun(*args) + dt = perf_counter() - t0 + a0 = args[0] + # Чтобы, если аргументом является массив, + # печаталась бы только его длина + try: + desc = f"(<{len(a0)}>,...)" + except TypeError: + desc = f"({a0},...)" + print(f"{fun.__name__}{desc}: {dt:.4f} s") + return ret + return wrapper_timer + + +# Проверим скорость заполнения списка и словаря +# с помощью for и list comprehension +@timer +def fill_list(n): + ret = [] + for i in range(n): + ret.append(i) + + +@timer +def fill_list_comp(n): + [i for i in range(n)] + + +if sys.argv[1] == "1": + for k in (4, 5, 6, 7): + fill_list(10**k) + for k in (4, 5, 6, 7): + fill_list_comp(10**k) + +# Результаты: +# fill_list(10000,...): 0.0008 s +# fill_list(100000,...): 0.0075 s +# fill_list(1000000,...): 0.0737 s +# fill_list(10000000,...): 0.7417 s +# +# fill_list_comp(10000,...): 0.0004 s +# fill_list_comp(100000,...): 0.0044 s +# fill_list_comp(1000000,...): 0.0478 s +# fill_list_comp(10000000,...): 0.4943 s +# +# Видно, что list comprehension дает почти двукратное ускорение +# для списков. Это можно объяснить возможностью заранее выделить +# пямять для итогового списка и избежать перераспределения памяти. + + +@timer +def fill_dict(n): + ret = {} + for i in range(n): + ret[i] = True + + +@timer +def fill_dict_comp(n): + {i: True for i in range(n)} + + +if sys.argv[1] == "2": + for k in (4, 5, 6, 7): + fill_dict(10**k) + for k in (4, 5, 6, 7): + fill_dict_comp(10**k) + +# Результаты: +# fill_dict(10000,...): 0.0006 s +# fill_dict(100000,...): 0.0083 s +# fill_dict(1000000,...): 0.1145 s +# fill_dict(10000000,...): 1.0419 s +# +# fill_dict_comp(10000,...): 0.0006 s +# fill_dict_comp(100000,...): 0.0081 s +# fill_dict_comp(1000000,...): 0.0985 s +# fill_dict_comp(10000000,...): 0.9942 s + +# То, что list comprehension не приводит к ускорению +# заполнения словаря говорит о том, что перераспределение +# памяти происходит в любом случае, т.к. размер +# хеш-таблицы словаря изменяется из-за коллизий, +# которые невозможно предугадать заранее. + +# Время заполнения словаря с ключами в виде целых чисел +# отличается от времени заполнения равного по размеру списка +# всего на четверть. Поскольку для целого n справедливо +# условие hash(n)==n, при заполнении последовательностью +# range() dict и list становятся алгоритмически эквивалентны. +# +# Проверим, как изменится время заполнения для строчных ключей: + + +@timer +def fill_list_str(n): + ret = [] + for i in range(n): + ret.append(str(i)) + + +@timer +def fill_dict_str(n): + ret = {} + for i in range(n): + ret[str(i)] = True + + +if sys.argv[1] == "3": + for k in (4, 5, 6, 7): + fill_list_str(10**k) + for k in (4, 5, 6, 7): + fill_dict_str(10**k) + +# fill_list(10000,...): 0.0023 s +# fill_list(100000,...): 0.0270 s +# fill_list(1000000,...): 0.3104 s +# fill_list(10000000,...): 2.6481 s +# +# fill_dict(10000,...): 0.0027 s +# fill_dict(100000,...): 0.0339 s +# fill_dict(1000000,...): 0.4624 s +# fill_dict(10000000,...): 5.8220 s + +# Теперь разница составляет уже два раза. +# Предположительно это связано с большей вероятностью +# коллизий и, соответственно, большим необходимым +# объемом памяти: +# +# >>> n=10000000 +# >>> len(set(hash(i) % n for i in range(n))) +# 10000000 +# >>> len(set(hash(str(i)) % n for i in range(n))) +# 6319569 + + +@timer +def remove_first(lst): + lst.pop(0) + + +@timer +def insert_first(lst): + lst.insert(0, 0) + + +if sys.argv[1] == "4": + for k in (4, 5, 6, 7): + lst = list(range(10**k)) + remove_first(lst) + for k in (4, 5, 6, 7): + lst = list(range(10**k)) + insert_first(lst) + +# remove_first(<9999>,...): 0.0000 s +# remove_first(<99999>,...): 0.0001 s +# remove_first(<999999>,...): 0.0012 s +# remove_first(<9999999>,...): 0.0123 s +# insert_first(<10001>,...): 0.0000 s +# insert_first(<100001>,...): 0.0001 s +# insert_first(<1000001>,...): 0.0053 s +# insert_first(<10000001>,...): 0.0130 s +# +# Время удаления и добавления элемента в начало списка +# пропорционально его длине из-за того, что такое +# удаление вызывает перемещение блока памяти. + + +@timer +def list_append(lst): + lst.append(0) + + +@timer +def list_pop(lst): + lst.pop() + + +@timer +def dict_insert(d): + d[0] = 0 + + +@timer +def dict_del(d): + del d[0] + + +if sys.argv[1] == "5": + n = 10000000 + lst = list(range(n)) + d = {i: True for i in range(n)} + list_append(lst) + list_pop(lst) + dict_insert(d) + dict_del(d) + +# list.append(), list.pop(), dict[] и dict[]= по очевидной +# причине дают близкое к нулю время +# +# list_append(<10000001>,...): 0.0003 s +# list_pop(<10000000>,...): 0.0000 s +# dict_insert(<10000000>,...): 0.0000 s +# dict_del(<9999999>,...): 0.0000 s diff --git "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_2.py" "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_2.py" index 7877a7df..6759778b 100644 --- "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_2.py" +++ "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_2.py" @@ -15,3 +15,107 @@ Введите пароль еще раз для проверки: 123 Вы ввели правильный пароль """ + +import sqlite3 +import sys +from textwrap import dedent +from random import choice, randint +from hashlib import pbkdf2_hmac + + +# Класс для удобства работы с базой данных +class SQLite(): + def __init__(self, file='passwd.db'): + self.file = file + + def __enter__(self): + self.conn = sqlite3.connect(self.file) + self.conn.row_factory = sqlite3.Row + return self.conn.cursor() + + def __exit__(self, type, value, traceback): + self.conn.commit() + self.conn.close() + + +# Генерирует имя пользователя из букв, +# следит за чередованием гласных и согласных +def gen_login(maxlen=10): + SONANTS = 'aeiou' + CONSONANTS = 'bcdfghjklmnpqrstvwxyz' + i1 = randint(0, 1) + i2 = randint(i1 + 3, maxlen) + result = [] + for i in range(i1, i2+1): + arr = SONANTS if i % 2 == 0 else CONSONANTS + result.append(choice(arr)) + return "".join(result) + + +# Генерирует пароль из произвольных символов +def gen_passwd(n=5): + return "".join(chr(randint(33, 126)) for _ in range(n)) + + +# Генерирует таблицу (логин, пароль) +def gen_db(size=10): + return [ + (gen_login(), gen_passwd()) + for i in range(size) + ] + + +def hashit(login, passwd): + scrambled = "".join( + [x for i, c in enumerate(login) for x in (c, login[-i-1])]*3) + saltb = scrambled.encode() + passb = passwd.encode() + return pbkdf2_hmac( + hash_name='sha256', + password=passb, + salt=saltb, + iterations=100000 + ).hex() + + +def reset_db(): + db = gen_db() + with open("passwd.txt", "w") as fo: + for login, passwd in db: + fo.write(f"{login}\t{passwd}\n") + data = [ + (login, hashit(login, passwd)) + for login, passwd in db + ] + + with SQLite() as cur: + try: + cur.execute(dedent("""\ + DELETE FROM users""")) + except sqlite3.OperationalError: + cur.execute(dedent("""\ + CREATE TABLE users + (login text, hash text)""")) + + cur.executemany(dedent("""\ + INSERT INTO users + VALUES (?, ?)"""), data) + + +def check_passwd(login, passwd): + with SQLite() as cur: + cur.execute('SELECT * FROM users WHERE login=?', (login,)) + res = cur.fetchone() + if res is not None: + if hashit(login, passwd) == res[1]: + print("Успешный вход в систему") + return None + print("Неправильный логин или пароль") + + +if len(sys.argv) > 2: + check_passwd(sys.argv[1], sys.argv[2]) +elif len(sys.argv) > 1 and sys.argv[1] == "--reset": + reset_db() +else: + print("usage: task_2.py OR task_2.py --reset") diff --git "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_3.py" "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_3.py" index ca72e990..d11f3719 100644 --- "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_3.py" +++ "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_3.py" @@ -14,3 +14,20 @@ р а """ + + +def count_substrings(s): + bag = set() + for i in range(len(s)): + for j in range(i+1, len(s)+1): + bag.add(hash(s[i:j])) + return len(bag)-1 + + +for s in ("bbbb", "baba", "bcde"): + print(f"{s} -> {count_substrings(s)}") + +# ---output--- +# bbbb -> 3 +# baba -> 6 +# bcde -> 9 diff --git "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_4.py" "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_4.py" index 4c6a5b7e..cba2ccc5 100644 --- "a/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_4.py" +++ "b/\320\243\321\200\320\276\320\272 3. \320\237\321\200\320\260\320\272\321\202\320\270\321\207\320\265\321\201\320\272\320\276\320\265 \320\267\320\260\320\264\320\260\320\275\320\270\320\265/task_4.py" @@ -7,4 +7,90 @@ Подсказка: задачу решите обязательно с применением 'соленого' хеширования Можете условжнить задачу, реализовав ее через ООП -""" \ No newline at end of file +""" +import hashlib +from random import randint, seed + + +# Поскольку в этой задаче абстрактные объекты типа "хеш" или "словарь" +# наделяются конкретным смыслом (страница, url), логично ожидать, +# что класс будет моделировать процесс загрузки страницы. +# Это значит, что должны быть по крайней мере два метода. +# 1. Узнать, есть ли страница в кэше, если да, то получить +# из кэша содержимое. +# 2. Положить в кэш содержимое после успешной загрузки. +# +# Примечание: класс не защищен от коллизий. Грубо говоря, +# есть вероятность, что по запросу http://kremlin.ru +# будет показан http://whitehouse.gov или наоборот. +# Соль никак не защищает от этого, вернее, какие-то коллизии +# она убирает, но какие-то добавляет, и заранее это не определить, +# (иначе это уже не криптография). +class Cache: + def __init__(self, salt): + self.thecache = {} + self.salt = salt.encode() + + def get_hash(self, url): + return hashlib.sha256(url.encode()+self.salt).hexdigest() + + def __getitem__(self, url): + hash_ = self.get_hash(url) + return self.thecache.get(hash_) + + def __setitem__(self, url, text): + hash_ = self.get_hash(url) + self.thecache[hash_] = text + + +# Для симуляции процесса загрузки +def get_text(url): + return f"<Это текст страницы {url}>" + + +cache = Cache( + "Подсказка: задачу решите обязательно с применением 'соленого' хеширования") + +urls = [ + "http://yandex.ru", + "http://google.com", + "http://geekbrains.ru" +] + +# симулируем пользовательскую активность +seed(12) +order = [randint(0, 2) for _ in range(10)] +# [1, 1, 2, 2, 2, 1, 0, 1, 0, 1] + +for i in order: + url = urls[i] + text = cache[url] + if not text: + # загружаем страницу + text = get_text(url) + print("Страница ", url, "не найдена в кэше, загружаем ее:\n", text) + cache[url] = text + else: + print("Страница ", url, "есть в кэше, ее содержимое:\n", text) + +# ---output--- +# Страница http://google.com не найдена в кэше, загружаем ее: +# <Это текст страницы http://google.com> +# Страница http://google.com есть в кэше, ее содержимое: +# <Это текст страницы http://google.com> +# Страница http://geekbrains.ru не найдена в кэше, загружаем ее: +# <Это текст страницы http://geekbrains.ru> +# Страница http://geekbrains.ru есть в кэше, ее содержимое: +# <Это текст страницы http://geekbrains.ru> +# Страница http://geekbrains.ru есть в кэше, ее содержимое: +# <Это текст страницы http://geekbrains.ru> +# Страница http://google.com есть в кэше, ее содержимое: +# <Это текст страницы http://google.com> +# Страница http://yandex.ru не найдена в кэше, загружаем ее: +# <Это текст страницы http://yandex.ru> +# Страница http://google.com есть в кэше, ее содержимое: +# <Это текст страницы http://google.com> +# Страница http://yandex.ru есть в кэше, ее содержимое: +# <Это текст страницы http://yandex.ru> +# Страница http://google.com есть в кэше, ее содержимое: +# <Это текст страницы http://google.com>