import unittest
import typing
_VARIANTS = [] # Сюда попадут все решения, которые нужно тестировать
# А это декоратор, которым нужно пометить все претенденты на решение
solution = lambda f: _VARIANTS.append(f) or f
EXAMPLES = [
('', ''),
('a', 'a1'),
('abc', 'a1b1c1'),
('aaaabbca', 'a4b2c1a1'),
]
@solution
def encode(inp: str) -> str:
"""Самое простое решение
Минусы: громоздко и многословно
(зато прозрачно и понятно, без магии и выкрутасов)
"""
if not inp:
return ''
count = 1
current = inp[0]
res = []
for c in inp[1:]:
if c == current:
count += 1
else:
res.append(f'{current}{count}')
count = 1
current = c
res.append(f'{current}{count}')
return ''.join(res)
def encode_stream(inp: typing.TextIO) -> typing.Iterator:
"""Потоковое решение.
С помощью него можно без затрат памяти кодировать файлы любого размера
Минусы: нет, если вам нужно закодировать гигабайты.
Ну или готов поспорить на эту тему, если вы не согласны.=)
"""
current = inp.read(1)
count = len(current)
while current:
c = inp.read(1)
if c == current:
count += 1
else:
yield f'{current}{count}'
current = c
count = len(current)
@solution
def encode_string_by_stream(inp: str) -> str:
"""Обёртка для использования потокового кодирования из строки"""
import io
return ''.join(encode_stream(io.StringIO(inp)))
@solution
def encode_elegant(s: str) -> str:
"""Довольно элегантное решение на словаре от @seven5674.
К сожалению в оригинальном варианте неверное, но я
исправил и отрефакторил.
Минусы: запутанное и непрозрчное, зато короткое"""
d = {}
g = 1
for c in s:
g = d.get((c, g), 0) and g or g + 1
d[c, g] = d.get((c, g), 0) + 1
return ''.join([f'{k[0]}{v}' for k, v in d.items()])
@solution
def encode_by_regexp(s: str) -> str:
"""Решение на регекспах от @seven5674,
я лишь чуть отформатировал и отрефакторил.
Минусы: регексп поведёт себя довольно непредсказуемо на больших данных,
к тому же регекспы читать умеют не все.
Но автор умеет в регекспы лучше, чем в питон, видимо с js пришел.
"""
import re
return ''.join(
f'{w}{len(l)}'
for l, w in
re.findall(r"((\w)+?(?!\2))", s)
)
#############################################################################
## Дальше идёт инфраструктура для тестирования решений
class Test(unittest.TestCase):
"""Автоматический тест решений.
Претенденты на решение должны быть помечены декоратором @solution
Примеры берутся из списка EXAMPLES.
"""
def closure(func, arg, res):
"""Временная функция, которая делает тест.
Она формирует каждый раз новую функуию-замыкание, которая будет тестировать
оережной кейс.
"""
def test(self):
f"""Тест функции {func.__name__}({arg!r})"""
self.assertEqual(func(arg), res, msg=f'Func {func.__name__}({arg!r})')
return test
# Перебираем все варианты реализаций:
for f in _VARIANTS:
# Перебираем все предлженные эталонные римеры:
for case_num, case in enumerate(EXAMPLES):
# Создаём новую функуию теста и добавляем ее в класс теста как метод.
locals()[f'test_{f.__name__}__{case_num}'] = closure(f, *case)
# Удаляем из контекста класса лишние переменные
del(closure, f, case_num, case)
if __name__ == '__main__':
unittest.main(verbosity=3)
import datetime
import itertools
from typing import NamedTuple
class Card(NamedTuple):
name: str = None
phys_damage: int = 0
hp: int = 0
mage_damage: int = 0
armor: int = 0
add_hp: int = 0
def __repr__(self):
"""repr у нас будет показывать только аргументы, которые отличаются от умолчательных"""
defaults = type(self)._field_defaults
params = ', '.join(
f'{k}={v!r}'
for k, v in self._asdict().items()
if k in defaults and v != defaults[k]
)
return f'{type(self).__name__}({params})'
def __str__(self):
return f'<{self.name}#{id(self)}>'
@property
def total_hp(self):
return self.hp + self.add_hp
@property
def group(self):
return max(
(self.mage_damage, 'Intelegence'),
(self.add_hp, 'Strength'),
(self.armor, 'Agility'),
)[1]
class Deck():
def __init__(self, deck_name="Колода", date=None, cards=None):
self.deck_name = deck_name
self.date = date or datetime.now()
self.cards = cards or []
def __iter__(self):
return iter(self.cards)
def by_group(self):
return itertools.groupby(self, lambda card: card.group)
my_deck = Deck("Моя колода", "24.05.2022", [
Card("Течис", phys_damage=32, hp=600, mage_damage=100),
Card("Земеля", phys_damage=40, hp=600, mage_damage=60),
Card("Рудге", phys_damage=80, hp=600, add_hp=150),
Card("Крип", phys_damage=60, hp=600, armor=10),
])
for group, cards in my_deck.by_group():
print(f'# {group}:')
for card in cards:
print(f'\t{card!r}')
itertools.product(*[words] * len(words))
def r(w=words, n=5):
if n > 1:
yield from ([i] + rr for i in w for rr in r(w=w, n=n-1))
else:
yield from ([i] for i in w)
for item in items:
try:
...
except KeyboardInterrupt:
raise
except Exception as e:
with open('errors.log', 'a') as log:
log.write(f'{e}')
# сюда же можно вывести трейсбэк, но я бы воспользовался стандартным логированием, чтобы не изобратать велосипед, однако его конфигурирование выходит за рамки данного отета.
def split0(arr: list) -> list:
res = [[]]
for x in arr:
res[-1].append(x) if x else res.append([])
return res
from itertools import takewhile
def split0(a):
it = iter(a)
return [[x for x in takewhile(bool, it)] for _ in range(a.count(0) + 1)]
years[1] += 1
range(*years)
month -= 1
year_increment, month = divmod(month + 1, 12)
month += 1
iter(range(3, 33))
), то получим итератор - инстанс класса range_iterator.