Compare commits
36 Commits
af2733cc02
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d66ec02d68 | |||
| 9afd997a2e | |||
| d88d3af47e | |||
| 801b0cad33 | |||
| ee91d98383 | |||
| fc813b6d5c | |||
| fa8cfb2b5d | |||
| c38575df6e | |||
| 8c5cd8e3e9 | |||
| 48f083afaf | |||
| c22ef9e724 | |||
| e96d20343b | |||
| 1b7e8b8147 | |||
| 5145c20156 | |||
| 5906af0f69 | |||
| b34a6842aa | |||
| 102e8ce6bd | |||
| bb5b226c3d | |||
| 3d311f9423 | |||
| f9d8ec192b | |||
| 5ce4f0565f | |||
| d3ab154609 | |||
| 5181bc6fcb | |||
| aa5cfa0124 | |||
| 0d65b8a938 | |||
| 607a6cf870 | |||
| 774718b2cd | |||
| 4e887c7782 | |||
| 20898206a7 | |||
| c6dda969b4 | |||
| 6ce82afa41 | |||
| e9a4f02399 | |||
| daf84580e9 | |||
| 3922323b1b | |||
| 233a3e8134 | |||
| 046db9e827 |
11
chunker.py
Normal file
11
chunker.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
def chunker(it, n):
|
||||||
|
it = iter(it)
|
||||||
|
while 1:
|
||||||
|
chunk = tuple(x[1] for x in zip(range(n), it))
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
|
||||||
|
for chunk in chunker(range(25), 4):
|
||||||
|
print(list(chunk))
|
||||||
27
genb85exec.py
Normal file
27
genb85exec.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import base64
|
||||||
|
import sys
|
||||||
|
|
||||||
|
WIDTH = 80
|
||||||
|
PREFIX = 4
|
||||||
|
|
||||||
|
|
||||||
|
def split_encoded(bytes_str: str, width: int):
|
||||||
|
for i in range(0, len(bytes_str), width):
|
||||||
|
yield bytes_str[i : i + width]
|
||||||
|
|
||||||
|
|
||||||
|
sys.stdin.reconfigure(encoding="utf-8")
|
||||||
|
code = sys.stdin.read().strip()
|
||||||
|
code = str(base64.b85encode(code.encode("utf-8")))
|
||||||
|
code = code[:-1]
|
||||||
|
|
||||||
|
print(f"{' ' * PREFIX}code = (")
|
||||||
|
first = True
|
||||||
|
for line in split_encoded(code, WIDTH - PREFIX):
|
||||||
|
if first:
|
||||||
|
print(f"{' ' * (PREFIX + 4)} {line}'")
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
print(f"{' ' * (PREFIX + 4) } + b'{line}'")
|
||||||
|
print(f"{' ' * PREFIX})")
|
||||||
|
print(f"{' ' * PREFIX}exec(__import__('base64').b85decode(code))")
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
https://stepik.org/lesson/701989/step/11?unit=702090
|
https://stepik.org/lesson/701989/step/12?unit=702090
|
||||||
|
|
||||||
В нейронных сетях использую операцию под названием Max Pooling.
|
В нейронных сетях использую операцию под названием Max Pooling.
|
||||||
Суть ее состоит в сканировании прямоугольной таблицы чисел (матрицы)
|
Суть ее состоит в сканировании прямоугольной таблицы чисел (матрицы)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
https://stepik.org/lesson/701989/step/11?unit=702090
|
https://stepik.org/lesson/701990/step/5?unit=702091
|
||||||
|
|
||||||
Объявите класс Track (маршрут), объекты которого создаются командой:
|
Объявите класс Track (маршрут), объекты которого создаются командой:
|
||||||
track = Track(start_x, start_y)
|
track = Track(start_x, start_y)
|
||||||
|
|||||||
186
mod_oop/3.5_06_dimensions_shopitem.py
Normal file
186
mod_oop/3.5_06_dimensions_shopitem.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701990/step/6?unit=702091
|
||||||
|
|
||||||
|
Объявите класс Dimensions (габариты) с атрибутами:
|
||||||
|
MIN_DIMENSION = 10
|
||||||
|
MAX_DIMENSION = 10000
|
||||||
|
|
||||||
|
>>> Dimensions.MIN_DIMENSION, Dimensions.MAX_DIMENSION
|
||||||
|
(10, 10000)
|
||||||
|
|
||||||
|
Каждый объект класса Dimensions должен создаваться командой:
|
||||||
|
d3 = Dimensions(a, b, c) # a, b, c - габаритные размеры
|
||||||
|
Значения a, b, c должны сохраняться в локальных приватных атрибутах __a, __b, __c объектах этого класса.
|
||||||
|
>>> d3 = Dimensions(10, 20, 30)
|
||||||
|
>>> d3._Dimensions__a, d3._Dimensions__b, d3._Dimensions__c
|
||||||
|
(10, 20, 30)
|
||||||
|
|
||||||
|
Для изменения и доступа к приватным атрибутам в классе Dimensions должны быть объявлены объекты-свойства (property) с именами: a, b, c.
|
||||||
|
Причем, в момент присваивания нового значения должна выполняться проверка попадания числа в диапазон [MIN_DIMENSION; MAX_DIMENSION].
|
||||||
|
Если число не попадает, то оно игнорируется и существующее значение не меняется.
|
||||||
|
>>> d3.a, d3.b, d3.c
|
||||||
|
(10, 20, 30)
|
||||||
|
>>> d3.a = 9; d3.a
|
||||||
|
10
|
||||||
|
|
||||||
|
С объектами класса Dimensions должны выполняться следующие операторы сравнения:
|
||||||
|
dim1 >= dim2 # True, если объем dim1 больше или равен объему dim2
|
||||||
|
dim1 > dim2 # True, если объем dim1 больше объема dim2
|
||||||
|
dim1 <= dim2 # True, если объем dim1 меньше или равен объему dim2
|
||||||
|
dim1 < dim2 # True, если объем dim1 меньше объема dim2
|
||||||
|
>>> dim1, dim2, dim3 = Dimensions(10, 20, 30), Dimensions(20, 15, 20), Dimensions(10, 15, 35)
|
||||||
|
>>> dim1 > dim3, dim1 == dim2, dim2 != dim3, dim3 < dim2, dim2 >= dim3
|
||||||
|
(True, True, True, True, True)
|
||||||
|
|
||||||
|
Объявите в программе еще один класс с именем ShopItem (товар), объекты которого создаются командой:
|
||||||
|
item = ShopItem(name, price, dim)
|
||||||
|
где name - название товара (строка); price - цена товара (целое или вещественное число); dim - габариты товара (объект класса Dimensions).
|
||||||
|
>>> item = ShopItem('Кеды', 1024, Dimensions(40, 30, 120))
|
||||||
|
|
||||||
|
В каждом объекте класса ShopItem должны создаваться локальные атрибуты:
|
||||||
|
name - название товара;
|
||||||
|
price - цена товара;
|
||||||
|
dim - габариты товара (объект класса Dimensions).
|
||||||
|
>>> item.name, item.price, item.dim
|
||||||
|
('Кеды', 1024, Dimensions(40, 30, 120))
|
||||||
|
|
||||||
|
Создайте список с именем lst_shop из четырех товаров со следующими данными:
|
||||||
|
- кеды; 1024; (40, 30, 120)
|
||||||
|
- зонт; 500.24; (10, 20, 50)
|
||||||
|
- холодильник; 40000; (2000, 600, 500)
|
||||||
|
- табуретка; 2000.99; (500, 200, 200)
|
||||||
|
>>> lst_shop[:2]
|
||||||
|
[ShopItem('кеды', 1024, Dimensions(40, 30, 120)), ShopItem('зонт', 500.24, Dimensions(10, 20, 50))]
|
||||||
|
>>> lst_shop[2:]
|
||||||
|
[ShopItem('холодильник', 40000, Dimensions(2000, 600, 500)), ShopItem('табуретка', 2000.99, Dimensions(500, 200, 200))]
|
||||||
|
|
||||||
|
Сформируйте новый список lst_shop_sorted с упорядоченными по возрастанию объема (габаритов) товаров списка lst_shop,
|
||||||
|
используя стандартную функцию sorted() языка Python и ее параметр key для настройки сортировки.
|
||||||
|
Прежний список lst_shop должен оставаться без изменений.
|
||||||
|
>>> lst_shop_sorted[:2]
|
||||||
|
[ShopItem('зонт', 500.24, Dimensions(10, 20, 50)), ShopItem('кеды', 1024, Dimensions(40, 30, 120))]
|
||||||
|
>>> lst_shop_sorted[2:]
|
||||||
|
[ShopItem('табуретка', 2000.99, Dimensions(500, 200, 200)), ShopItem('холодильник', 40000, Dimensions(2000, 600, 500))]
|
||||||
|
|
||||||
|
P.S. На экран в программе ничего выводить не нужно.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
|
|
||||||
|
def make_properties(*names):
|
||||||
|
def decorator(cls):
|
||||||
|
def prop(private_name: str):
|
||||||
|
def getter(self):
|
||||||
|
return getattr(self, private_name)
|
||||||
|
|
||||||
|
def setter(self, value):
|
||||||
|
return setattr(self, private_name, value)
|
||||||
|
|
||||||
|
return getter, setter
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
setattr(cls, name, property(*prop(f"_{cls.__name__}__{name}")))
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
@make_properties(*"abc")
|
||||||
|
class Dimensions:
|
||||||
|
MIN_DIMENSION = 10
|
||||||
|
MAX_DIMENSION = 10000
|
||||||
|
|
||||||
|
def __init__(self, a, b, c):
|
||||||
|
self.a, self.b, self.c = a, b, c
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name in ["MIN_DIMENSION", "MAX_DIMENSION"]:
|
||||||
|
raise AttributeError(
|
||||||
|
"Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено."
|
||||||
|
)
|
||||||
|
if not self.MIN_DIMENSION <= value <= self.MAX_DIMENSION:
|
||||||
|
return
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume(self):
|
||||||
|
return self.a * self.b * self.c
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.volume
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.volume == other.volume
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.volume < other.volume
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self.a, self.b, self.c)!r}"
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class ShopItem:
|
||||||
|
def __init__(self, name, price, dim):
|
||||||
|
self.name, self.price, self.dim = name, price, dim
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self.name, self.price, self.dim)!r}"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.dim == other.dim
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.dim < other.dim
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
data = [
|
||||||
|
("кеды", 1024, (40, 30, 120)),
|
||||||
|
("зонт", 500.24, (10, 20, 50)),
|
||||||
|
("холодильник", 40000, (2000, 600, 500)),
|
||||||
|
("табуретка", 2000.99, (500, 200, 200)),
|
||||||
|
]
|
||||||
|
|
||||||
|
lst_shop = [ShopItem(name, price, Dimensions(*dim)) for name, price, dim in data]
|
||||||
|
lst_shop_sorted = sorted(lst_shop)
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
from base64 import b85decode
|
||||||
|
|
||||||
|
b = (
|
||||||
|
b"VRLh3a&#bUWo{^Jb97&GXm4;SAU!=GG%O$@(TC8u(Sgvr(7qtijnKQ$wa~oKwa~rMg3!LuvLMj1AZ&AVUvp@0a3IjV"
|
||||||
|
+ b"(6u1ZfY7keve3QIz92Lr3JPp<bYF9DAUz;kT?%Y-bYF9DE@5zRWo~3BQ)q8+NpxjxC@0Xm(6!LC(TgW6ATcm9G%O%QX>Db"
|
||||||
|
+ b"0b7^mGb0{=0EFd#5EFdv5Fexc13T$(9UvqFSVQ_F|Ze%D^Xm4;ybY*QQC(yUhzR<nVf+s8>H83zPGBhk8L}_hhZgXjGZgV"
|
||||||
|
+ b"IxFf1T4Ff1T7Fexc13T$(9UvqFSVQ_F|Ze%D^Xm4;ybY*QQC((t_zR<hSzR<MLxX`=NjL^N%xX`*MEFd&6FfcGIAVg_xWo"
|
||||||
|
+ b"~n6Z*FraGB7YOEFd;8Ff1T7Ffb`8DGF?JbYF9DE@5zRWo~3BQ)q8+NpxjxC@0Z^(6G?4(Sy-|(6!Nm(7MpDCoCW`FfcGKI"
|
||||||
|
+ b"XNsKL}_hhZgXjGZgVI#Ffc42GB7YKATls8DJdxm3T$(9UvqF@b8m8VWn>^dAX_KUx6r=Oz0ravEFdS)y3n=Iw9$(vEFdS*"
|
||||||
|
+ b"g3z$gvC)IkfY7zkg3!9quqP}aC((t_zR<hSzR<MLxX`=NjL^N%xX`*MT?%s`Js?|nE^c9MWgup6av*phX>K5Fb97&GXm4;"
|
||||||
|
+ b"|b8m8VWn^6nVRLh3a&#bUb97&Ga9?w8a&%>6AU!=Gb1Wbt(Sgvv(74fo(7w>RAZ&AVUvp@0a9?w8a&%>6Akl%*gwVdxfY7"
|
||||||
|
+ b"|qxY2;nzR<GJu+Y6A(7n*L(6Z3A(SXps(7qxH3S==LJs?DBZDnqAX>V?GC^RrEATuy5ATcs9DGFpVAUz;NX>Db0b7^mGb0"
|
||||||
|
+ b"{=0EFd#5EFdv5FewUTGax-6L}_hhZgXjGZgVIzFf1T4Ff1T3Ffb_!VRLh3a&#bMF(5oWAY?KuAR^Gc(6!LA(6!Nk(7n*UA"
|
||||||
|
+ b"ke<ig3*A`u+Xv4zR`lvu+Y08(7w>W(6!Nk(6G^h(7w@tAUr)H3So0|WpZ>NWHTT<Js@N<EFdD#z0kGLve32BfY80rz97)P"
|
||||||
|
+ b"(Sp%{(6G?4(7w@v(6G?EAke<hztFYOfY7kfg3!LvfFL|QA_`%1b7gXLAY?NjJRoE;EFdD#z0kGLve32BfY80rz97)P(Sp%"
|
||||||
|
+ b"{(6G?4(7w@v(6G?EAke<hztFYOfY7kfg3!LvfFL{~3JPR0E@2=&ATclsWHK&dAUz;4FbZTcE@L1)ATclsVRLh3a&#bMG9W"
|
||||||
|
+ b"x4WHBrtBGA3iwa~KAwb6jkz0kfO(7w@v(SXpf(6P|I(Sp#h(7PbezR<tWwb6jku+f6hzR`dnJRs1&(7w@u(7VvJAketbx6"
|
||||||
|
+ b"r)Mwa~rLwa~rLxY3Uw(7w>I(TdQu(7e#FAkl}=wb6jkwa~X9(7w>I(TdQu(7Mrr(Tgq7fzYzhzR<bRfzg7{ve2*~VJskGE"
|
||||||
|
+ b"Ffbd3I"
|
||||||
|
)
|
||||||
|
exec(b85decode(b))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
166
mod_oop/3.5_08_morph.py
Normal file
166
mod_oop/3.5_08_morph.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701990/step/8?unit=702091
|
||||||
|
|
||||||
|
Ваша задача написать программу поиска слова в строке. Задача усложняется тем, что слово должно определяться в разных его формах. Например, слово:
|
||||||
|
программирование
|
||||||
|
может иметь следующие формы:
|
||||||
|
программирование, программированию, программированием, программировании, программирования, программированиям, программированиями, программированиях
|
||||||
|
|
||||||
|
Для решения этой задачи необходимо объявить класс Morph (морфология), объекты которого создаются командой:
|
||||||
|
mw = Morph(word1, word2, ..., wordN)
|
||||||
|
где word1, word2, ..., wordN - возможные формы слова.
|
||||||
|
|
||||||
|
В классе Morph реализовать методы:
|
||||||
|
add_word(self, word) - добавление нового слова (если его нет в списке слов объекта класса Morph);
|
||||||
|
get_words(self) - получение кортежа форм слов.
|
||||||
|
|
||||||
|
Также с объектами класса Morph должны выполняться следующие операторы сравнения:
|
||||||
|
mw1 == "word" # True, если объект mv1 содержит слово "word" (без учета регистра)
|
||||||
|
mw1 != "word" # True, если объект mv1 не содержит слово "word" (без учета регистра)
|
||||||
|
И аналогичная пара сравнений:
|
||||||
|
"word" == mw1
|
||||||
|
"word" != mw1
|
||||||
|
|
||||||
|
После создания класса Morph, формируется список dict_words из объектов этого класса, для следующих слов с их словоформами:
|
||||||
|
- связь, связи, связью, связей, связям, связями, связях
|
||||||
|
- формула, формулы, формуле, формулу, формулой, формул, формулам, формулами, формулах
|
||||||
|
- вектор, вектора, вектору, вектором, векторе, векторы, векторов, векторам, векторами, векторах
|
||||||
|
- эффект, эффекта, эффекту, эффектом, эффекте, эффекты, эффектов, эффектам, эффектами, эффектах
|
||||||
|
- день, дня, дню, днем, дне, дни, дням, днями, днях
|
||||||
|
|
||||||
|
Затем, прочитайте строку из входного потока командой:
|
||||||
|
text = input()
|
||||||
|
Найдите все вхождения слов из списка dict_words (используя операторы сравнения) в строке text (без учета регистра, знаков пунктуаций и их словоформы). Выведите на экран полученное число.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
sys.stdin.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def run_test(request, expected, num=0, count=0):
|
||||||
|
test_id = "" if num == 0 else f" #{num}" + (f"of {count}" if count else "")
|
||||||
|
|
||||||
|
p = subprocess.run(
|
||||||
|
[sys.executable, __file__],
|
||||||
|
input=f"{request}\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if p.stderr:
|
||||||
|
print("StdErr:\n", p.stderr, file=sys.stderr)
|
||||||
|
p.check_returncode()
|
||||||
|
|
||||||
|
answer = (p.stderr + p.stdout).strip()
|
||||||
|
assert (
|
||||||
|
answer == expected
|
||||||
|
), f"""\nFailed test{test_id}. Wrong answer
|
||||||
|
|
||||||
|
This is a sample test from the problem statement!
|
||||||
|
|
||||||
|
Test input:
|
||||||
|
{request}
|
||||||
|
|
||||||
|
Correct output:
|
||||||
|
{expected}
|
||||||
|
|
||||||
|
|
||||||
|
Your code output:
|
||||||
|
{answer}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def stdin_tests():
|
||||||
|
tests = [
|
||||||
|
("Мы будем устанавливать связь завтра днем.", "2"),
|
||||||
|
("Завтра после полудня мы установим контакт.", "0"),
|
||||||
|
("Напишите формулу L1-нормы вектора.", "2"),
|
||||||
|
("Ф:о@рм-у-л.а за формулой, д.е.н.ь за днем", "4"),
|
||||||
|
]
|
||||||
|
for i, test in enumerate(tests):
|
||||||
|
run_test(*test, i, len(tests))
|
||||||
|
|
||||||
|
|
||||||
|
if sys.argv[-1] == "test":
|
||||||
|
stdin_tests()
|
||||||
|
exit()
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class Morph:
|
||||||
|
def __init__(self, *words):
|
||||||
|
self.words = set(map(str.lower, words))
|
||||||
|
# тесты просят сохранять порядок и дубликаты для get_words
|
||||||
|
# но только для исходных данных
|
||||||
|
# немножко возражаю, хочу set!
|
||||||
|
self.words_for_tests = list(map(str.lower, words))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({', '.join(map(repr, self.words))})"
|
||||||
|
|
||||||
|
def add_word(self, word):
|
||||||
|
l0 = len(self)
|
||||||
|
self.words.add(word.lower())
|
||||||
|
if len(self) > l0:
|
||||||
|
self.words_for_tests.append(word.lower())
|
||||||
|
|
||||||
|
def get_words(self):
|
||||||
|
return tuple(self.words_for_tests)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.words)
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return self + other
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if hasattr(other, "words"):
|
||||||
|
return self.__class__(*self.words, *other.words)
|
||||||
|
if isinstance(other, str):
|
||||||
|
return self.__class__(*self.words, *other.split())
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __contains__(self, other):
|
||||||
|
return self == other
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if hasattr(other, "words"):
|
||||||
|
return self.words == other.words
|
||||||
|
if isinstance(other, str):
|
||||||
|
return other.lower() in self.words
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if hasattr(other, "words"):
|
||||||
|
return self.words < other.words
|
||||||
|
if isinstance(other, str):
|
||||||
|
return other.lower() in self.words
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if hasattr(other, "words"):
|
||||||
|
return self.words > other.words
|
||||||
|
if isinstance(other, str):
|
||||||
|
return False
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
data = """\
|
||||||
|
- связь, связи, связью, связей, связям, связями, связях
|
||||||
|
- формула, формулы, формуле, формулу, формулой, формул, формулам, формулами, формулах
|
||||||
|
- вектор, вектора, вектору, вектором, векторе, векторы, векторов, векторам, векторами, векторах
|
||||||
|
- эффект, эффекта, эффекту, эффектом, эффекте, эффекты, эффектов, эффектам, эффектами, эффектах
|
||||||
|
- день, дня, дню, днем, дне, дни, дням, днями, днях\
|
||||||
|
"""
|
||||||
|
sanitizer = str.maketrans({k: "" for k in "–?!,.:;()+-*^%$#@№/\\<>"})
|
||||||
|
dict_words = [*map(lambda x: Morph(*x.translate(sanitizer).split()), data.split("\n"))]
|
||||||
|
all_words = sum(dict_words, Morph())
|
||||||
|
|
||||||
|
text = input() # эту строчку не менять
|
||||||
|
|
||||||
|
words = [*map(lambda x: x.translate(sanitizer), text.split())]
|
||||||
|
print(sum(word in all_words for word in words))
|
||||||
158
mod_oop/3.5_10_centralbank.py
Normal file
158
mod_oop/3.5_10_centralbank.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701990/step/10?unit=702091
|
||||||
|
|
||||||
|
В программе необходимо объявить классы для работы с кошельками в разных валютах:
|
||||||
|
MoneyR - для рублевых кошельков
|
||||||
|
MoneyD - для долларовых кошельков
|
||||||
|
MoneyE - для евро-кошельков
|
||||||
|
|
||||||
|
Объекты этих классов могут создаваться командами:
|
||||||
|
rub = MoneyR() # с нулевым балансом
|
||||||
|
dl = MoneyD(1501.25) # с балансом в 1501.25 долларов
|
||||||
|
euro = MoneyE(100) # с балансом в 100 евро
|
||||||
|
>>> rub = MoneyR()
|
||||||
|
>>> dl = MoneyD(1501.25)
|
||||||
|
>>> euro = MoneyE(100)
|
||||||
|
>>> rub.volume, dl.volume, euro.volume
|
||||||
|
(0, 1501.25, 100)
|
||||||
|
|
||||||
|
В каждом объекте этих классов должны формироваться локальные атрибуты:
|
||||||
|
__cb - ссылка на класс CentralBank (центральный банк, изначально None);
|
||||||
|
__volume - объем денежных средств в кошельке (если не указано, то 0).
|
||||||
|
|
||||||
|
Также в классах MoneyR, MoneyD и MoneyE должны быть объекты-свойства (property) для работы с локальными атрибутами:
|
||||||
|
cb - для изменения и считывания данных из переменной __cb;
|
||||||
|
volume - для изменения и считывания данных из переменной __volume.
|
||||||
|
|
||||||
|
Объекты классов должны поддерживать следующие операторы сравнения:
|
||||||
|
rub < dl
|
||||||
|
dl >= euro
|
||||||
|
rub == euro # значения сравниваются по текущему курсу центрального банка с погрешностью 0.1 (плюс-минус)
|
||||||
|
euro > rub
|
||||||
|
При реализации операторов сравнения считываются соответствующие значения __volume из сравниваемых объектов и приводятся к рублевому эквиваленту в соответствии с курсом валют центрального банка.
|
||||||
|
|
||||||
|
Чтобы каждый объект классов MoneyR, MoneyD и MoneyE "знал" текущие котировки, необходимо в программе объявить еще один класс CentralBank.
|
||||||
|
Объекты класса CentralBank создаваться не должны (запретить), при выполнении команды:
|
||||||
|
>>> CentralBank()
|
||||||
|
|
||||||
|
должно просто возвращаться значение None. А в самом классе должен присутствовать атрибут:
|
||||||
|
|
||||||
|
rates = {'rub': 72.5, 'dollar': 1.0, 'euro': 1.15}
|
||||||
|
>>> CentralBank.rates
|
||||||
|
{'rub': 72.5, 'dollar': 1.0, 'euro': 1.15}
|
||||||
|
|
||||||
|
Здесь числа (в значениях словаря) - курс валюты по отношению к доллару.
|
||||||
|
|
||||||
|
Также в CentralBank должен быть метод уровня класса:
|
||||||
|
register(cls, money) - для регистрации объектов классов MoneyR, MoneyD и MoneyE.
|
||||||
|
|
||||||
|
При регистрации значение __cb объекта money должно ссылаться на класс CentralBank.
|
||||||
|
Через эту переменную объект имеет возможность обращаться к атрибуту rates класса CentralBank и брать нужные котировки.
|
||||||
|
|
||||||
|
Если кошелек не зарегистрирован, то при операциях сравнения должно генерироваться исключение:
|
||||||
|
raise ValueError("Неизвестен курс валют.")
|
||||||
|
Пример использования классов (эти строчки в программе писать не нужно):
|
||||||
|
|
||||||
|
>>> CentralBank.rates = {'rub': 72.5, 'dollar': 1.0, 'euro': 1.15}
|
||||||
|
>>> r = MoneyR(45000)
|
||||||
|
>>> d = MoneyD(500)
|
||||||
|
>>> CentralBank.register(r)
|
||||||
|
>>> CentralBank.register(d)
|
||||||
|
>>> r > d, r.value, d.value
|
||||||
|
(True, 620.6896551724138, 500.0)
|
||||||
|
|
||||||
|
if r > d:
|
||||||
|
print("неплохо")
|
||||||
|
else:
|
||||||
|
print("нужно поднажать")
|
||||||
|
|
||||||
|
>>> r2 = MoneyR(100)
|
||||||
|
>>> r2 > r
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: Неизвестен курс валют.
|
||||||
|
|
||||||
|
P.S. В программе на экран ничего выводить не нужно, только объявить классы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
|
|
||||||
|
class CentralBank:
|
||||||
|
rates = {"rub": 72.5, "dollar": 1.0, "euro": 1.15}
|
||||||
|
|
||||||
|
def __new__(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls, obj):
|
||||||
|
obj.register_cb(cls)
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class Wallet:
|
||||||
|
currency: str
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.volume!r})"
|
||||||
|
|
||||||
|
def register_cb(self, cb: type):
|
||||||
|
self.cb = cb
|
||||||
|
|
||||||
|
def __init__(self, volume=0):
|
||||||
|
self.cb = None
|
||||||
|
self.volume = volume
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if hasattr(other, "value"):
|
||||||
|
return abs(self.value - other.value) <= 0.1
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if hasattr(other, "value"):
|
||||||
|
return abs(self.value - other.value) > 0.1 and self.value < other.value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cb(self):
|
||||||
|
return getattr(self, f"_{self.__class__.__name__}__cb")
|
||||||
|
|
||||||
|
@cb.setter
|
||||||
|
def cb(self, value):
|
||||||
|
setattr(self, f"_{self.__class__.__name__}__cb", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume(self):
|
||||||
|
return getattr(self, f"_{self.__class__.__name__}__volume")
|
||||||
|
|
||||||
|
@volume.setter
|
||||||
|
def volume(self, value):
|
||||||
|
setattr(self, f"_{self.__class__.__name__}__volume", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rate(self):
|
||||||
|
if not self.cb:
|
||||||
|
raise ValueError("Неизвестен курс валют.")
|
||||||
|
return self.cb.rates[self.currency]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.volume / self.rate
|
||||||
|
|
||||||
|
|
||||||
|
class MoneyR(Wallet):
|
||||||
|
currency = "rub"
|
||||||
|
|
||||||
|
|
||||||
|
class MoneyD(Wallet):
|
||||||
|
currency = "dollar"
|
||||||
|
|
||||||
|
|
||||||
|
class MoneyE(Wallet):
|
||||||
|
currency = "euro"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
162
mod_oop/3.6_09_database.py
Normal file
162
mod_oop/3.6_09_database.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701991/step/9?unit=702092
|
||||||
|
|
||||||
|
Объявите класс с именем DataBase (база данных - БД), объекты которого создаются командой:
|
||||||
|
|
||||||
|
db = DataBase(path)
|
||||||
|
где path - путь к файлу с данными БД (строка).
|
||||||
|
|
||||||
|
Также в классе DataBase нужно объявить следующие методы:
|
||||||
|
|
||||||
|
write(self, record) - для добавления новой записи в БД, представленной объектом record;
|
||||||
|
read(self, pk) - чтение записи из БД (возвращает объект Record) по ее уникальному идентификатору pk (уникальное целое положительное число); запись ищется в значениях словаря (см. ниже)
|
||||||
|
|
||||||
|
Каждая запись БД должна описываться классом Record, а объекты этого класса создаваться командой:
|
||||||
|
|
||||||
|
record = Record(fio, descr, old)
|
||||||
|
где fio - ФИО некоторого человека (строка); descr - характеристика человека (строка); old - возраст человека (целое число).
|
||||||
|
|
||||||
|
В каждом объекте класса Record должны формироваться следующие локальные атрибуты:
|
||||||
|
|
||||||
|
pk - уникальный идентификатор записи (число: целое, положительное); формируется автоматически при создании каждого нового объекта;
|
||||||
|
fio - ФИО человека (строка);
|
||||||
|
descr - характеристика человека (строка);
|
||||||
|
old - возраст человека (целое число).
|
||||||
|
|
||||||
|
Реализовать для объектов класса Record вычисление хэша по атрибутам: fio и old (без учета регистра). Если они одинаковы для разных записей, то и хэши должны получаться равными. Также для объектов класса Record с одинаковыми хэшами оператор == должен выдавать значение True, а с разными хэшами - False.
|
||||||
|
|
||||||
|
Хранить записи в БД следует в виде словаря dict_db (атрибут объекта db класса DataBase), ключами которого являются объекты класса Record, а значениями список из объектов с равными хэшами:
|
||||||
|
|
||||||
|
dict_db[rec1] = [rec1, rec2, ..., recN]
|
||||||
|
|
||||||
|
где rec1, rec2, ..., recN - объекты класса Record с одинаковыми хэшами.
|
||||||
|
|
||||||
|
Для наполнения БД прочитайте строки из входного потока с помощью команды:
|
||||||
|
|
||||||
|
lst_in = list(map(str.strip, sys.stdin.readlines()))
|
||||||
|
где каждая строка представлена в формате:
|
||||||
|
|
||||||
|
"ФИО; характеристика; возраст"
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
Балакирев С.М.; программист; 33
|
||||||
|
Кузнецов А.В.; разведчик-нелегал; 35
|
||||||
|
Суворов А.В.; полководец; 42
|
||||||
|
Иванов И.И.; фигурант всех подобных списков; 26
|
||||||
|
Балакирев С.М.; преподаватель; 37
|
||||||
|
|
||||||
|
Каждая строка должна быть представлена объектом класса Record и записана в БД db (в словарь db.dict_db).
|
||||||
|
|
||||||
|
P.S. На экран ничего выводить не нужно.
|
||||||
|
|
||||||
|
Sample Input:
|
||||||
|
Балакирев С.М.; программист; 33
|
||||||
|
Кузнецов Н.И.; разведчик-нелегал; 35
|
||||||
|
Суворов А.В.; полководец; 42
|
||||||
|
Иванов И.И.; фигурант всех подобных списков; 26
|
||||||
|
Балакирев С.М.; преподаватель; 33
|
||||||
|
Sample Output:
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
# здесь объявляйте классы
|
||||||
|
class Record:
|
||||||
|
__pk: int = 0
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
cls.__pk += 1
|
||||||
|
return super().__new__(cls)
|
||||||
|
|
||||||
|
def __init__(self, fio: str, descr: str, old: int):
|
||||||
|
self.fio, self.descr, self.old = fio, descr, old
|
||||||
|
self.__pk = self.__pk
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self.fio, self.descr, self.old)!r}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pk(self) -> int:
|
||||||
|
return self.__pk
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.fio.lower(), self.old))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return hash(self) == hash(other)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return hash(self) != hash(other)
|
||||||
|
|
||||||
|
|
||||||
|
class DataBase:
|
||||||
|
def __init__(self, path: str):
|
||||||
|
self.path = path
|
||||||
|
self.dict_db = {}
|
||||||
|
self.__records_by_pk = {}
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.path!r})"
|
||||||
|
|
||||||
|
def read(self, pk) -> Record:
|
||||||
|
return self.__records_by_pk[pk]
|
||||||
|
|
||||||
|
def write(self, record: Record):
|
||||||
|
self.__records_by_pk[record.pk] = record
|
||||||
|
self.dict_db.setdefault(record, []).append(record)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.argv[-1] == "test":
|
||||||
|
lst_in = [
|
||||||
|
"Балакирев С.М.; программист; 33",
|
||||||
|
"Кузнецов Н.И.; разведчик-нелегал; 35",
|
||||||
|
"Суворов А.В.; полководец; 42",
|
||||||
|
"Иванов И.И.; фигурант всех подобных списков; 26",
|
||||||
|
"Балакирев С.М.; преподаватель; 33",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# считывание списка из входного потока
|
||||||
|
lst_in = list(map(str.strip, sys.stdin.readlines())) # список lst_in не менять!
|
||||||
|
|
||||||
|
# здесь продолжайте программу (используйте список строк lst_in)
|
||||||
|
db = DataBase("/dev/null")
|
||||||
|
for name, descr, old in map(lambda x: x.split("; "), lst_in):
|
||||||
|
db.write(Record(name, descr, int(old)))
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"WFS2tbailSWhi7~E@WwAbYEm*E_PvTb!BrXDJfepU0X0+3So0|WpZ>Nba`-PC}b{VWpiV4DIh&"
|
||||||
|
+ b"PAaitbAYpD~Aar?fWhi7WW@&FJAU!=Gb98bbVQyp~ba`-PC}b{gY-A}QJv|_4ZgealBG9+cz0k1H"
|
||||||
|
+ b"htRdqz0kPPkI=l(xFFGi(7VvS(6Z35(SXs9AkehXzR<hSw$QcEy&%xB(TmZ7(TpI`fzZFuxY2>oz"
|
||||||
|
+ b"R<cL(74dIAke<hvC)dqwa~iJg3!LuvLMj9(7VvE(Sgx{(6As<Wn*+8(Sab)z0k1GvCzKJfY83syd"
|
||||||
|
+ b"co9(Sp%{(74dC(Sy-~(7w>JIv`|ab7OKKDA9q@g3*A`zR<eRuqiAcW@&FADA9q@g3*A`zR<eRuqi"
|
||||||
|
+ b"AcZ){{BDA9({wa~lJzR<NG(TC8u(Sgvr(7q`m3JPRmGBPtXH6T48L}7GcLSb`dC?_#8Gbbquaxox"
|
||||||
|
+ b"1AW~&xZ*pWPCuV7HCoCW*WMy+>awjYxF)%3#axx%2AW~&xZ*pWPCuV7HCoCW*WMy+>awjYxF)%3#"
|
||||||
|
+ b"VRLh3a&#baF)naxAR#><axyM(Yb+ol(SXpf(6Z3I(TmWvAkeqaz0k1HhtRdqz0kPPk08*n(Sp%{("
|
||||||
|
+ b"74dC(Sy-~(6AtIYar2sAkl!(u+X>Az0r%&g&@$r(6P~q(6!LI(Sp#v(6S)Vy3o7Ou+f3hfzYrZQe"
|
||||||
|
+ b"|Ura%3V33S?q3GBY$aE_ZTibY&=VGARmjGBO}NAY@`PGBY$aE^=jIWGHenE^uop3So0|WpZ>Naxy"
|
||||||
|
+ b"Y5aBCnvJs@&2E^uogVQyp~axyY5W@&FAJv|_DGA?FmZy;fAWFT@fGA?9gb7OKKJv|_DGA?9gb7OK"
|
||||||
|
+ b"KVQyp~axyY5Z){{BJv|_DGA?gyWGo;e(7(}u(6}JbzR<tWwb6jku+fIlxY3W%g&=owX>?^E(6}IS"
|
||||||
|
+ b"Wnp9>(7(}u(7w@!(74fp(6G?G(7n-%(77PczR<DJiqN&ty3v9l(74dN(6!LD(Sjh*z0kGLve32Bf"
|
||||||
|
+ b"Y80si_o<o(6`XN(6G^m(6!LL(74f$AkeVUg3*A`xX`iDgVBP}zR<EF3JPI!b7gXLAZ%rBC}d(XGB"
|
||||||
|
+ b"Y$aE@WwAbYEm*DIh&PATcZ;BGA3iwa~KAwb6jkz0khUwII=l(74fo(7VvSAke<hvC)dqwa~iJg3!"
|
||||||
|
+ b"LuvLMj1Akl%)yU@PSve2;6fY7xdWNBk`Uu0q;3JPXvZy-G&Y;$y9X>MCET`qHQY-w~TCp#x8TQFT"
|
||||||
|
+ b"Nb98cPa40DXb|5_<Y-w|JC}d(TWNBk`Uu0q~c42IFWpgMgDGF(3AZBTAAU!=GBG8f0u+Y2Eu+X~D"
|
||||||
|
+ b"xY2;nwa~I4(4j8SoGv0d3LqdLAYpTJWpZ>NY-MgJc3UuADIh&PATl6fZe$>AWo{^TTQOZJAU!=GF"
|
||||||
|
+ b"(6@XWFTy1ZYXwJGF>SkJv|^XAYpD~AZ%rBD0W*jT`3?vJs>eGAR^Gc(6!LA(6!Nk(7n*UAkl%*gw"
|
||||||
|
+ b"VdxfY7|qxY2;nzR<GJu+Y6A(Sgvr(7w>J(6G^f(TpHuX=8L>WMU!;ARr(h3Tb8_W@&FAJv|^I(38"
|
||||||
|
+ b"-$(7Dlq(Sab)kuD-S3LqdLAYpTJWpZ>NY-MgJc3UuADIh&PATl6fZe$>AWo{^TTQOZJAU!=GG9Y1"
|
||||||
|
+ b"YWFTy1ZYXwJGF>SkJv|^XAYpD~AZ%rBD0W*jT`3?vJs>eGAR^Gc(6!LA(6!Nk(7n*UAkl%*gwVdx"
|
||||||
|
+ b"fY7|qxY2;nzR<GJu+Y6A(Sgvr(7w>J(6G^f(TpHuX=8L>WMU!"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
287
mod_oop/3.7_10_minesweeper.py
Normal file
287
mod_oop/3.7_10_minesweeper.py
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701992/step/10?unit=702093
|
||||||
|
|
||||||
|
Вы начинаете разрабатывать игру "Сапер". Для этого вам нужно уметь представлять и управлять игровым полем. Будем полагать, что оно имеет размеры N x M клеток. Каждая клетка будет представлена объектом класса Cell и содержать либо число мин вокруг этой клетки, либо саму мину.
|
||||||
|
|
||||||
|
Для начала в программе объявите класс GamePole, который будет создавать и управлять игровым полем. Объект этого класса должен формироваться командой:
|
||||||
|
pole = GamePole(N, M, total_mines)
|
||||||
|
|
||||||
|
>>> SingletonMeta._instances = {}
|
||||||
|
>>> N, M, total_mines = 10, 10, 10
|
||||||
|
>>> pole = GamePole(N, M, total_mines)
|
||||||
|
|
||||||
|
И, так как поле в игре одно, то нужно контролировать создание только одного объекта класса GamePole (используйте паттерн Singleton, о котором мы с вами говорили, когда рассматривали магический метод __new__()).
|
||||||
|
|
||||||
|
Объект pole должен иметь локальный приватный атрибут:
|
||||||
|
__pole_cells - двумерный (вложенный) кортеж, размерами N x M элементов (N строк и M столбцов), состоящий из объектов класса Cell.
|
||||||
|
>>> hasattr(pole, "_GamePole__pole_cells")
|
||||||
|
True
|
||||||
|
|
||||||
|
Для доступа к этой коллекции объявите в классе GamePole объект-свойство (property):
|
||||||
|
pole - только для чтения (получения) ссылки на коллекцию __pole_cells.
|
||||||
|
>>> type(GamePole.pole).__name__
|
||||||
|
'property'
|
||||||
|
|
||||||
|
Далее, в самом классе GamePole объявите следующие методы:
|
||||||
|
init_pole() - для инициализации начального состояния игрового поля (расставляет мины и делает все клетки закрытыми);
|
||||||
|
open_cell(i, j) - открывает ячейку с индексами (i, j); нумерация индексов начинается с нуля; метод меняет значение атрибута __is_open объекта Cell в ячейке (i, j) на True;
|
||||||
|
show_pole() - отображает игровое поле в консоли (как именно сделать - на ваше усмотрение, этот метод - домашнее задание).
|
||||||
|
>>> hasattr(GamePole, "init_pole"), hasattr(GamePole, "open_cell"), hasattr(GamePole, "show_pole")
|
||||||
|
(True, True, True)
|
||||||
|
|
||||||
|
Расстановку мин выполняйте случайным образом по игровому полю (для этого удобно воспользоваться функцией randint модуля random). После расстановки всех total_mines мин, вычислите их количество вокруг остальных клеток (где нет мин). Область охвата - соседние (прилегающие) клетки (8 штук).
|
||||||
|
В методе open_cell() необходимо проверять корректность индексов (i, j). Если индексы указаны некорректно, то генерируется исключение командой:
|
||||||
|
|
||||||
|
raise IndexError('некорректные индексы i, j клетки игрового поля')
|
||||||
|
Следующий класс Cell описывает состояние одной ячейки игрового поля. Объекты этого класса создаются командой:
|
||||||
|
>>> cell = Cell()
|
||||||
|
|
||||||
|
При этом в самом объекте создаются следующие локальные приватные свойства:
|
||||||
|
__is_mine - булево значение True/False; True - в клетке находится мина, False - мина отсутствует;
|
||||||
|
__number - число мин вокруг клетки (целое число от 0 до 8);
|
||||||
|
__is_open - флаг того, открыта клетка или закрыта: True - открыта; False - закрыта.
|
||||||
|
|
||||||
|
Для работы с этими приватными атрибутами объявите в классе Cell следующие объекты-свойства с именами:
|
||||||
|
is_mine - для записи и чтения информации из атрибута __is_mine;
|
||||||
|
number - для записи и чтения информации из атрибута __number;
|
||||||
|
is_open - для записи и чтения информации из атрибута __is_open.
|
||||||
|
>>> hasattr(cell, "is_mine"), hasattr(cell, "number"), hasattr(cell, "is_open")
|
||||||
|
(True, True, True)
|
||||||
|
|
||||||
|
В этих свойствах необходимо выполнять проверку на корректность переданных значений (либо булево значение True/False, либо целое число от 0 до 8). Если передаваемое значение некорректно, то генерировать исключение командой:
|
||||||
|
raise ValueError("недопустимое значение атрибута")
|
||||||
|
С объектами класса Cell должна работать функция:
|
||||||
|
|
||||||
|
>>> bool(cell)
|
||||||
|
True
|
||||||
|
|
||||||
|
которая возвращает True, если клетка закрыта и False - если открыта.
|
||||||
|
|
||||||
|
Пример использования классов (эти строчки в программе писать не нужно):
|
||||||
|
|
||||||
|
>>> SingletonMeta._instances = {}
|
||||||
|
>>> random.seed(5)
|
||||||
|
>>> pole = GamePole(10, 20, 10) # создается поле размерами 10x20 с общим числом мин 10
|
||||||
|
>>> pole.init_pole()
|
||||||
|
>>> if pole.pole[0][1]:
|
||||||
|
... pole.open_cell(0, 1)
|
||||||
|
>>> if pole.pole[3][5]:
|
||||||
|
... pole.open_cell(3, 5)
|
||||||
|
>>> pole.open_cell(30, 100) # генерируется исключение IndexError
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
IndexError: Недопустимый индекс
|
||||||
|
>>> pole.show_pole()
|
||||||
|
╭───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───╮
|
||||||
|
│ ▧ │ ✸ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ① │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||||
|
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||||
|
╰───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───╯
|
||||||
|
|
||||||
|
P.S. В программе на экран выводить ничего не нужно, только объявить классы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
from typing import Dict, Final, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonMeta(type):
|
||||||
|
_instances: Dict[type, type] = {}
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
if cls not in cls._instances:
|
||||||
|
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
|
def make_properties(*names):
|
||||||
|
def decorator(cls):
|
||||||
|
def prop(private_name: str):
|
||||||
|
def getter(self):
|
||||||
|
return getattr(self, private_name)
|
||||||
|
|
||||||
|
def setter(self, value):
|
||||||
|
return setattr(self, private_name, value)
|
||||||
|
|
||||||
|
return getter, setter
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
setattr(cls, name, property(*prop(f"_{cls.__name__}__{name}")))
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@make_properties("number", "is_mine", "is_open")
|
||||||
|
class Cell:
|
||||||
|
CHARS: Final[str] = "▧✸▢①②③④⑤⑥⑦⑧"
|
||||||
|
|
||||||
|
def __init__(self, number: int = 0, is_mine: bool = False, is_open: bool = False):
|
||||||
|
self.__number, self.__is_mine, self.__is_open = number, is_mine, is_open
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return not self.is_open
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self.number, self.is_mine, self.is_open)!r}"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.CHARS[not self and (self.is_mine or self.number + 2)]
|
||||||
|
|
||||||
|
def __setattr__(self, name: str, value) -> None:
|
||||||
|
if name == "number":
|
||||||
|
if not isinstance(value, int) or not 0 <= value <= 8:
|
||||||
|
raise ValueError("Недопустимое значение атрибута")
|
||||||
|
if name in ("is_mine", "is_open"):
|
||||||
|
if not isinstance(value, bool):
|
||||||
|
raise ValueError("Недопустимое значение атрибута")
|
||||||
|
return super().__setattr__(name, value)
|
||||||
|
|
||||||
|
|
||||||
|
class GamePole(metaclass=SingletonMeta):
|
||||||
|
around_offsets: Final[Tuple[Tuple[int, int], ...]] = tuple(
|
||||||
|
(dx, dy) for dx in range(-1, 2) for dy in range(-1, 2) if (dx, dy) != (0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, N: int, M: int, total_mines: int, pole=None):
|
||||||
|
self.N, self.M, self.total_mines = N, M, total_mines
|
||||||
|
if pole:
|
||||||
|
self.__pole_cells = pole
|
||||||
|
else:
|
||||||
|
self.init_pole()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pole(self) -> List[List[Cell]]:
|
||||||
|
return self.__pole_cells
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self.N, self.M, self.total_mines, self.pole)!r}"
|
||||||
|
|
||||||
|
def init_pole(self):
|
||||||
|
gen = iter(range(1, self.total_mines + 1))
|
||||||
|
flat_pole = [
|
||||||
|
Cell(0, bool(next(gen, 0))) for _ in range(self.N) for _ in range(self.M)
|
||||||
|
]
|
||||||
|
random.shuffle(flat_pole)
|
||||||
|
self.__pole_cells = [
|
||||||
|
flat_pole[i : i + self.M] for i in range(0, self.N * self.M, self.M)
|
||||||
|
]
|
||||||
|
self.fill_around()
|
||||||
|
|
||||||
|
def fill_around(self):
|
||||||
|
for i in range(self.N):
|
||||||
|
for j in range(self.M):
|
||||||
|
self.pole[i][j].number = self.count_around(i, j)
|
||||||
|
|
||||||
|
def count_around(self, x: int, y: int) -> int:
|
||||||
|
return sum(
|
||||||
|
(
|
||||||
|
self.pole[x + i][y + j].is_mine
|
||||||
|
for i, j in GamePole.around_offsets
|
||||||
|
if 0 <= x + i < self.N and 0 <= y + j < self.M
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def cell(self, i: int, j: int) -> Cell:
|
||||||
|
try:
|
||||||
|
return self.pole[i][j]
|
||||||
|
except IndexError as e:
|
||||||
|
raise IndexError("Недопустимый индекс") from e
|
||||||
|
|
||||||
|
def open_cell(self, i: int, j: int):
|
||||||
|
cell = self.cell(i, j)
|
||||||
|
cell.is_open = True
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
for i in range(self.N):
|
||||||
|
for j in range(self.M):
|
||||||
|
self.open_cell(i, j)
|
||||||
|
|
||||||
|
def open_random(self, count: int):
|
||||||
|
for _ in range(count):
|
||||||
|
i, j = random.randint(0, self.N - 1), random.randint(0, self.M - 1)
|
||||||
|
self.open_cell(i, j)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
c = self.M - 1
|
||||||
|
result = (
|
||||||
|
f"╭─{'──┬─' * c}──╮\n"
|
||||||
|
+ f"├─{'──┼─' * c}──┤\n".join(
|
||||||
|
map(lambda row: f"│ {' │ '.join(map(str, row))} │\n", self.pole)
|
||||||
|
)
|
||||||
|
+ f"╰─{'──┴─' * c}──╯"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def show_pole(self):
|
||||||
|
print(self)
|
||||||
|
|
||||||
|
|
||||||
|
pole_game = GamePole(10, 8, 12)
|
||||||
|
pole_game.open_random(30)
|
||||||
|
pole_game.show_pole()
|
||||||
|
pole_game.open()
|
||||||
|
pole_game.show_pole()
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
SingletonMeta._instances = {}
|
||||||
|
random.seed()
|
||||||
|
code = (
|
||||||
|
b"a4{e~AV*<sWl(QyWhgN)EFdy4EFdv3DGG2hAUz;QVQpnlZ){~KF)%D3GB7M4F)%3#VRLh3a&#b"
|
||||||
|
+ b"RWGHYkDIh&PAZcVMa55<@AR^I$(7w>O(6rF7(6!Nm(SgyAAke+gwb6mly3oGRyU~o$y3oEL(7w>I"
|
||||||
|
+ b"(TdQu(7Mrr(7w>JAkezdyU?)Ffzg4`upmcaZDmkzY-J(}a3DP(a4`xBV`Xe?AUz;MWo&FHDGFh8b"
|
||||||
|
+ b"7gXLAar?fWhg^sY-}!Rb6;&~Ze=MTJv|_Ba&K^Da&&nhVQyp~ba`-PC_`mzY%XqfZDM6|DIh&PAa"
|
||||||
|
+ b"HVTaAk6Ic_3kKWFT~TaAhb%Wo&FNX>(t1aAj^OAU!=GaB^>OWpZ?REFdD#vLMj9(7VvE(Sgx{(6t"
|
||||||
|
+ b"~#Wo&F9(6rFL(7VvK(7n-%AkeYVi_wD7j3Cgy(6P~q(6!LI(Sp&7EzyC{ve3TJxzT~qg3z+iupnu"
|
||||||
|
+ b"3Uu|h_Wh@|Wb!}p0ax5Teb6;<8Wo{x03S(t#Y%XbYUu|h_WgtBuRC0A?3S(t#Y%XqfZDM6|AUz;8"
|
||||||
|
+ b"3S(t#Y%XbYUvF?_ZXi7%RC0A?3So0|WpZ>NVsCG3C}U-8Y$+f;Js?J5Y;$ESAR^I((Sy*v(7Mrv("
|
||||||
|
+ b"74f$AYyNCY$z!p(6Z3A(SXps(Sy*t(6AuTz0kGLve32BfY80rzR<NG(6`XN(6G^m(6!LL(74dGA_"
|
||||||
|
+ b"@w0a(OxmARr(hV`Xe?E@^XLZE0?0AUz;4FbZXOV`Xr3AXZ^)b!A0za&K}v3LqdLAYpTJWpZ>NRC0"
|
||||||
|
+ b"A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`i"
|
||||||
|
+ b"vj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~|VQh6}MRIa)av}-}ARr(h3UqRLItm~lARuF9Y-}!Wb!}p0"
|
||||||
|
+ b"av(h*F)#{ccw=R7bRbq?Y;|Qta&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@V"
|
||||||
|
+ b"Qh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX"
|
||||||
|
+ b"`sAR$**)WkqswZ*n3E3UDrIZfSI1aBpm7C@BhUAUz;33TAI|AaZYaAZczOa4v9fY-KtMARr(hW^Z"
|
||||||
|
+ b"yJcpzzRAaZYaItm~lARr(hARu9Lb7gXLAZc@HZgX^DZewLAcq|}8Wo&FIEFdD#y3o7Owb6pmy3nx"
|
||||||
|
+ b"DywJEH(74dE(SXpt(6Z3J(6i9KAke?izR<hTk08*r(7w>S(6-RM(TgC^vC)gsg3*j1(7w>I(TdQu"
|
||||||
|
+ b"(7Mrr(TgC^y3o7Ou+f3hfzYrZLuG7iA_^cNARr(hARuXGAb2imb6;&~Ze=<OARr(hARr(hARr(hZ"
|
||||||
|
+ b"6GT>ATbIGVRLh3a&#bVAU!=GF)%D3BGA3iuprRC(7w>S(6u1ZfY7kffzg4{g3z$gve3KGwa~rLz9"
|
||||||
|
+ b"7)O(6!LA(6!Nk(7n*U(6u1Yy3oGRyU@7NhtRdrfzg7{ve3RD(7e#N(7hrGa4v6fWo}<%Wo&FHFf1"
|
||||||
|
+ b"T3DGG2dZ*XO9Ut?u#Y$!P_ATc>93JP>`c{&OpARr)cE^lyUZeL?%Y-}hoFf1T4FewUUcw=R7bRbD"
|
||||||
|
+ b"?WMz0oa&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@VQh0{EFdD#z0kEF(Sgvj"
|
||||||
|
+ b"(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX`sANp56ictvt@Z*n3E3"
|
||||||
|
+ b"JPRpW*}p4b#8QDZE0?0b0~0cY-KDUX)GXWDLM)uARr)aAUz;33LqdLAZBlJAZs9LZXj}DZf9jEEi"
|
||||||
|
+ b"o)0GATL=ARr(hARr(hW^ZyJY#?cFAaY@DXJsfYF)Sc5DLM)uARr(hARr(hARr)VX)GXWY9KuzYb$"
|
||||||
|
+ b"9iAZ#mY3LqdLARr(hARr(hAZcbGX=xxlATS_rav*7GAU+^DAa8OYYHA=nATS_rav*AIAU+^5IXVg"
|
||||||
|
+ b";ARr(hARr(hARr(hARr)PZ*FvHZgph}ARr(hARr(hARr(hX=WgBZ){~-X=z<sYHD3BX>(s~X>MgY"
|
||||||
|
+ b"3LqdLARr(hARr(hARr(hAZ{QlJs>d(ARr(hARr(hARr(hARr(h3LqdLAaZ4Nb#iVXZVCztW^ZyJX"
|
||||||
|
+ b")GXeZ+9SRZXjiDb!}yGVRU6Ea4v9fY-K4r3LqdLAZBlJAZjcicpzzRAZ2cKZDn#{bY&=VZ+9s=3L"
|
||||||
|
+ b"qdLARr(hAZcbGZf|rTa4v9fY-L+%U0Z5hE@^XLZE0?0Itm~lARr(hARr(hARuiZJs@Lmb#8QDZE0"
|
||||||
|
+ b"?0b0}~waBpm7EFfttAZjTJARr(hARr(hARr(hVRLh3a&#bVAU!=Ga4v9fY-L+%U0Z5hE^c*gVr6n"
|
||||||
|
+ b"HAR^Gc(6!LA(6!Nk(7n*UAke?izR<MMfzgN1xY2^pu+Y8Gz97+u(74fo(7VvSAke(fxX`^I(6Z3J"
|
||||||
|
+ b"(7Mrp(Sy*lAkezdyU?}Kg3!9qxFP"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
183
mod_oop/3.8_08_stack.py
Normal file
183
mod_oop/3.8_08_stack.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701993/step/8?unit=702094
|
||||||
|
|
||||||
|
Ранее вы уже создавали стек-подобную структуру, когда один объект ссылается на следующий и так по цепочке до последнего:
|
||||||
|
|
||||||
|
Для этого в программе объявлялись два класса:
|
||||||
|
StackObj - для описания объектов стека;
|
||||||
|
Stack - для управления стек-подобной структурой.
|
||||||
|
|
||||||
|
И, далее, объекты класса StackObj следовало создавать командой:
|
||||||
|
obj = StackObj(data)
|
||||||
|
где data - это строка с некоторым содержимым объекта (данными). При этом каждый объект класса StackObj должен иметь следующие локальные атрибуты:
|
||||||
|
data - ссылка на строку с данными, указанными при создании объекта;
|
||||||
|
next - ссылка на следующий объект класса StackObj (при создании объекта принимает значение None).
|
||||||
|
|
||||||
|
Класс Stack предполагается использовать следующим образом:
|
||||||
|
st = Stack() # создание объекта стек-подобной структуры
|
||||||
|
В каждом объекте класса Stack должен быть локальный публичный атрибут:
|
||||||
|
top - ссылка на первый объект стека (если стек пуст, то top = None).
|
||||||
|
|
||||||
|
А в самом классе Stack следующие методы:
|
||||||
|
push(self, obj) - добавление объекта класса StackObj в конец стека;
|
||||||
|
pop(self) - извлечение последнего объекта с его удалением из стека;
|
||||||
|
|
||||||
|
Дополнительно в классе Stack нужно объявить магические методы для обращения к объекту стека по его индексу, например:
|
||||||
|
obj_top = st[0] # получение первого объекта
|
||||||
|
obj = st[4] # получение 5-го объекта стека
|
||||||
|
st[2] = StackObj("obj3") # замена прежнего (3-го) объекта стека на новый
|
||||||
|
Если индекс не целое число или число меньше нуля или больше числа объектов в стеке, то должно генерироваться исключение командой:
|
||||||
|
raise IndexError('неверный индекс')
|
||||||
|
|
||||||
|
Пример использования классов Stack и StackObj (эти строчки в программе не писать):
|
||||||
|
st = Stack()
|
||||||
|
st.push(StackObj("obj1"))
|
||||||
|
st.push(StackObj("obj2"))
|
||||||
|
st.push(StackObj("obj3"))
|
||||||
|
st[1] = StackObj("new obj2")
|
||||||
|
print(st[2].data) # obj3
|
||||||
|
print(st[1].data) # new obj2
|
||||||
|
res = st[3] # исключение IndexError
|
||||||
|
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class StackObj:
|
||||||
|
def __init__(self, data=None):
|
||||||
|
self.__next = None
|
||||||
|
self.__data = data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self.__data
|
||||||
|
|
||||||
|
@data.setter
|
||||||
|
def data(self, value):
|
||||||
|
self.__data = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next(self):
|
||||||
|
return self.__next
|
||||||
|
|
||||||
|
@next.setter
|
||||||
|
def next(self, value):
|
||||||
|
if isinstance(value, (self.__class__, None.__class__)):
|
||||||
|
self.__next = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.data!r})"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class Stack:
|
||||||
|
def __init__(self, data=None):
|
||||||
|
self.top = None
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
for x in map(lambda x: isinstance(x, StackObj) and x or StackObj(x), data):
|
||||||
|
self.push(x)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bottom(self) -> StackObj:
|
||||||
|
curr, last = self.top, None
|
||||||
|
while curr:
|
||||||
|
curr, last = curr.next, curr
|
||||||
|
return last
|
||||||
|
|
||||||
|
def push(self, obj: StackObj):
|
||||||
|
if not self.top:
|
||||||
|
self.top = obj
|
||||||
|
else:
|
||||||
|
self.bottom.next = obj
|
||||||
|
|
||||||
|
def pop(self) -> StackObj:
|
||||||
|
if not self.top:
|
||||||
|
return None
|
||||||
|
|
||||||
|
a, b, c = [self.top] + [None] * 2
|
||||||
|
while a:
|
||||||
|
a, b, c = a.next, a, b
|
||||||
|
|
||||||
|
if c:
|
||||||
|
c.next = None
|
||||||
|
|
||||||
|
if self.top in [b, c]:
|
||||||
|
self.top = None
|
||||||
|
|
||||||
|
return b
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
count, obj = 0, self.top
|
||||||
|
while obj:
|
||||||
|
obj, count = obj.next, count + 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
def __getitem__(self, idx: int) -> StackObj:
|
||||||
|
if not isinstance(idx, int) or not 0 <= idx < len(self):
|
||||||
|
raise IndexError("неверный индекс")
|
||||||
|
count, obj = 0, self.top
|
||||||
|
while obj:
|
||||||
|
if count == idx:
|
||||||
|
return obj
|
||||||
|
obj, count = obj.next, count + 1
|
||||||
|
|
||||||
|
def __setitem__(self, idx: int, value: StackObj):
|
||||||
|
if not isinstance(idx, int) or not 0 <= idx <= len(self):
|
||||||
|
raise IndexError("неверный индекс")
|
||||||
|
|
||||||
|
if not isinstance(value, StackObj):
|
||||||
|
value = StackObj(value)
|
||||||
|
|
||||||
|
if idx == len(self):
|
||||||
|
self.push(value)
|
||||||
|
return
|
||||||
|
|
||||||
|
old = self[idx]
|
||||||
|
value.next = old.next
|
||||||
|
|
||||||
|
if idx > 0:
|
||||||
|
self[idx - 1].next = value
|
||||||
|
else:
|
||||||
|
self.top = value
|
||||||
|
|
||||||
|
def get_data(self) -> List[StackObj]:
|
||||||
|
return [x for x in self]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.get_data()!r})"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return " -> ".join(map(str, self.get_data()))
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"b95j*AX9W<V{0fW3UhQWaCLKNC{uJ{V{1=hYA7OaVrnrlA}J{fb963nb#rJaQ*>ctYfoZoC?ao"
|
||||||
|
+ b"SYB4e*DJcqbbS`jpb7&}2bYWv_Phx5)B5z`9F*715DGGCRTQOZAJs?wbVPk7gVrnQNZ(?dPEpBCZ"
|
||||||
|
+ b"A}I=Cb8}^KbRctdTQFTNWMOn+AU!=GB5z`9F)<<_VQyp~b97rVT`pu{bYUPpJs=`)VrnuiZe@2OE"
|
||||||
|
+ b"FdD#u+f6ifY7+mvC)Ikf*@pJbYURSzR<DJiqN&ty3vBruprR7(7VvE(Sgx{(6As=bYWv_Phx5y(S"
|
||||||
|
+ b"gvu(6rFC(SXpl(74fpAke+gwa~KAwb6jkz0r%%wII;6(6G?G(7n-%(6u583UqRLItm~lARupIY9K"
|
||||||
|
+ b"uzb97rXT?%D*V`Xr3AW3dyWq3t$a&K}v3LqdLAYpTJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4"
|
||||||
|
+ b"yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O"
|
||||||
|
+ b"(6t~*Ze(S6MRIa)av}-}Z(?d7Js@**E^u#fC@BhIb8}^KbRchHYA$49bYUPpJs=`)VrnrnA}k;x("
|
||||||
|
+ b"7e#K(Sp#v(6k_MZ*U;cw9vlLyU@1Kwa~pF(Sy*m(6G?E(T~xB(TpI_ztFzXfzZ3qwa~QCz0kPOxg"
|
||||||
|
+ b"gNK(6P~q(6!LI(Sjh+fzg7{wa~iIuprR5AkebVzR<VOveAIhu+fRou+f6ij3Cgp(6i9KA_@v_AUz"
|
||||||
|
+ b";33TPlbAaissbZ>A9cW7yBWguue3LqdLAYpTJWpZ>NX>)0Ab97;DV`V64EFe>KVPk7gVrnTYAR^G"
|
||||||
|
+ b"d(6P~q(6!LI(Sjh+fzg7{wa~iIuprR1(7w>S(6-RE(7hngvC)gsg3*j1(T&i$(6`XF(7e#U(7Vx("
|
||||||
|
+ b"(SXpt(7Yhfy3o7Ou+f3hfzYrZQ*>ctYfoZoA_^cNARulaD?K1F3LqdLAZQ>xAZRXbWq5Q7ARr(h3"
|
||||||
|
+ b"So0|WpZ>NZXi89ATlf<BGA3iwa~KAwb6jkz0khUwII=l(74fo(7VvSAke<hvC)dqwa~iJg3!LuvL"
|
||||||
|
+ b"Mj1Akl%*g3z_ly3n;CDA2OdzR<VOywJYTw$Q!MzAPZnz0k1HfYF1|h|smrz0j~A(6!LB(7qtifzg"
|
||||||
|
+ b"7|fYF1{y3vBsgVBJ{uqh$"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
169
mod_oop/3.8_10_tictactoe.py
Normal file
169
mod_oop/3.8_10_tictactoe.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701993/step/10?unit=702094
|
||||||
|
|
||||||
|
Вам нужно реализовать в программе игровое поле для игры "Крестики-нолики".
|
||||||
|
|
||||||
|
Для этого требуется объявить класс TicTacToe (крестики-нолики), объекты которого создаются командой:
|
||||||
|
game = TicTacToe()
|
||||||
|
|
||||||
|
Каждый объект game должен иметь публичный атрибут:
|
||||||
|
pole - игровое поле: кортеж размером 3х3 с объектами класса Cell.
|
||||||
|
|
||||||
|
Каждая клетка игрового поля представляется объектом класса Cell и создается командой:
|
||||||
|
cell = Cell()
|
||||||
|
|
||||||
|
Объекты класса Cell должны иметь следующие публичные локальные атрибуты:
|
||||||
|
is_free - True, если клетка свободна; False в противном случае;
|
||||||
|
value - значение поля: 1 - крестик; 2 - нолик (по умолчанию 0).
|
||||||
|
|
||||||
|
Также с каждым объектом класса Cell должна работать функция:
|
||||||
|
bool(cell)
|
||||||
|
которая возвращает True, если клетка свободна (cell.is_free=True) и False в противном случае.
|
||||||
|
|
||||||
|
Класс TicTacToe должен иметь следующий метод:
|
||||||
|
clear() - очистка игрового поля (все клетки заполняются нулями и переводятся в закрытое состояние);
|
||||||
|
|
||||||
|
А объекты этого класса должны иметь следующую функциональность (обращение по индексам):
|
||||||
|
game[0, 0] = 1 # установка нового значения, если поле закрыто
|
||||||
|
res = game[1, 1] # получение значения центральной ячейки поля (возвращается число)
|
||||||
|
|
||||||
|
Если указываются некорректные индексы, то должно генерироваться исключение командой:
|
||||||
|
raise IndexError('неверный индекс клетки')
|
||||||
|
|
||||||
|
Если идет попытка присвоить новое значение в открытую клетку поля, то генерировать исключение:
|
||||||
|
raise ValueError('клетка уже занята')
|
||||||
|
|
||||||
|
Также должны быть реализованы следующие полные срезы при обращении к клеткам игрового поля:
|
||||||
|
slice_1 = game[:, indx] # выбираются все элементы (кортеж) столбца с индексом indx
|
||||||
|
slice_2 = game[indx, :] # выбираются все элементы (кортеж) строки с индексом indx
|
||||||
|
|
||||||
|
Пример использования классов (эти строчки в программе не писать):
|
||||||
|
game = TicTacToe()
|
||||||
|
game.clear()
|
||||||
|
game[0, 0] = 1
|
||||||
|
game[1, 0] = 2
|
||||||
|
# формируется поле:
|
||||||
|
# 1 0 0
|
||||||
|
# 2 0 0
|
||||||
|
# 0 0 0
|
||||||
|
game[3, 2] = 2 # генерируется исключение IndexError
|
||||||
|
if game[0, 0] == 0:
|
||||||
|
game[0, 0] = 2
|
||||||
|
v1 = game[0, :] # 1, 0, 0
|
||||||
|
v2 = game[:, 0] # 1, 2, 0
|
||||||
|
|
||||||
|
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||||
|
P.P.S. При передаче среза в магических методах __setitem__() и __getitem__() параметр индекса становится объектом класса slice.
|
||||||
|
Его можно указывать непосредственно в квадратных скобках упорядоченных коллекций (списков, кортежей и т.п.).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
CHARS = " OX"
|
||||||
|
|
||||||
|
def __init__(self, value=0, is_free=True):
|
||||||
|
self.value, self.is_free = value, is_free
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return self.is_free
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self.value, self.is_free)!r}"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.CHARS[not self and self.value]
|
||||||
|
|
||||||
|
|
||||||
|
class TicTacToe:
|
||||||
|
def __init__(self, pole=None):
|
||||||
|
if pole is None:
|
||||||
|
pole = tuple(tuple(Cell() for _ in range(3)) for _ in range(3))
|
||||||
|
self.pole = pole
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for row in self.pole:
|
||||||
|
for cell in row:
|
||||||
|
cell.is_free = True
|
||||||
|
cell.value = 0
|
||||||
|
|
||||||
|
def _get_key(self, key):
|
||||||
|
if not isinstance(key, tuple) or len(key) != 2:
|
||||||
|
raise IndexError("неверный индекс клетки")
|
||||||
|
return tuple(x if isinstance(x, int) else None for x in key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
key = self._get_key(key)
|
||||||
|
row, col = key
|
||||||
|
if row is None and col is None:
|
||||||
|
return tuple(tuple(c.value for c in r) for r in self.pole)
|
||||||
|
if row is not None and col is not None:
|
||||||
|
return self.pole[row][col].value
|
||||||
|
if row is not None and col is None:
|
||||||
|
return tuple(x.value for x in self.pole[row])
|
||||||
|
return tuple(self.pole[row][col].value for row in range(len(self.pole)))
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
key = self._get_key(key)
|
||||||
|
row, col = key
|
||||||
|
if row is None and col is None:
|
||||||
|
for i, row in enumerate(value):
|
||||||
|
for j, v in enumerate(row):
|
||||||
|
self[i, j] = v
|
||||||
|
return
|
||||||
|
if row is not None and col is not None:
|
||||||
|
cell = self.pole[row][col]
|
||||||
|
if not cell.is_free:
|
||||||
|
raise ValueError("клетка уже занята")
|
||||||
|
cell.value = value
|
||||||
|
cell.is_free = False
|
||||||
|
return
|
||||||
|
if row is not None and col is None:
|
||||||
|
for j, v in enumerate(value):
|
||||||
|
self[row, j] = v
|
||||||
|
return
|
||||||
|
for i, v in enumerate(value):
|
||||||
|
self[i, col] = v
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.pole!r})"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "\n".join(map(lambda r: " ".join(map(str, r)), self.pole))
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"XCOTwRB2;WVPjNpWhf~MXD(xGWnpqCDGFh8b7gXLAZJ@JEFdslAU!=GFd$)WWFTi-GAtl6T_8O"
|
||||||
|
+ b"@ATTT-BGA3iu+fLmu+Y2FjL^N&i_o<o(6`XN(6G^m(6!LL(74f$AkebWfzY+lg&@$n(7VvJ(Sp#v"
|
||||||
|
+ b"(7GVdw9vlLyU@1Kz0r#x(6P~r(Sp&8Akl!(u+Xy5z0r#xFd_<PTQMvkF<l@%ATbJOTQV#lF<l@%A"
|
||||||
|
+ b"TkPJb8}^KbRcJ2F)Sc4T_8O@ATc0eZe$>5TQV#lF<l@%Js>hHAR^Gc(6!LA(6!Nk(7n*UAke<ig3"
|
||||||
|
+ b"*A`u+Xv4zR`lvu+Y2EuprRB(7({N(SXpf(T32t(T^a|ztMouxY2>ove2;5xX`lDu+Y8GxY3Uw(7n"
|
||||||
|
+ b"*U(6Z5s(S;z;x6r-Nu+fLmwa~rLxX`&E(7MpO(6!Nm(7MpD(7YhfxX`oFfY83sve3TJv(UaE(7({"
|
||||||
|
+ b"W(7Vx(ASlqd(7VvMEFjRm(6!LI(7w@t(SXpk(7Mrr(7n*UAkl!(u+Xv4zR`lvu+X*9f*{d>(TC8u"
|
||||||
|
+ b"(Sp&7(6Z35(7n*O(6u1Yx6r-Nu+fLmwa~rLxX`&NA_@w0a(OxmARr(ha%FQMJs@XWGb|u5T?%D*V"
|
||||||
|
+ b"`Xr3AW3dyWq3t$a&K}v3LqdLAYpTJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6"
|
||||||
|
+ b"u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~*Ze(S6MRI"
|
||||||
|
+ b"a)av;#Z(SXpnAkl%*htRmug3*i6ve2;5z0kPOxFFED(6=Daz0kGMfzgA}iO{vtfzg7{veARlj?sz"
|
||||||
|
+ b"Gwa~dB(T~xG(6!LH(7MpLA_@v1ARr(LbaHt*3LqdLAZJ@MEFdslAUz;83T1d>WpH#LNp56ictvt@"
|
||||||
|
+ b"Z*n>cARr(hVRLh3a&#b6a&=`2Wo&b0Itm~lARu9Lb7gXLAVy(qb7d?bBGA3iwII=f(6i9B(7n*L("
|
||||||
|
+ b"SXpn(SXpt(6Z35(7VvS(Sgy7AketcfzZ0pyU~u(htRdqz0kPOwIE4uWMz0oa&m8SAke?jfY7)g(6"
|
||||||
|
+ b"`XA(7({Q(SgvoAkeZP(7n*L(Sgx}(TUKt(Sgx|(6Z5k(T>rH(Sy;BAkmM}htRdqxzM`NgCYtF3TG"
|
||||||
|
+ b"~3Y-M3`C@BhOTQDpjFkK)$ATbJOTQMvkFkK)$ATkPPTQV#lFkK)$ATtUIVRLh3a&#bPTQDpjI$a<"
|
||||||
|
+ b"=Js>DCEFdr}ATTK)VQyp~XIn8WAUa(jJv|^OGAtl4EFdr`AYpD~AZJ@TEFdslAU!=GC^0M`GAtl7"
|
||||||
|
+ b"DJ&o&(7n*L(7MpR(SXr_(6!LI(Sp#u(7qthzR`lwfY7kevCzKJg3z$gyU@5G(Sgx`(6!LF(TgC^z"
|
||||||
|
+ b"tFzXfzZ3qwII;4(TmWx(7w>J(6AuTywJ7Lg3!Luw9v31V{Bz%awsVv(6}JbztMouxY2>ove2;5xX"
|
||||||
|
+ b"`lDu+Y8GxY3Uw(7n*U(6Z5s(S;z;x6r-Nu+fLmwa~rLxX`&G3JPOoY-}JsAVXzrY$z!TVRLh3a&#"
|
||||||
|
+ b"bLWo&FNc42IFWgtC0ATTT-BGA3iu+fLmu+Y2FjL^N%zR<NG(6`XN(6G^m(6!LL(74dGAkeVUg3*A"
|
||||||
|
+ b"`xX`iDgVBP}upo9}Y;|QI(7MpO(6G^g(SgvgAVXzrY#`9I(7w>S(6-RM(7qthvC)gsg3*j1(SXpf"
|
||||||
|
+ b"(6Z3I(7qrrA_{V4b09q+V`Xe?E@^XLW^!d^3S(t#Y%XbYUuJS;WgtBuRC0A?3So0|WpZ>NVsCG3C"
|
||||||
|
+ b"}U-8Y$+@tBGH7=gV4Ruy3vNvxY3UwVsCG3AkebVwb6jkz0rfvyU?&8MqzAoWgyVB(7Vx(Akl%)ve"
|
||||||
|
+ b"3TJvCzKIw9viKzR<ZK(7MpO(6!Nm(7MpLA^"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
205
mod_oop/3.9_10_stack_iter.py
Normal file
205
mod_oop/3.9_10_stack_iter.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701994/step/10?unit=702095
|
||||||
|
|
||||||
|
Вы несколько раз уже делали стек-подобную структуру, когда объекты последовательно связаны между собой:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Доведем ее функционал до конца. Для этого, по прежнему, нужно объявить классы:
|
||||||
|
Stack - для представления стека в целом;
|
||||||
|
StackObj - для представления отдельных объектов стека.
|
||||||
|
|
||||||
|
В классе Stack должны быть методы:
|
||||||
|
push_back(obj) - для добавления нового объекта obj в конец стека;
|
||||||
|
push_front(obj) - для добавления нового объекта obj в начало стека.
|
||||||
|
|
||||||
|
В каждом объекте класса Stack должен быть публичный атрибут:
|
||||||
|
top - ссылка на первый объект стека (при пустом стеке top = None).
|
||||||
|
|
||||||
|
Объекты класса StackObj создаются командой:
|
||||||
|
obj = StackObj(data)
|
||||||
|
где data - данные, хранящиеся в объекте стека (строка).
|
||||||
|
|
||||||
|
Также в каждом объекте класса StackObj должны быть публичные атрибуты:
|
||||||
|
data - ссылка на данные объекта;
|
||||||
|
next - ссылка на следующий объект стека (если его нет, то next = None).
|
||||||
|
|
||||||
|
Наконец, с объектами класса Stack должны выполняться следующие команды:
|
||||||
|
st = Stack()
|
||||||
|
st[indx] = value # замена прежних данных на новые по порядковому индексу (indx); отсчет начинается с нуля
|
||||||
|
data = st[indx] # получение данных из объекта стека по индексу
|
||||||
|
n = len(st) # получение общего числа объектов стека
|
||||||
|
|
||||||
|
for obj in st: # перебор объектов стека (с начала и до конца)
|
||||||
|
print(obj.data) # отображение данных в консоль
|
||||||
|
|
||||||
|
При работе с индексами (indx), нужно проверять их корректность. Должно быть целое число от 0 до N-1, где N - число объектов в стеке. Иначе, генерировать исключение командой:
|
||||||
|
raise IndexError('неверный индекс')
|
||||||
|
|
||||||
|
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class StackObj:
|
||||||
|
def __init__(self, data=None):
|
||||||
|
self.__next = None
|
||||||
|
self.__data = data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self.__data
|
||||||
|
|
||||||
|
@data.setter
|
||||||
|
def data(self, value):
|
||||||
|
self.__data = value
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if hasattr(other, "data"):
|
||||||
|
return self.data == other.data
|
||||||
|
return self.data == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next(self):
|
||||||
|
return self.__next
|
||||||
|
|
||||||
|
@next.setter
|
||||||
|
def next(self, value):
|
||||||
|
if isinstance(value, (self.__class__, None.__class__)):
|
||||||
|
self.__next = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap(cls, value):
|
||||||
|
return value if isinstance(value, cls) else cls(value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.data!r})"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class Stack:
|
||||||
|
def __init__(self, data=None):
|
||||||
|
self.top = None
|
||||||
|
self.load(data or [])
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
obj = self.top
|
||||||
|
while obj:
|
||||||
|
yield obj
|
||||||
|
obj = obj.next
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bottom(self) -> StackObj:
|
||||||
|
for last in self:
|
||||||
|
...
|
||||||
|
return last
|
||||||
|
|
||||||
|
def push_back(self, value):
|
||||||
|
obj = StackObj.wrap(value)
|
||||||
|
if not self.top:
|
||||||
|
self.top = obj
|
||||||
|
else:
|
||||||
|
self.bottom.next = obj
|
||||||
|
|
||||||
|
def push_front(self, value):
|
||||||
|
obj = StackObj.wrap(value)
|
||||||
|
obj.next = self.top
|
||||||
|
self.top = obj
|
||||||
|
|
||||||
|
def pop(self) -> StackObj:
|
||||||
|
if not self.top:
|
||||||
|
return None
|
||||||
|
b, c = None, None
|
||||||
|
for a in self:
|
||||||
|
b, c = a, b
|
||||||
|
if c:
|
||||||
|
c.next = None
|
||||||
|
if b is self.top:
|
||||||
|
self.top = None
|
||||||
|
return b
|
||||||
|
|
||||||
|
def load(self, data):
|
||||||
|
for x in data:
|
||||||
|
self.push_back(x)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return sum(1 for _ in self)
|
||||||
|
|
||||||
|
def validate_index(self, idx: int):
|
||||||
|
if not isinstance(idx, int) or not 0 <= idx < len(self):
|
||||||
|
raise IndexError("неверный индекс")
|
||||||
|
|
||||||
|
def __getitem__(self, idx: int):
|
||||||
|
self.validate_index(idx)
|
||||||
|
for i, v in enumerate(self):
|
||||||
|
if idx == i:
|
||||||
|
return v.data
|
||||||
|
|
||||||
|
def __setitem__(self, idx: int, value):
|
||||||
|
self.validate_index(idx)
|
||||||
|
|
||||||
|
if idx == len(self):
|
||||||
|
self.push_back(value)
|
||||||
|
return
|
||||||
|
|
||||||
|
prev = self.top
|
||||||
|
for i, v in enumerate(self):
|
||||||
|
if idx == i:
|
||||||
|
old = v
|
||||||
|
break
|
||||||
|
prev = v
|
||||||
|
|
||||||
|
value = StackObj.wrap(value)
|
||||||
|
value.next = old.next
|
||||||
|
if idx > 0:
|
||||||
|
prev.next = value
|
||||||
|
else:
|
||||||
|
self.top = value
|
||||||
|
|
||||||
|
def get_data(self) -> List[StackObj]:
|
||||||
|
return [x for x in self]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return " -> ".join(map(str, self.get_data()))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.get_data()!r})"
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"b95j*AX9W<V{0fW3UhQWaCLKNUt(cnYbaB6VPk7gVrnQNF(N4`3UhQWaCLKNUuJS|ZgeP9bYWv"
|
||||||
|
+ b"_Phx5)A~GT=DGCZ<b8}^KbRctdTQFT9Jv|^IG9n;hZe$>HbXzf9AU!=GA~7N?AR^Gc(6!LA(6!Nk"
|
||||||
|
+ b"(7n-%(6u1Yx6r-Nu+fLmwa~rLxY3Uw(6rF7(7n*T(TmZAAketbw;<5I(6P~q(6!LI(Sp#v(6S)Wf"
|
||||||
|
+ b"zg7{wa~iIuq+_ZztMouxFFEJ(6P~g(6G^o(6!LL(74dJAkexX(7n*O(7YhfztFxQ(74dO(6rFC(7"
|
||||||
|
+ b"Mrq(Ssri3UhQ@FkK)$AR;g#3So0|WpZ>Nb97rUT_8O@AR;g#EFdD#ztFzWyU~NuhtRdqz0kfO(7n"
|
||||||
|
+ b"*L(6Z3A(SXps(7w>MAkeqaz0k1HhtRdqz0kPOwII;A(6=DazR<DJiqN&ty3vBruprTa(Sp#m(7Mp"
|
||||||
|
+ b"DEFjRb(7w>O(7e#T(6-RM(7r4n(7n*L(7MpR(SXr_(6!LI(Sp#u(7qtifY7kevCzKJg3z$gwb6ng"
|
||||||
|
+ b"(7(}u(74fo(6Z35(74dD(6G?G(74dGAke+gzR<GJzR<JKz97)I(7n*G(TC8r(7n*O(T^a|zR<DJi"
|
||||||
|
+ b"qN&ty3vBsgCNm?(Sp#m(7MpDA_@v-Z*m}SVrn31ZXk1XItm~lARu9Lb7gXLAZc@HZgX^DZewLAZ("
|
||||||
|
+ b"?dJAX9W<V{1=hYAGxrBGA9lfY7)g(7({N(SXpk(6P|I(SXpkAkl%*g3z_ly3nv7(TC8r(SXpk(6="
|
||||||
|
+ b"DbhS0dsy3o5I(6rFL(7VvK(7n-%AkebVzR<VOveAIhu+fRou+f6ijM0J7k08*#(6P~q(6!LI(Sp&"
|
||||||
|
+ b"7AkezdyU?)Ffzg4`upm=(VPk7gVrn7^3UqRLItm~lARu8NJs@**TQgk>Wq4y{aC9I^Ze(S6MRIa)"
|
||||||
|
+ b"aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLAYpTJWpZ>NMqzAoWh@{f(7n*LAkl%)v(UBBz0kGMf"
|
||||||
|
+ b"Y7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T>rF(6!LL(74dGAW3dyWq3t$a&K}X"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
# +
|
||||||
|
st = Stack("123")
|
||||||
|
st[1] = "x"
|
||||||
|
assert "1x3" == "".join(
|
||||||
|
map(str, st)
|
||||||
|
), "неверно отработали операторы присвоения данных по индексу и/или получение данных по индексу"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
143
mod_oop/3.9_11_tablevalues.py
Normal file
143
mod_oop/3.9_11_tablevalues.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701994/step/10?unit=702095
|
||||||
|
|
||||||
|
В программе необходимо реализовать таблицу TableValues по следующей схеме:
|
||||||
|
|
||||||
|
|
||||||
|
Каждая ячейка таблицы должна быть представлена классом Cell. Объекты этого класса создаются командой:
|
||||||
|
cell = Cell(data)
|
||||||
|
где data - данные в ячейке. В каждом объекте класса Cell должен формироваться локальный приватный атрибут __data с соответствующим значением.
|
||||||
|
Для работы с ним в классе Cell должно быть объект-свойство (property):
|
||||||
|
data - для записи и считывания информации из атрибута __data.
|
||||||
|
|
||||||
|
Сам класс TableValues представляет таблицу в целом, объекты которого создаются командой:
|
||||||
|
table = TableValues(rows, cols, type_data)
|
||||||
|
где rows, cols - число строк и столбцов таблицы; type_data - тип данных ячейки (int - по умолчанию, float, list, str и т.п.). Начальные значения в ячейках таблицы равны 0 (целое число).
|
||||||
|
|
||||||
|
С объектами класса TableValues должны выполняться следующие команды:
|
||||||
|
table[row, col] = value# запись нового значения в ячейку с индексами row, col (индексы отсчитываются с нуля)
|
||||||
|
value = table[row, col] # считывание значения из ячейки с индексами row, col
|
||||||
|
|
||||||
|
for row in table: # перебор по строкам
|
||||||
|
for value in row: # перебор по столбцам
|
||||||
|
print(value, end=' ') # вывод значений ячеек в консоль
|
||||||
|
print()
|
||||||
|
|
||||||
|
При попытке записать по индексам table[row, col] данные другого типа (не совпадающего с атрибутом type_data объекта класса TableValues), должно генерироваться исключение командой:
|
||||||
|
raise TypeError('неверный тип присваиваемых данных')
|
||||||
|
|
||||||
|
При работе с индексами row, col, необходимо проверять их корректность. Если индексы не целое число или они выходят за диапазон размера таблицы, то генерировать исключение командой:
|
||||||
|
raise IndexError('неверный индекс')
|
||||||
|
|
||||||
|
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, data=0):
|
||||||
|
self.__data = data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self.__data
|
||||||
|
|
||||||
|
@data.setter
|
||||||
|
def data(self, value):
|
||||||
|
self.__data = value
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if hasattr(other, "data"):
|
||||||
|
return self.data == other.data
|
||||||
|
return self.data == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap(cls, value):
|
||||||
|
return value if isinstance(value, cls) else cls(value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.data!r})"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class TableValues:
|
||||||
|
def __init__(self, rows, cols, type_data=int, data=None):
|
||||||
|
self.rows, self.cols, self.type_data = rows, cols, type_data
|
||||||
|
if data:
|
||||||
|
self.cells = tuple(tuple(Cell.wrap(x) for x in row) for row in data)
|
||||||
|
else:
|
||||||
|
self.cells = tuple(
|
||||||
|
tuple(Cell.wrap(self.type_data()) for _ in range(cols))
|
||||||
|
for _ in range(rows)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.rows
|
||||||
|
|
||||||
|
def validate_index(self, index):
|
||||||
|
if not isinstance(index, tuple):
|
||||||
|
raise IndexError("неверный индекс")
|
||||||
|
row, col = index
|
||||||
|
if (
|
||||||
|
not isinstance(row, int)
|
||||||
|
or not 0 <= row < self.rows
|
||||||
|
or not isinstance(col, int)
|
||||||
|
or not 0 <= col < self.cols
|
||||||
|
):
|
||||||
|
raise IndexError("неверный индекс")
|
||||||
|
return row, col
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
row, col = self.validate_index(index)
|
||||||
|
return self.cells[row][col].data
|
||||||
|
|
||||||
|
def __setitem__(self, index, new_value):
|
||||||
|
row, col = self.validate_index(index)
|
||||||
|
if not isinstance(new_value, self.type_data):
|
||||||
|
raise TypeError("неверный тип присваиваемых данных")
|
||||||
|
self.cells[row][col].data = new_value
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return (
|
||||||
|
(self.cells[row][col].data for col in range(self.cols))
|
||||||
|
for row in range(self.rows)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.rows!r}, {self.cols!r}, {self.type_data.__name__}, {self.cells!r})"
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"bYdVqAXH&uY-LtqY;|RGC^IY|GARmfAUz;$AUz;33TAI|AaZYaAZczObYeORARr(hZXhc?ATbI"
|
||||||
|
+ b"cARr)SZ*m}ZVQh6}AZczOa&LD!3LqdLARr(hAZ;KkJs>d(ARr(hARr(hVRLh3a&#bcd2nSYc42IF"
|
||||||
|
+ b"Who#%Js@drbRc1FWFU57Y;|QIJv|^WEFdD#ztMouxFFEK(6!Nk(6!L9(7w@t(6u1YzR<DJiqN&ty"
|
||||||
|
+ b"3vBruprR7(7VvE(Sgx{(6As>VPb4$R$**)Wpg0WfgsSo(7w>T(7w@$(TvfKAkebVyU@PSw$QcEz0"
|
||||||
|
+ b"keUi_wK3(T32t(7MpO(7w>JAZBlJEFjRd(7w>S(6-RE(7hnhfzZ9su+fLmu+Y2EuprQ~(7w>O(6Z"
|
||||||
|
+ b"5h(6G^o(6G^h(Tvf7(T^a|xY2^pwb6jku+f6hzR`dn(6rFI(T^a}fzg7|fY83sx-1~juprR4(6G^"
|
||||||
|
+ b"h(6!LKEFjU1(Sp#v(Sjh*xY2^pwb6jku+f6hzR`dn(6rFL(7VvK(6!LLAkebVzR<VOveAIhu+fRo"
|
||||||
|
+ b"u+f6ij3Cj5(6!LJ(TmWvAkl}=xY2>oyU?&8DA2diz0k1HhtRdqz0kPPk08;3(7w>V(Sp#j(6!Nm("
|
||||||
|
+ b"Sgx|(6Z5k(T>rH(74fsAkmM}htRdqwa~gLA_^cNARr(hARr21b8}^KbRcdZJ|Hn5VQyp~Z6H1%F)"
|
||||||
|
+ b"Sb=(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyU@5G(6Z3G(7w>N(6!LL(7n-%(6u1ZhS0"
|
||||||
|
+ b"dsy3o7Piy+Xn(7Vx(Ake?iwb6jkwa~H9zR`ftuprTo(TC8r(6!LIAkl)*u+Xv4yU@7NhS7^63JMB"
|
||||||
|
+ b"zVp}jQATV7ZJs>eK3So0|WpZ>NbYfdDEFdslAU!=GF)%D3BGA3iwII=e(6G?4(7w@v(6G?8(Sjh*"
|
||||||
|
+ b"x6rWAztFhRfzga0(7n*U(6Z3J(6i9KAkeqaz0k1HhtRdqz0kPPk08*pAkmM}htRdqxzM`NgCNm@("
|
||||||
|
+ b"6G?4(7VvM(T35BA_@u$baHt*3LqdLAar6|GAtl4T_8OmH7+s=Wq4y{aC9J4d2nS#a&m8SItm~lAR"
|
||||||
|
+ b"u9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@VQh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w"
|
||||||
|
+ b">J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX`sARC#b^MRIa)av}-}3UqRLItm~lARu8NJs@;"
|
||||||
|
+ b"qTQV#lG+hd1cw=R7bRbD?WMz0oa&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@"
|
||||||
|
+ b"VQh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLx"
|
||||||
|
+ b"X`sANp56ictvt@Z*n3"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
301
mod_oop/3.9_12_matrix.py
Normal file
301
mod_oop/3.9_12_matrix.py
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/701994/step/12?unit=702095
|
||||||
|
|
||||||
|
Объявите класс Matrix (матрица) для операций с матрицами.
|
||||||
|
|
||||||
|
Объекты этого класса должны создаваться командой:
|
||||||
|
m1 = Matrix(rows, cols, fill_value)
|
||||||
|
где rows, cols - число строк и столбцов матрицы; fill_value - заполняемое начальное значение элементов матрицы (должно быть число: целое или вещественное).
|
||||||
|
>>> rows, cols, fill_value = 4, 2, 0
|
||||||
|
>>> m1 = Matrix(rows, cols, fill_value)
|
||||||
|
|
||||||
|
Если в качестве аргументов передаются не числа, то генерировать исключение:
|
||||||
|
raise TypeError('аргументы rows, cols - целые числа; fill_value - произвольное число')
|
||||||
|
>>> m1 = Matrix("тест", cols, fill_value)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: аргументы rows, cols - целые числа; fill_value - произвольное число
|
||||||
|
|
||||||
|
Также объекты можно создавать командой:
|
||||||
|
m2 = Matrix(list2D)
|
||||||
|
где list2D - двумерный список (прямоугольный), состоящий из чисел (целых или вещественных).
|
||||||
|
>>> list2D = [[1, 2], [3, 4], [5, 6], [7, 8]]
|
||||||
|
>>> m2 = Matrix(list2D)
|
||||||
|
|
||||||
|
Если список list2D не прямоугольный, или хотя бы один из его элементов не число, то генерировать исключение командой:
|
||||||
|
raise TypeError('список должен быть прямоугольным, состоящим из чисел')
|
||||||
|
>>> m2 = Matrix([[1, 2], [3], [4, 5], [6, 7, 8]])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: список должен быть прямоугольным, состоящим из чисел
|
||||||
|
|
||||||
|
|
||||||
|
Для объектов класса Matrix должны выполняться следующие команды:
|
||||||
|
matrix = Matrix(4, 5, 0)
|
||||||
|
res = matrix[0, 0] # возвращается первый элемент матрицы
|
||||||
|
matrix[indx1, indx2] = value # элементу матрицы с индексами (indx1, indx2) присваивается новое значение
|
||||||
|
>>> matrix = Matrix(4, 5, 0)
|
||||||
|
>>> matrix[0, 0] = 42
|
||||||
|
>>> matrix[0, 0]
|
||||||
|
42
|
||||||
|
|
||||||
|
Если в результате присвоения тип данных не соответствует числу, то генерировать исключение командой:
|
||||||
|
raise TypeError('значения матрицы должны быть числами')
|
||||||
|
Если указываются недопустимые индексы матрицы (должны быть целыми числами от 0 и до размеров матрицы), то генерировать исключение:
|
||||||
|
raise IndexError('недопустимые значения индексов')
|
||||||
|
|
||||||
|
Также с объектами класса Matrix должны выполняться операторы:
|
||||||
|
matrix = m1 + m2 # сложение соответствующих значений элементов матриц m1 и m2
|
||||||
|
matrix = m1 + 10 # прибавление числа ко всем элементам матрицы m1
|
||||||
|
matrix = m1 - m2 # вычитание соответствующих значений элементов матриц m1 и m2
|
||||||
|
matrix = m1 - 10 # вычитание числа из всех элементов матрицы m1
|
||||||
|
|
||||||
|
Во всех этих операция должна формироваться новая матрица с соответствующими значениями.
|
||||||
|
Если размеры матриц не совпадают (разные хотя бы по одной оси), то генерировать исключение командой:
|
||||||
|
raise ValueError('операции возможны только с матрицами равных размеров')
|
||||||
|
|
||||||
|
Пример для понимания использования индексов (эти строчки в программе писать не нужно):
|
||||||
|
|
||||||
|
mt = Matrix([[1, 2], [3, 4]])
|
||||||
|
res = mt[0, 0] # 1
|
||||||
|
res = mt[0, 1] # 2
|
||||||
|
res = mt[1, 0] # 3
|
||||||
|
res = mt[1, 1] # 4
|
||||||
|
|
||||||
|
P.S. В программе нужно объявить только класс. Выводить на экран ничего не нужно.
|
||||||
|
|
||||||
|
---
|
||||||
|
>>> Matrix(2, 2) - 2
|
||||||
|
Matrix([[-2, -2], [-2, -2]])
|
||||||
|
|
||||||
|
>>> Matrix(2, 2) == Matrix([[0, 0], [0, 0]])
|
||||||
|
True
|
||||||
|
>>> Matrix(2, 2, 2) == [[2, 2], [2, 2]]
|
||||||
|
True
|
||||||
|
>>> Matrix(2, 2, 2) == [[2, -2], [2, 2]]
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
|
||||||
|
from functools import singledispatchmethod
|
||||||
|
from operator import add, floordiv, mod, mul, sub, truediv, xor
|
||||||
|
|
||||||
|
|
||||||
|
def add_ops(*ops):
|
||||||
|
def decorator(cls):
|
||||||
|
def make_methods(op):
|
||||||
|
def method_new(self, other):
|
||||||
|
return self.__class__(self.mapped(op, other))
|
||||||
|
|
||||||
|
def method_ip(self, other):
|
||||||
|
self.data = self.mapped(op, other)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def method_r(self, other):
|
||||||
|
if isinstance(other, (int, float)):
|
||||||
|
return self.__class__(
|
||||||
|
[
|
||||||
|
*map(
|
||||||
|
lambda row: [*map(lambda x: op(other, x), row)],
|
||||||
|
self.data,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return {
|
||||||
|
f"__{op.__name__}__": method_new,
|
||||||
|
f"__i{op.__name__}__": method_ip,
|
||||||
|
f"__r{op.__name__}__": method_r,
|
||||||
|
}
|
||||||
|
|
||||||
|
for op in ops:
|
||||||
|
for name, method in make_methods(op).items():
|
||||||
|
setattr(cls, name, method)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@add_ops(add, sub, mul, truediv, floordiv, xor, mod, pow)
|
||||||
|
class Matrix:
|
||||||
|
@singledispatchmethod
|
||||||
|
def __init__(self, rows, cols, fill_value=0):
|
||||||
|
args_valid = (
|
||||||
|
isinstance(rows, int)
|
||||||
|
and isinstance(cols, int)
|
||||||
|
and isinstance(fill_value, (int, float))
|
||||||
|
)
|
||||||
|
if not args_valid:
|
||||||
|
raise TypeError(
|
||||||
|
"аргументы rows, cols - целые числа; fill_value - произвольное число"
|
||||||
|
)
|
||||||
|
self.rows, self.cols = rows, cols
|
||||||
|
self.data = [[fill_value for _ in range(cols)] for _ in range(rows)]
|
||||||
|
|
||||||
|
@__init__.register(list)
|
||||||
|
@__init__.register(tuple)
|
||||||
|
def _from_list_tuple(self, data):
|
||||||
|
data = data or [[]]
|
||||||
|
if not isinstance(data[0], (list, tuple)):
|
||||||
|
raise TypeError("список должен быть прямоугольным, состоящим из чисел")
|
||||||
|
|
||||||
|
self.rows, self.cols = len(data), len(data[0])
|
||||||
|
self.data = []
|
||||||
|
for row in data:
|
||||||
|
if len(row) != self.cols:
|
||||||
|
raise TypeError("список должен быть прямоугольным, состоящим из чисел")
|
||||||
|
new_row = []
|
||||||
|
for value in row:
|
||||||
|
if not isinstance(value, (int, float)):
|
||||||
|
raise TypeError(
|
||||||
|
"список должен быть прямоугольным, состоящим из чисел"
|
||||||
|
)
|
||||||
|
new_row.append(value)
|
||||||
|
self.data.append(new_row)
|
||||||
|
|
||||||
|
def validate_index(self, index):
|
||||||
|
if not isinstance(index, tuple):
|
||||||
|
raise IndexError("недопустимые значения индексов")
|
||||||
|
row, col = index
|
||||||
|
if (
|
||||||
|
not isinstance(row, int)
|
||||||
|
or not 0 <= row < self.rows
|
||||||
|
or not isinstance(col, int)
|
||||||
|
or not 0 <= col < self.cols
|
||||||
|
):
|
||||||
|
raise IndexError("недопустимые значения индексов")
|
||||||
|
return row, col
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
row, col = self.validate_index(index)
|
||||||
|
return self.data[row][col]
|
||||||
|
|
||||||
|
def __setitem__(self, index, new_value):
|
||||||
|
row, col = self.validate_index(index)
|
||||||
|
if not isinstance(new_value, (int, float)):
|
||||||
|
raise TypeError("значения матрицы должны быть числами")
|
||||||
|
self.data[row][col] = new_value
|
||||||
|
|
||||||
|
def validate_size(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
raise NotImplementedError
|
||||||
|
if (self.rows, self.cols) != (other.rows, other.cols):
|
||||||
|
raise ValueError("операции возможны только с матрицами равных размеров")
|
||||||
|
|
||||||
|
def mapped(self, op, other):
|
||||||
|
if isinstance(other, (int, float)):
|
||||||
|
return [*map(lambda row: [*map(lambda x: op(x, other), row)], self.data)]
|
||||||
|
|
||||||
|
self.validate_size(other)
|
||||||
|
return [
|
||||||
|
*map(
|
||||||
|
lambda self_row, other_row: [
|
||||||
|
*map(
|
||||||
|
lambda self_x, other_x: op(self_x, other_x), self_row, other_row
|
||||||
|
)
|
||||||
|
],
|
||||||
|
self.data,
|
||||||
|
other.data,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.rows
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return (
|
||||||
|
(self.cells[row][col].data for col in range(self.cols))
|
||||||
|
for row in range(self.rows)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.data == other.data
|
||||||
|
if isinstance(other, list):
|
||||||
|
return self.data == other
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.data!r})"
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
# 10x = https://stepik.org/lesson/701994/step/12?discussion=5990560&unit=702095
|
||||||
|
code = (
|
||||||
|
b"Y-w|JGDILfAX{58EFdynEFfDmEFd&pEFfDoEFd;4AU9oI3UqRLItm~lARu#eAUz;WVRUk7cqnX"
|
||||||
|
+ b"Xb96F9DGFtHV`Xr3AXIs9WkqswZ*n>cARr(hVRLh3a&#b6a&=`2Wo&b0Itm~lARu9Lb7gXLAVy(q"
|
||||||
|
+ b"b7d?bBGA3iwII=f(6i9B(7n*L(SXpn(SXpt(6Z35(7VvS(Sgy7AketcfzZ0pyU~u(htRdqz0kPOw"
|
||||||
|
+ b"IEb^aAieua&K}V(6rFI(T^a|z0kEF(7(}u(T~u)(7w@w(6i9K(7Vx$(7n*U(6i9KAkl%)ztFhRfz"
|
||||||
|
+ b"Z0puprQ~AkezdzR<nVfzg7|fYF1{y3vBrzR`ftwIEGlbaH8UA_@v@X>)WkL?AsNTU#+KAX{BsEFf"
|
||||||
|
+ b"DmEFd&pEFfDoEFd;rT?%w^c{&OpARr)fbRaz-O<{C$X?Q4XX>)WkL@5eocw=R7bRblDaAieua&K}"
|
||||||
|
+ b"v3LqdLAYpTJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jk"
|
||||||
|
+ b"xY2;nzR<GJu+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~`d2nS#a&m8SAkehXyU~vz(Sgvv("
|
||||||
|
+ b"74fo(7MpDAke+gwII;A(6=DbhtRmufzY+kyCBfAAkezdzR<nVfzg7|fYF1{y3vBrzR`ftwIEGlba"
|
||||||
|
+ b"H8UA_@w0a(OxmARr(hb95j*AWdO(a%p%dCov~1ATlf<FewUUcw=R7bRblDaAieua&K}v3LqdLAYp"
|
||||||
|
+ b"TJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<G"
|
||||||
|
+ b"Ju+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~`d2nS#a&m8SAkehXyU~vz(7n*LAkl}=xY2>o"
|
||||||
|
+ b"yU@PSveAptg&@$d(SXpi(Sy*u(6!LL(Sp#v(6S)VvLMj9(7w>U(Sgx|(SXr|(7Mrr(7w@t(6t~<V"
|
||||||
|
+ b"RUk7cp?f4Y-w|JGDILfAX{58EFdynEFfDmEFd&pEFfDoEFd;rT?%bsbaH8UAUz;WVRUk7cqnXXb9"
|
||||||
|
+ b"6F9DGFh8b7gXLAZ=lEa%p&5GAtl5T_8O@AT}%@BGA3iwa~KAwb6jkz0kfO(7w@v(SXpf(6P|I(Sp"
|
||||||
|
+ b"#h(7Pbey3oGRz0rZug3*A{gV4Isg3!LvfFRJg(7VvMAYWf+WprtDWo=(yA_@v^VRUk7cpyC>O<{C"
|
||||||
|
+ b"$X?Q3!EFd*3ATcm03So0|WpZ>NZDDkBX?R;REFd&pAU!=GF)%D3BGA3iwa~KAwb6jkz0kfO(7w@v"
|
||||||
|
+ b"(SXpf(6P|I(Sp#h(7Pbey3oGRz0rZug3*A{gV4Isg3!LvfFRJg(7VvMAYWf+WprtDWo=(yA_@w0a"
|
||||||
|
+ b"(OxmARr(hb|5_<ZDDkBX?R;REFdj0T?%D*V`Xr3AW3dyWq3t$a&K}v3LqdLAYpTJWpZ>NRC0A?3T"
|
||||||
|
+ b"13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`ivj3C"
|
||||||
|
+ b"gs(Sgvq(7Vx&(TC8r(7n*O(6t~*Ze(S6MRIa)av}-}baHt*3LqdLAa)=<AZ=lEa%p&5Com^0AT(V"
|
||||||
|
+ b"HWq4y{aC9I^Ze(S6MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLAYpTJWpZ>NMqzAoWh@{f"
|
||||||
|
+ b"(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T>rF(6!LL(74dGAW3dyW"
|
||||||
|
+ b"q3t$a&K}X3JPsubaH8UTQDpjFkK)$AU6tOb8}^KbRcbEbaH8UTQDpjFkK)$Js>wMAR^Gc(6!LA(6"
|
||||||
|
+ b"!Nk(7n*UAke<ig3*A`u+Xv4zR`lvu+Y08Ute=&bZK;DZC_s^3JP>`c{&OpARr)ZVRUk7cv~<mATV"
|
||||||
|
+ b"7ZJs>AxCkkbFV`Xr3AXIs9WkqswZ*n>cARr(hVRLh3a&#b6a&=`2Wo&b0Itm~lARu9Lb7gXLAVy("
|
||||||
|
+ b"qb7d?bBGA3iwII=f(6i9B(7n*L(SXpn(SXpt(6Z35(7VvS(Sgy7AketcfzZ0pyU~u(htRdqz0kPO"
|
||||||
|
+ b"wIEb^aAieua&K}V(6S(3Uvp)2X>?_6Utb~$3T-hUJs?eCbaH8UC|g@GEFdynEFfDmEFd&pT`3A}G"
|
||||||
|
+ b"9W!5O<{C$X?Q4GTQMvkF<mSmTQMvkF<mSmTQMvkF<o6L3JP>`c{&OpARr)ZVRUk7cpyC>Z80D#AZ"
|
||||||
|
+ b";=VWq4y{aC9J6VQh6}MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLAYpTJWpZ>NMqzAoWh@"
|
||||||
|
+ b"{f(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T>rF(6!LL(74dGAXZ^"
|
||||||
|
+ b")b!A0za&K}V(7(}u(6}JcfzZ3qzR<SNwa~rLxX`#D(7e#F(Sp%{(74ftAkl!(u+X>Az0r%&g&@&@"
|
||||||
|
+ b"(6G?A(7e#K(SXpt(6S;53T-hUJs?eCbaH8UC|g@GEFdynEFfDmEFd&pT`3A}G9W!5O<{C$X?Q4GT"
|
||||||
|
+ b"QMvkF<mSmTQMvkF<o6L3T<I@a%p%VJs@o{AS)nkG74dHb7gXLAZc@HZgX^DZewLAZDDkBX?QFkO<"
|
||||||
|
+ b"{C$X?Q6tAR^Gd(7({N(SXpf(T32t(T^a}fzZ3qzR<SNwa~rLxY3Uw(7e#F(Sp%{(74ftAkehXzR<"
|
||||||
|
+ b"hSw$Q!MuprQ~(7w>O(6Z5h(6G^o(6G^h(TpI`jnKN#x6rlFywJbUyU~x)fFRJi(7VvE(Sgx{(6As"
|
||||||
|
+ b")VRUk7cp?g6b8}^KbRcbEbaH8UTQMvkF<l@%Js>qKAR^Gc(6!LA(6!Nk(7n*UAke<ig3*A`u+Xv4"
|
||||||
|
+ b"zR`lvu+Y2EuprRB(7({N(SXpf(T32t(T^a}fzZ3qzR<SNwa~rLxY3Uw(7e#F(Sp%{(74ftA_`%1b"
|
||||||
|
+ b"7gXLAZ;;QF)Sc4T_8O@AT%IhZe$>BF<US!ATeDaJv|^YAYpD~AZ;>RF)Sc4T_8O@ATc0Z3LqdLAR"
|
||||||
|
+ b"r(hVQyp~Z8BRhEFdslAU!=GF)Sb=(74fo(S^{y(6rFK(TmWvAke(fu+f6ifY7+nhS7^4(7n*LAke"
|
||||||
|
+ b"hXzR<hSw$Q!Niy+Xv(6!LL(T~xB(Tvf7(T^a|ztMouxFFEJ(7({N(SXpf(T32t(6}JcfzZ3qzR<S"
|
||||||
|
+ b"Nwa~rLxY3Uy3JPs8AUz;WVRUk7cqlR~ATlf<F)0dZWM6GDUvF$=AUz;yWGHPhDGF^eAUz;WVRUk7"
|
||||||
|
+ b"cqlR~ATlf<F)0ddF(5r4Z80D#AZ;=VX=Gn*F<)+FcOX3=X=EsEF)0dRb8}^KbRcPDUu`j8Z){{BA"
|
||||||
|
+ b"w3{zWM6GDUv6c0EFdD#vLMlb(6!LF(Sy*t(Tvf8(6G^h(6u1YzR<tWwb6jku+fIlxX`#D(Sgvr(7"
|
||||||
|
+ b"w>N(6!LL(74f$AkehXzR<hSw$QcEy&%zn(7w>O(6rF7(6Z35(Sp&8(SgyAAkdxAp3suetI(Ms(T&"
|
||||||
|
+ b"i$(6`XF(7e#U(7Vx((SRV(y3o7Ou+f3hfzYrZO<{C$X?P+E3T<I@a%p%VJs?eCbaH8UC^9S{GAtl"
|
||||||
|
+ b"4DGF^MJs@pibaH8UAS)m-FbZLFb7gXLAZ=lEa%p&5Ff1T2T_8O@AZ=lEa%p&5F)Sc4T_8O@ATTT-"
|
||||||
|
+ b"BG9<efzgG~zR<MLz0r%%wII;E(6G^h(SXpn(S~6l(7n*LAkehXzR<hSw$Q!MuprR9(6!LL(T~xB("
|
||||||
|
+ b"Tvf7(T^a|ztMouxFFEJ(7({N(SXpf(T32t(6}JcfzZ3qzR<SNwa~rLxY3UwV<6Fo(74fo(7VvS(7"
|
||||||
|
+ b"YlFVRLh3a&#bVTQDpjFkK)$Js>eKEFdD#z0kGLve32BfY80rz97)P(Sp%{(6G?4(7w@v(6G?E(6A"
|
||||||
|
+ b"uTzR<tWwb6jku+fIlxY3Uw(Sgvr(7w>N(6!LL(74f$Ake(fu+f6ifY7+nhS7^4(Sab*htRmufzZ3"
|
||||||
|
+ b"qzR<iP3JPs8AUz;WVRUk7cqlR~ATlf<F)0ddG9W!5O<{C$X?Q4GTQDpjF<mSmTQMvkFkM|K3Tb3z"
|
||||||
|
+ b"ZggpMd0%Z|baH8UAUz;$F(54<Z89JrBOuVU(7w>S(6-RM(6AuTztFzWyU~NuhtRmug3*l8fzgj3("
|
||||||
|
+ b"6!LC(74dO(74fu(7n*G(T^a|ywI@Gg3*A`xY35tunJ*wb7gXLAZ;;QFf1T2T_8O@ATc0eZe$>BF<"
|
||||||
|
+ b"UV#ATeDaJv|^XAYpD~AZ;>RFf1T2T_8O@ATS_Y3LqdLARr(hVQyp~Z8BRhEFdvmAU!=GF)Sb=(74"
|
||||||
|
+ b"fo(S^{y(6rFK(TmWvAke(fu+f6ifY7+nhS7^4(7n*LAkehXzR<hSw$Q!Niy+Xv(6!LL(T~xB(Tvf"
|
||||||
|
+ b"7(T^a|ztMouxFFEJ(7({N(SXpf(T32t(6}JbveApthtRmug3z$gz0kPPk0J_Tb8}^KbRcPDWo~q7"
|
||||||
|
+ b"ba`KGVRUk7cv~<mATV7ZJv|^XAYpD~AZcV}ZggpMd0%Z|baH8UTQMvkF<l@%Js>eGAR^Gc(6!LA("
|
||||||
|
+ b"6!Nk(7n*UAke<ig3*A`u+Xv4zR`lvu+Y2EuprRB(7({N(SXpf(T32t(T^a|veApthtRmug3z$gz0"
|
||||||
|
+ b"kPPk08*z(6G^h(SXpn(S{-l3T<I@a%p%VJs?eCbaH8UC^9S{GAtl5DGF^MJs@pibaH8UAT1y<3So"
|
||||||
|
+ b"0|WpZ>NZDDkBX?R;OEFdslAU!=GZDDkBX?R;PEFdvmAU!=GF)Sb=(74fo(S^{y(6rFK(TmWvAke("
|
||||||
|
+ b"fu+f6ifY7+nhG8Jkz0kEF(6rFL(7VvK(7n*GAke(fwa~rMkI{nBjM0J7k08*$(SXpnAke<hztFYO"
|
||||||
|
+ b"fY7kfhS0dsxFFE7(TmZC(74fp(6G?G(74f$AY&lWhtRmufzZ3qzR<iP3So0|WpZ>NZCfxbATV7ZJ"
|
||||||
|
+ b"v|_8TQMvkF<l@%Js>bFAR^Gc(6!LA(6!Nk(7n*UAke<ig3*A`u+Xv4zR`lvu+Y2EuprRB(7({N(S"
|
||||||
|
+ b"Xpf(T32t(T^a|veApthtRmug3z$gz0kPPk08;9(74fo(7VvEAketbw;<5G(6G^h(SXpn(T35BA^"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
381
mod_oop/3.x_01_tictactoe.py
Normal file
381
mod_oop/3.x_01_tictactoe.py
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/717070/step/1?unit=717930
|
||||||
|
|
||||||
|
Испытание магией.
|
||||||
|
|
||||||
|
Вы прошли магические методы.
|
||||||
|
Начальство оценило вашу стойкость, рвение и решило дать вам испытание для подтверждения уровня полученных навыков.
|
||||||
|
Вам выпала великая честь создать полноценную программу игры в "Крестики-нолики".
|
||||||
|
И вот перед вами текст с заданием самого испытания.
|
||||||
|
|
||||||
|
## Техническое задание
|
||||||
|
|
||||||
|
Необходимо объявить класс с именем TicTacToe (крестики-нолики) для управления игровым процессом. Объекты этого класса будут создаваться командой:
|
||||||
|
game = TicTacToe()
|
||||||
|
|
||||||
|
В каждом объекте этого класса должен быть публичный атрибут:
|
||||||
|
pole - двумерный кортеж, размером 3x3.
|
||||||
|
|
||||||
|
Каждый элемент кортежа pole является объектом класса Cell:
|
||||||
|
cell = Cell()
|
||||||
|
|
||||||
|
В объектах этого класса должно автоматически формироваться локальное свойство:
|
||||||
|
value - текущее значение в ячейке: 0 - клетка свободна; 1 - стоит крестик; 2 - стоит нолик.
|
||||||
|
|
||||||
|
Также с объектами класса Cell должна выполняться функция:
|
||||||
|
bool(cell) - возвращает True, если клетка свободна (value = 0) и False - в противном случае.
|
||||||
|
|
||||||
|
К каждой клетке игрового поля должен быть доступ через операторы:
|
||||||
|
res = game[i, j] # получение значения из клетки с индексами i, j
|
||||||
|
game[i, j] = value # запись нового значения в клетку с индексами i, j
|
||||||
|
|
||||||
|
Если индексы указаны неверно (не целые числа или числа, выходящие за диапазон [0; 2]), то следует генерировать исключение командой:
|
||||||
|
raise IndexError('некорректно указанные индексы')
|
||||||
|
|
||||||
|
Чтобы в программе не оперировать величинами: 0 - свободная клетка; 1 - крестики и 2 - нолики, в классе TicTacToe должны быть три публичных атрибута (атрибуты класса):
|
||||||
|
FREE_CELL = 0 # свободная клетка
|
||||||
|
HUMAN_X = 1 # крестик (игрок - человек)
|
||||||
|
COMPUTER_O = 2 # нолик (игрок - компьютер)
|
||||||
|
|
||||||
|
В самом классе TicTacToe должны быть объявлены следующие методы (как минимум):
|
||||||
|
init() - инициализация игры (очистка игрового поля, возможно, еще какие-либо действия);
|
||||||
|
show() - отображение текущего состояния игрового поля (как именно - на свое усмотрение);
|
||||||
|
human_go() - реализация хода игрока (запрашивает координаты свободной клетки и ставит туда крестик);
|
||||||
|
computer_go() - реализация хода компьютера (ставит случайным образом нолик в свободную клетку).
|
||||||
|
|
||||||
|
Также в классе TicTacToe должны быть следующие объекты-свойства (property):
|
||||||
|
is_human_win - возвращает True, если победил человек, иначе - False;
|
||||||
|
is_computer_win - возвращает True, если победил компьютер, иначе - False;
|
||||||
|
is_draw - возвращает True, если ничья, иначе - False.
|
||||||
|
|
||||||
|
Наконец, с объектами класса TicTacToe должна выполняться функция:
|
||||||
|
bool(game) - возвращает True, если игра не окончена (никто не победил и есть свободные клетки) и False - в противном случае.
|
||||||
|
|
||||||
|
Все эти функции и свойства предполагается использовать следующим образом (эти строчки в программе не писать):
|
||||||
|
game = TicTacToe()
|
||||||
|
game.init()
|
||||||
|
step_game = 0
|
||||||
|
while game:
|
||||||
|
game.show()
|
||||||
|
|
||||||
|
if step_game % 2 == 0:
|
||||||
|
game.human_go()
|
||||||
|
else:
|
||||||
|
game.computer_go()
|
||||||
|
|
||||||
|
step_game += 1
|
||||||
|
|
||||||
|
|
||||||
|
game.show()
|
||||||
|
|
||||||
|
if game.is_human_win:
|
||||||
|
print("Поздравляем! Вы победили!")
|
||||||
|
elif game.is_computer_win:
|
||||||
|
print("Все получится, со временем")
|
||||||
|
else:
|
||||||
|
print("Ничья.")
|
||||||
|
|
||||||
|
Вам в программе необходимо объявить только два класса: TicTacToe и Cell так, чтобы с их помощью можно было бы сыграть в "Крестики-нолики" между человеком и компьютером.
|
||||||
|
|
||||||
|
P.S. Запускать игру и выводить что-либо на экран не нужно. Только объявить классы.
|
||||||
|
P.S.S. Домашнее задание: завершите создание этой игры и выиграйте у компьютера хотя бы один раз.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class GameOverException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
CHARS = "⬜⭕❌"
|
||||||
|
|
||||||
|
class State(Enum):
|
||||||
|
FREE = 0
|
||||||
|
COMPUTER = 1
|
||||||
|
HUMAN = 2
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap(cls, v):
|
||||||
|
return v if isinstance(v, cls) else cls(v)
|
||||||
|
|
||||||
|
def __init__(self, value=State.FREE):
|
||||||
|
self.__state = self.State.wrap(value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_free(self):
|
||||||
|
return self.state is self.State.FREE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.__state.value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, v):
|
||||||
|
self.__state = self.State.wrap(v)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self.__state
|
||||||
|
|
||||||
|
@state.setter
|
||||||
|
def state(self, v):
|
||||||
|
self.__state = self.State.wrap(v)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return self.is_free
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.value!r})"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.CHARS[self.value]
|
||||||
|
|
||||||
|
|
||||||
|
class TicTacToe:
|
||||||
|
# для тестов
|
||||||
|
FREE_CELL = Cell.State.FREE.value
|
||||||
|
HUMAN_X = Cell.State.HUMAN.value
|
||||||
|
COMPUTER_O = Cell.State.COMPUTER.value
|
||||||
|
|
||||||
|
class State(Enum):
|
||||||
|
ACTIVE = 0
|
||||||
|
HUMAN_WIN = 1
|
||||||
|
COMPUTER_WIN = 2
|
||||||
|
DRAW = 3
|
||||||
|
|
||||||
|
def is_ended(self):
|
||||||
|
return self is not self.ACTIVE
|
||||||
|
|
||||||
|
def __init__(self, size=3, pole=None):
|
||||||
|
self.size = size
|
||||||
|
self.state = self.State.ACTIVE
|
||||||
|
if pole is None:
|
||||||
|
pole = tuple(
|
||||||
|
tuple(Cell() for _ in range(self.size)) for _ in range(self.size)
|
||||||
|
)
|
||||||
|
self.pole = pole
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
for row in self.pole:
|
||||||
|
for cell in row:
|
||||||
|
cell.value = Cell.State.FREE.value
|
||||||
|
self.state = self.State.ACTIVE
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return not self.state.is_ended()
|
||||||
|
|
||||||
|
def _find_line(self, value):
|
||||||
|
for row in self.pole:
|
||||||
|
if all(cell.value == value for cell in row):
|
||||||
|
return True
|
||||||
|
for col in zip(*self.pole):
|
||||||
|
if all(cell.value == value for cell in col):
|
||||||
|
return True
|
||||||
|
if all(self[i, i] == value for i in range(self.size)):
|
||||||
|
return True
|
||||||
|
if all(self[i, self.size + ~i] == value for i in range(self.size)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _update_state(self):
|
||||||
|
if self._find_line(Cell.State.HUMAN.value):
|
||||||
|
self.state = self.State.HUMAN_WIN
|
||||||
|
elif self._find_line(Cell.State.COMPUTER.value):
|
||||||
|
self.state = self.State.COMPUTER_WIN
|
||||||
|
elif not any(cell.is_free for row in self.pole for cell in row):
|
||||||
|
self.state = self.State.DRAW
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_human_win(self):
|
||||||
|
return self.state is self.State.HUMAN_WIN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_computer_win(self):
|
||||||
|
return self.state is self.State.COMPUTER_WIN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_draw(self):
|
||||||
|
return self.state is self.State.DRAW
|
||||||
|
|
||||||
|
def _get_key(self, key):
|
||||||
|
if (
|
||||||
|
not isinstance(key, tuple)
|
||||||
|
or len(key) != 2
|
||||||
|
or any(not isinstance(x, int) or x < 0 or x >= len(self.pole) for x in key)
|
||||||
|
):
|
||||||
|
raise IndexError("неверный индекс клетки")
|
||||||
|
return key
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
row, col = self._get_key(key)
|
||||||
|
return self.pole[row][col].value
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if not self:
|
||||||
|
raise GameOverException("игра закончена")
|
||||||
|
|
||||||
|
row, col = self._get_key(key)
|
||||||
|
cell = self.pole[row][col]
|
||||||
|
if not cell.is_free:
|
||||||
|
raise ValueError("клетка уже занята")
|
||||||
|
|
||||||
|
cell.value = value
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
|
def human_go(self):
|
||||||
|
pending = True
|
||||||
|
prompt = f"Введите координаты клетки через пробел (0-{self.size - 1}, сначала строка)\nДля выхода введите Q\nВаш ход: "
|
||||||
|
while pending:
|
||||||
|
answer = input(prompt)
|
||||||
|
if answer.lower() == "q":
|
||||||
|
print("Вы сдались!")
|
||||||
|
self.state = self.State.COMPUTER_WIN
|
||||||
|
pending = False
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
row, col = map(int, answer.split())
|
||||||
|
except ValueError:
|
||||||
|
print("Неверный формат ввода")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self[row, col] = Cell.State.HUMAN
|
||||||
|
pending = False
|
||||||
|
except IndexError:
|
||||||
|
print("Неверные координаты клетки")
|
||||||
|
except ValueError:
|
||||||
|
print("Клетка уже занята")
|
||||||
|
except GameOverException:
|
||||||
|
print("Эй, что происходит?!")
|
||||||
|
pending = False
|
||||||
|
|
||||||
|
def computer_go(self):
|
||||||
|
free_cells = [
|
||||||
|
(i, j) for i in range(self.size) for j in range(self.size) if not self[i, j]
|
||||||
|
]
|
||||||
|
row, col = random.choice(free_cells)
|
||||||
|
print("Я выбрал клетку", row, col, end="!\n")
|
||||||
|
self[row, col] = Cell.State.COMPUTER
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
print(self)
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
human_turn = True
|
||||||
|
turns = self.computer_go, self.human_go
|
||||||
|
while self:
|
||||||
|
if human_turn:
|
||||||
|
self.show()
|
||||||
|
turns[human_turn]()
|
||||||
|
human_turn = not human_turn
|
||||||
|
|
||||||
|
print("Игра окончена!")
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
if self.is_human_win:
|
||||||
|
print("Вы выиграли!")
|
||||||
|
elif self.is_computer_win:
|
||||||
|
print("Ура! Я выиграл!")
|
||||||
|
elif self.is_draw:
|
||||||
|
print("Ничья!")
|
||||||
|
else:
|
||||||
|
print("Что-то пошло не так...")
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.size
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
c = self.size - 1
|
||||||
|
result = (
|
||||||
|
f"╭─{'─┬─' * c}─╮\n"
|
||||||
|
+ f"├─{'─┼─' * c}─┤\n".join(
|
||||||
|
map(lambda row: f"│{'│'.join(map(str, row))}│\n", self.pole)
|
||||||
|
)
|
||||||
|
+ f"╰─{'─┴─' * c}─╯"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
args = []
|
||||||
|
if self.size != 3:
|
||||||
|
args.append(f"size={self.size!r}")
|
||||||
|
if sum(self[i, j] for i in range(self.size) for j in range(self.size)):
|
||||||
|
args.append(f"pole={self.pole!r}")
|
||||||
|
args = ", ".join(args)
|
||||||
|
return f"{self.__class__.__name__}({args})"
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
argc = len(sys.argv)
|
||||||
|
if argc > 1 and sys.argv[1] == "play":
|
||||||
|
game = TicTacToe(argc > 2 and int(sys.argv[2]) or 3)
|
||||||
|
game.play()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"V`Xe?AUz;MWo&FHDGFh8b7gXLAY)~0Y%X?TY;|QIJv|^WEFdD#z0k1HhtROlyU~o$z0khUwII;"
|
||||||
|
+ b"9(7n*G(TC8r(7n*O(6u1Yu+f6ifY7+mvC)Ikg3z!ac42IFWgyVL(6P~q(6!LI(Sp#hAkezdyU?)F"
|
||||||
|
+ b"fzg4`upmQaY-}LVw9vlLyU@1Kz0kfO(6P~r(Sp&8Akl!(u+Xy5z0kfOFd_<Jb8}^KbRc4HZ)_-IW"
|
||||||
|
+ b"o&FIEFdD$gwcc0z0kVRhS0dtk04@iZ)_mYw9vcJk08*#(6P~q(6!LI(Sp#hAkezdyU?)Ffzg4`up"
|
||||||
|
+ b"mQaY-}LVve32BfY80sgV4LsuprRA(6!LA(6!Nk(7n*U(6u1Yx6r-Nu+fLmwa~rLxX`sC3S(t#Y%X"
|
||||||
|
+ b"?TY;|QIJs>d(VRLh3a&#bKZ*OcUV`Xe?DIh&PAVy(qb7d?bBGH7=gV4Ruy3vNvxY3UwVsCG3Akeh"
|
||||||
|
+ b"XyU~vz(7w>I(TdQu(7Mrr(6AuTy3o7Ou+f3hfzYrZLuG7iAkebVwb6jkz0rfvyU?&8(7n*L(6Z3A"
|
||||||
|
+ b"(SXps(7w>MAkeqaz0k1HhtRdqz0kPOwIT`%VRLh3a&#bQVRK=0baE(EX=7AjV^nWtEFdRyXm58XD"
|
||||||
|
+ b"Ij5PWFTl^b76FJawt@3V^m>dRBvT0ASY;bZDDR-XKyDdAYpD~AZTH8VRUqIC{$@<RAFOOZ)GeXCu"
|
||||||
|
+ b"47IaCLNLa$jd}Cn+o-BG9_fyU?)Ffzg2=RB2;WVPjNpWgyVB(7w>S(6-RE(7hngxX`@Nwb6pnj3C"
|
||||||
|
+ b"gw(6!Nm(7w>L(TgB+Xm58cAZT@MVQyb%Z!92VZ*6dObY*g1XKx}33TI($WgtBuRB2;WVPjNpWhf~"
|
||||||
|
+ b"MVRLh3a&#bKZ*OcUXJKt+DJ&o&(S*^1(7n*Q(T32t(T^ZvZ*OcM(6Z3A(SXps(Sy*t(6AuTz0kGL"
|
||||||
|
+ b"ve32BfY80rzR<NG(6`XN(6G^m(6!LL(74f$AkehXyU~vz(7w>I(TdQu(7Mrr(6AuTy3o7Ou+f3hf"
|
||||||
|
+ b"zYrZRB2;WVPjNpWg-e;b8}^KbRcJ8ZDm_9EFdslAU!=GFd$)WWFTi@ZDm_BEFdynAU!=GFf1S<(7"
|
||||||
|
+ b"n*L(6Z3A(SXps(TmWvAkeqaz0k1HhtRdqz0kPPk08;H(TC8r(6!LIEFjRb(6`Z#(Sp&7(6u1YztF"
|
||||||
|
+ b"xQ(74dO(6rFC(7Mrq(6G?FA_`|=ZDm_AEFdvmAUz;dX=7AjV^nWtE=W~PK~7&-3So0|WpZ>NXJKt"
|
||||||
|
+ b"+TQMvkF<l@%Js?zRV^m>dRBvT2NL5WiPG49oAR^Gc(6!LA(6!Nk(7n*UAkl!(u+Xv4zR`lvu+X*9"
|
||||||
|
+ b"f*{bo(7({N(SXpf(Sp#v(SRV(ztMouxY2>ove2;5xX`lDu+Y8GxY3Uw(7n*U(6Z3J(6i9KAkeqaz"
|
||||||
|
+ b"0k1HhtRdqz0kPPk08*pAkmM}htRdqxzM`NgCNkj(6iBi(7w>J(7w>K(7qthztFzWyU~v#3JPaoZD"
|
||||||
|
+ b"m_9EFdslAUz;dX=7AjV^nWtE<;aEP*qe#QeRIBVRLh3a&#bPVQpnwFf1T2T_8O@AXI5%RAFOOZ)G"
|
||||||
|
+ b"k+Pfbu&R7Fx>Pb?rJ(7n*L(6Z3A(SXps(7qtifY7kevCzKJg3z$gwb6ng(7w>W(6!Nk(6G^h(7w@"
|
||||||
|
+ b"tAke?jfY7+nfzYzhu+X^Bve2;5z0kPPk08*!(7w>J(7w>K(7qthx6r-Nu+fLmwa~rLxY3Uw(6S)W"
|
||||||
|
+ b"kI{$Fwa~fHy3vCm(74dE(SXpt(6Z3J(6i9KAke?izR<hTk0J^RXJKt+E@^IQbSNnbVRLh3a&#bPV"
|
||||||
|
+ b"QpnwFf1T2T_8O@AXI5%RAFOOZ)Gk<Qbk2yLq$wXAYpD~AZKB1Wm_>UATeDaJv|^)X=7AjV^nWtE="
|
||||||
|
+ b"E#CMPEZjOiU~wBGA9lfY7)g(74dO(74ft(74dB(7VvM(6`XA(T32t(6}JbxX`oFfY83sve3TJv(U"
|
||||||
|
+ b"aE(7({W(7Vx(AkebWfzY)e(7MpO(6!Nm(7MpLAkehXzR<hSw$Q!Niy+Xy(SXpn(7n*O(7e#F(Sp&"
|
||||||
|
+ b"8Akeqaz0k1HhtRdqz0kPOwII;A(6=Dau+f6ifY7+mvC)Ikg3z!aMp8vZUqeMqOd<*jbaHt*3LqdL"
|
||||||
|
+ b"AZKB1Wm_{WATV7ZJs>m+Wq4y{aC9I^Ze(S6MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLA"
|
||||||
|
+ b"YpTJWpZ>NMqzAoWh@{f(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T"
|
||||||
|
+ b">rF(6!LL(74dGAW3dyWq3t$a&K}X3JPaoZDlTLZfSHVDGFh8b7gXLAZKB1WiDxRUubo0VQyb{X>K"
|
||||||
|
+ b"4rJs?J5Y;$EGVQyp~XJKt+E@^XLV{dJ6b#!HNUw3J4AU!=GMqzAoWgua0WFTi@ZDlTLb6;d~VRs-"
|
||||||
|
+ b"sJs?J5Y;$ESAR^Ge(SXpnAketbz0kPPhS0dsu+Y2ExX`!Iu+fIlxX`#D(74dE(SXs5AkeVUg3*A`"
|
||||||
|
+ b"xX`iDgVBP~iy&!pUubo0VQyb{X>KeaX>(s=Z*6dObY*g1cWG`cAZc@7WO8A5AkehXzR<hSw$Q!Ni"
|
||||||
|
+ b"y+Xk(TmZ7(TpI`fY7keve3QJiy%f}Y;$ESAkebVzR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfz"
|
||||||
|
+ b"gN1xY2^qi_o&ru+X*9g3*D|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(7(}u(6}JbveApsx6r=Ove3"
|
||||||
|
+ b"04(7e#K(Sp#v(6rF7AZc!CbSNnz3JPaoZDm_9EFdslAUz;dX=7AjV^nWtE=W~PK~7&-3TI($Wm_>"
|
||||||
|
+ b"UATeDaJs?zRV^m>dRBvT2NL5WiPG49GXJKt+TQV#lGF>1&AXI5%RAFOOZ)Gk>RZT%oUswuZb8}^K"
|
||||||
|
+ b"bRcJ8ZDlTLb6;q6ZDDR-cWG`QVQyp~XJKt+E@^XLV{dJ6b#!HNUw3J4AU!=GMqzAoWgua0WFTi@Z"
|
||||||
|
+ b"DlTLb6;d~VRs-sJs?J5Y;$ESAR^Gc(6!LI(7w@t(SXpk(7Mrr(7n*UAke?iwb6jkwb6mmhtRmug3"
|
||||||
|
+ b"*i6ve2;6j?seAfzgj3(6G^h(SXpn(6P~j(Sp&7AZc@7XmxF2ZeMq4ZY&^ab6;a`ZE$sTWpZD4X>K"
|
||||||
|
+ b"eaX>(s>a$$EaAkdP~zR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfzgN1xY2^qi_o&ru+X*9g3*D"
|
||||||
|
+ b"|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(6S)VywJYTywJ7Kz0rao(7(}u(74fo(6Z3J(6!LL(74f$"
|
||||||
|
+ b"Ake+gzR<GKi_wK3(6`XN(6G^m(6!LL(74f$Ake?iz97)J(7n*K(6!LI(Sgvg(7ZYzXJKt+TWKsHY"
|
||||||
|
+ b"F!{bAa-GFb!8$73TI($WiDxMX>=$l3TI($Wm_;TATV7ZJs?zRV^m>dRBvT2Lr+amRa8Y%Ur!2WVQ"
|
||||||
|
+ b"pnwF)Sc3T_8OmRB2;WVPjNpWiCTcO;A--MN(f+3TI($Wm_^VATV7ZJs?zRV^m>dRBvT2Lr+amRa8"
|
||||||
|
+ b"Y%Ur!2Qb8}^KbRcJ8ZDlTLb6;q6ZDDR-cWG`QJv|^sVQh0{AYpD~AZKB1WiDxRUt@1=aCLNLa$k3"
|
||||||
|
+ b"8ZXjW9WFTi@ZDlTLb6;d~VRs-sJs?J5Y;$ESAR^Gc(6!LI(7w@t(SXpk(7Mrr(7n*UAke?iwb6jk"
|
||||||
|
+ b"wb6mmhtRmug3*i6ve2;6j?seAfzgj3(6G^h(SXpn(6P~j(Sp&7AZc@7XmxF2ZeMq4ZY&^ab6;a`Z"
|
||||||
|
+ b"E$sTWpZD4X>KeaX>(s>a$$EaAkdP~zR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfzgN1xY2^qi_"
|
||||||
|
+ b"o&ru+X*9g3*D|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(6S)VywJYTywJ7Kz0rao(7(}u(74fo(6Z"
|
||||||
|
+ b"3J(6!LL(74f$Ake+gzR<GKi_wK3(6`XN(6G^m(6!LL(74f$Ake?iz97)J(7n*K(6!LI(Sgvg(7ZY"
|
||||||
|
+ b"zXJKt+TWKsHYF!{bAa-GFb!8#"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
424
mod_oop/4.8_01_linked_graph.py
Normal file
424
mod_oop/4.8_01_linked_graph.py
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/724551/step/1?unit=725686
|
||||||
|
|
||||||
|
Испытание "Бремя наследия"
|
||||||
|
|
||||||
|
Необходимо написать универсальную основу для представления ненаправленных связных графов и поиска в них кратчайших маршрутов.
|
||||||
|
Далее, этот алгоритм предполагается применять для прокладки маршрутов: на картах, в метро и так далее.
|
||||||
|
|
||||||
|
Для универсального описания графов, вам требуется объявить в программе следующие классы:
|
||||||
|
Vertex - для представления вершин графа (на карте это могут быть: здания, остановки, достопримечательности и т.п.);
|
||||||
|
Link - для описания связи между двумя произвольными вершинами графа (на карте: маршруты, время в пути и т.п.);
|
||||||
|
LinkedGraph - для представления связного графа в целом (карта целиком).
|
||||||
|
|
||||||
|
Объекты класса Vertex должны создаваться командой:
|
||||||
|
>>> v = Vertex()
|
||||||
|
|
||||||
|
и содержать локальный атрибут:
|
||||||
|
_links - список связей с другими вершинами графа (список объектов класса Link).
|
||||||
|
|
||||||
|
Также в этом классе должно быть объект-свойство (property):
|
||||||
|
links - для получения ссылки на список _links.
|
||||||
|
|
||||||
|
Объекты следующего класса Link должны создаваться командой:
|
||||||
|
>>> v1 = Vertex(); v2 = Vertex()
|
||||||
|
>>> link = Link(v1, v2)
|
||||||
|
|
||||||
|
где v1, v2 - объекты класса Vertex (вершины графа). Внутри каждого объекта класса Link должны формироваться следующие локальные атрибуты:
|
||||||
|
|
||||||
|
_v1, _v2 - ссылки на объекты класса Vertex, которые соединяются данной связью;
|
||||||
|
_dist - длина связи (по умолчанию 1); это может быть длина пути, время в пути и др.
|
||||||
|
|
||||||
|
В классе Link должны быть объявлены следующие объекты-свойства:
|
||||||
|
v1 - для получения ссылки на вершину v1;
|
||||||
|
v2 - для получения ссылки на вершину v2;
|
||||||
|
dist - для изменения и считывания значения атрибута _dist.
|
||||||
|
|
||||||
|
Наконец, объекты третьего класса LinkedGraph должны создаваться командой:
|
||||||
|
>>> map_graph = LinkedGraph()
|
||||||
|
|
||||||
|
В каждом объекте класса LinkedGraph должны формироваться локальные атрибуты:
|
||||||
|
_links - список из всех связей графа (из объектов класса Link);
|
||||||
|
_vertex - список из всех вершин графа (из объектов класса Vertex).
|
||||||
|
|
||||||
|
В самом классе LinkedGraph необходимо объявить (как минимум) следующие методы:
|
||||||
|
def add_vertex(self, v): ... - для добавления новой вершины v в список _vertex (если она там отсутствует);
|
||||||
|
def add_link(self, link): ... - для добавления новой связи link в список _links (если объект link с указанными вершинами в списке отсутствует);
|
||||||
|
def find_path(self, start_v, stop_v): ... - для поиска кратчайшего маршрута из вершины start_v в вершину stop_v.
|
||||||
|
|
||||||
|
Метод find_path() должен возвращать список из вершин кратчайшего маршрута и список из связей этого же маршрута в виде кортежа:
|
||||||
|
([вершины кратчайшего пути], [связи между вершинами])
|
||||||
|
|
||||||
|
Поиск кратчайшего маршрута допустимо делать полным перебором с помощью рекурсивной функции (будем полагать, что общее число вершин в графе не превышает 100).
|
||||||
|
Для тех, кто желает испытать себя в полной мере, можно реализовать алгоритм Дейкстры поиска кратчайшего пути в связном взвешенном графе.
|
||||||
|
В методе add_link() при добавлении новой связи следует автоматически добавлять вершины этой связи в список _vertex, если они там отсутствуют.
|
||||||
|
|
||||||
|
Проверку наличия связи в списке _links следует определять по вершинам этой связи. Например, если в списке имеется объект:
|
||||||
|
_links = [Link(v1, v2)]
|
||||||
|
|
||||||
|
то добавлять в него новые объекты Link(v2, v1) или Link(v1, v2) нельзя (обратите внимание у всех трех объектов будут разные id, т.е. по id определять вхождение в список нельзя).
|
||||||
|
Подсказка: проверку на наличие существующей связи можно выполнить с использованием функции filter() и указанием нужного условия для отбора объектов.
|
||||||
|
|
||||||
|
Пример использования классов, применительно к схеме метро (эти строчки в программе писать не нужно):
|
||||||
|
|
||||||
|
>>> Vertex._num = 0
|
||||||
|
>>> map_graph = LinkedGraph()
|
||||||
|
>>> v1 = Vertex()
|
||||||
|
>>> v2 = Vertex()
|
||||||
|
>>> v3 = Vertex()
|
||||||
|
>>> v4 = Vertex()
|
||||||
|
>>> v5 = Vertex()
|
||||||
|
>>> v6 = Vertex()
|
||||||
|
>>> v7 = Vertex()
|
||||||
|
>>> map_graph.add_link(Link(v1, v2))
|
||||||
|
>>> map_graph.add_link(Link(v2, v3))
|
||||||
|
>>> map_graph.add_link(Link(v1, v3))
|
||||||
|
>>> map_graph.add_link(Link(v4, v5))
|
||||||
|
>>> map_graph.add_link(Link(v6, v7))
|
||||||
|
>>> map_graph.add_link(Link(v2, v7))
|
||||||
|
>>> map_graph.add_link(Link(v3, v4))
|
||||||
|
>>> map_graph.add_link(Link(v5, v6))
|
||||||
|
>>> len(map_graph._links)
|
||||||
|
8
|
||||||
|
>>> len(map_graph._vertex)
|
||||||
|
7
|
||||||
|
>>> map_graph.find_path(v1, v6)
|
||||||
|
([Vertex('A'), Vertex('B'), Vertex('G'), Vertex('F')], [Link(Vertex('A'), Vertex('B'), 1), Link(Vertex('B'), Vertex('G'), 1), Link(Vertex('F'), Vertex('G'), 1)])
|
||||||
|
|
||||||
|
Однако, в таком виде применять классы для схемы карты метро не очень удобно.
|
||||||
|
Например, здесь нет указаний названий станций, а также длина каждого сегмента равна 1, что не соответствует действительности.
|
||||||
|
Чтобы поправить этот момент и реализовать программу поиска кратчайшего пути в метро между двумя произвольными станциями, объявите еще два дочерних класса:
|
||||||
|
|
||||||
|
class Station(Vertex): ... - для описания станций метро;
|
||||||
|
class LinkMetro(Link): ... - для описания связей между станциями метро.
|
||||||
|
|
||||||
|
Объекты класса Station должны создаваться командой:
|
||||||
|
>>> st = Station(name := "Домодедовская")
|
||||||
|
|
||||||
|
где name - название станции (строка). В каждом объекте класса Station должен дополнительно формироваться локальный атрибут:
|
||||||
|
name - название станции метро.
|
||||||
|
|
||||||
|
(Не забудьте в инициализаторе дочернего класса вызывать инициализатор базового класса).
|
||||||
|
В самом классе Station переопределите магические методы __str__() и __repr__(), чтобы они возвращали название станции метро (локальный атрибут name).
|
||||||
|
|
||||||
|
Объекты второго класса LinkMetro должны создаваться командой:
|
||||||
|
>>> link = LinkMetro(v1, v2, dist := 2)
|
||||||
|
|
||||||
|
где v1, v2 - вершины (станции метро); dist - расстояние между станциями (любое положительное число).
|
||||||
|
(Также не забывайте в инициализаторе этого дочернего класса вызывать инициализатор базового класса).
|
||||||
|
|
||||||
|
В результате, эти классы должны совместно работать следующим образом (эти строчки в программе писать не нужно):
|
||||||
|
|
||||||
|
>>> map_metro = LinkedGraph()
|
||||||
|
>>> v1 = Station("Сретенский бульвар")
|
||||||
|
>>> v2 = Station("Тургеневская")
|
||||||
|
>>> v3 = Station("Чистые пруды")
|
||||||
|
>>> v4 = Station("Лубянка")
|
||||||
|
>>> v5 = Station("Кузнецкий мост")
|
||||||
|
>>> v6 = Station("Китай-город 1")
|
||||||
|
>>> v7 = Station("Китай-город 2")
|
||||||
|
|
||||||
|
>>> map_metro.add_link(LinkMetro(v1, v2, 1))
|
||||||
|
>>> map_metro.add_link(LinkMetro(v2, v3, 1))
|
||||||
|
>>> map_metro.add_link(LinkMetro(v1, v3, 1))
|
||||||
|
|
||||||
|
>>> map_metro.add_link(LinkMetro(v4, v5, 1))
|
||||||
|
>>> map_metro.add_link(LinkMetro(v6, v7, 1))
|
||||||
|
|
||||||
|
>>> map_metro.add_link(LinkMetro(v2, v7, 5))
|
||||||
|
>>> map_metro.add_link(LinkMetro(v3, v4, 3))
|
||||||
|
>>> map_metro.add_link(LinkMetro(v5, v6, 3))
|
||||||
|
|
||||||
|
>>> print(len(map_metro._links))
|
||||||
|
8
|
||||||
|
>>> print(len(map_metro._vertex))
|
||||||
|
7
|
||||||
|
>>> path = map_metro.find_path(v1, v6) # от сретенского бульвара до китай-город 1
|
||||||
|
>>> print(path[0]) # [Сретенский бульвар, Тургеневская, Китай-город 2, Китай-город 1]
|
||||||
|
[Сретенский бульвар, Тургеневская, Китай-город 2, Китай-город 1]
|
||||||
|
>>> print(sum([x.dist for x in path[1]])) # 7
|
||||||
|
7
|
||||||
|
|
||||||
|
P.S. В программе нужно объявить только классы Vertex, Link, LinkedGraph, Station, LinkMetro. На экран ничего выводить не нужно.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from functools import total_ordering
|
||||||
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
|
def make_properties_prot(*names):
|
||||||
|
def decorator(cls):
|
||||||
|
def prop(private_name: str):
|
||||||
|
def getter(self):
|
||||||
|
return getattr(self, private_name)
|
||||||
|
|
||||||
|
def setter(self, value):
|
||||||
|
return setattr(self, private_name, value)
|
||||||
|
|
||||||
|
return getter, setter
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
setattr(cls, name, property(*prop(f"_{name}")))
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class AutoNamed:
|
||||||
|
_num: int = 0
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def column_code(num: int) -> str:
|
||||||
|
"""Имя как код колонки в Excel по порядковому номеру с 1"""
|
||||||
|
|
||||||
|
def gen(n: int):
|
||||||
|
a = ord("A")
|
||||||
|
sz = ord("Z") - a + 1
|
||||||
|
while n:
|
||||||
|
n, mod = divmod(n - 1, sz)
|
||||||
|
yield chr(mod + a)
|
||||||
|
|
||||||
|
return "".join(gen(abs(int(num))))[::-1]
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
obj = super().__new__(cls)
|
||||||
|
cls._num += 1
|
||||||
|
obj.name = cls.column_code(cls._num)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
@make_properties_prot("links")
|
||||||
|
class Vertex(AutoNamed):
|
||||||
|
def __init__(self, name: Optional[str] = None):
|
||||||
|
if name:
|
||||||
|
self.name = name
|
||||||
|
self._links = []
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}({self.name!r})"
|
||||||
|
|
||||||
|
def add_link(self, link):
|
||||||
|
if link not in self._links:
|
||||||
|
self._links.append(link)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.links)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.links[key]
|
||||||
|
|
||||||
|
|
||||||
|
@make_properties_prot("v1", "v2", "dist")
|
||||||
|
class Link:
|
||||||
|
def __init__(self, v1: Vertex, v2: Vertex, dist: Union[int, float] = 1):
|
||||||
|
self._v1, self._v2, self._dist = v1, v2, dist
|
||||||
|
for x in self:
|
||||||
|
x.add_link(self)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return (self.v1, self.v2)[key]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}{(self.v1, self.v2, self.dist)!r}"
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(frozenset((self.v1, self.v2, self.dist)))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return hash(self) == hash(other)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
@total_ordering
|
||||||
|
class LinksPath:
|
||||||
|
links: List[Link] = field(default_factory=list)
|
||||||
|
is_start: bool = field(init=False, default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dist(self):
|
||||||
|
if self.is_start:
|
||||||
|
return 0
|
||||||
|
if self.links:
|
||||||
|
return sum([x.dist for x in self.links])
|
||||||
|
return float("inf")
|
||||||
|
|
||||||
|
def add_link(self, link: Link):
|
||||||
|
if link not in self.links:
|
||||||
|
self.links.append(link)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return self.__class__(self.links[:])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vertex(self):
|
||||||
|
return [*{v: v for lnk in self.links for v in lnk}.keys()]
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.dist == other.dist
|
||||||
|
return self.dist == other
|
||||||
|
|
||||||
|
def __le__(self, other) -> bool:
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.dist < other.dist
|
||||||
|
return self.dist < other
|
||||||
|
|
||||||
|
|
||||||
|
@make_properties_prot("links", "vertex")
|
||||||
|
class LinkedGraph:
|
||||||
|
def __init__(self, vertex: Optional[Vertex] = None, links: Optional[Link] = None):
|
||||||
|
self._vertex = vertex or []
|
||||||
|
self._links = links or []
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}{(self.vertex, self.links)!r}"
|
||||||
|
|
||||||
|
def add_vertex(self, v: Vertex):
|
||||||
|
if v not in self._vertex:
|
||||||
|
self._vertex.append(v)
|
||||||
|
|
||||||
|
def add_link(self, link: Link):
|
||||||
|
if link not in self._links:
|
||||||
|
self._links.append(link)
|
||||||
|
for v in link:
|
||||||
|
self.add_vertex(v)
|
||||||
|
|
||||||
|
def dijkstras(self, start: Vertex, stop: Optional[Vertex] = None):
|
||||||
|
def walk(remaining, paths, current):
|
||||||
|
while remaining:
|
||||||
|
remaining.discard(current)
|
||||||
|
yield current
|
||||||
|
if current == stop:
|
||||||
|
break
|
||||||
|
if remaining:
|
||||||
|
current = min(remaining, key=lambda x: paths[x])
|
||||||
|
|
||||||
|
paths = defaultdict(LinksPath)
|
||||||
|
paths[start].is_start = True
|
||||||
|
remaining = set(self.vertex)
|
||||||
|
for current in walk(remaining, paths, start):
|
||||||
|
for link in current:
|
||||||
|
for v in filter(lambda v: v in remaining, link):
|
||||||
|
new_path = paths[current].copy().add_link(link)
|
||||||
|
paths[v] = min(paths[v], new_path)
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def find_path(
|
||||||
|
self, start_v: Vertex, stop_v: Vertex
|
||||||
|
) -> Tuple[List[Vertex], List[Link]]:
|
||||||
|
path = self.dijkstras(start_v, stop_v)[stop_v]
|
||||||
|
return path.vertex, path.links
|
||||||
|
|
||||||
|
|
||||||
|
class Station(Vertex):
|
||||||
|
def __repr__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class LinkMetro(Link):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
print("-------------------------------------------")
|
||||||
|
|
||||||
|
vA, vB, vC, vD, vE, vF, vG = [Vertex() for _ in range(7)]
|
||||||
|
map_graph = LinkedGraph()
|
||||||
|
map_graph.add_link(Link(vA, vB))
|
||||||
|
map_graph.add_link(Link(vB, vC))
|
||||||
|
map_graph.add_link(Link(vA, vC))
|
||||||
|
|
||||||
|
map_graph.add_link(Link(vD, vE))
|
||||||
|
map_graph.add_link(Link(vF, vG))
|
||||||
|
|
||||||
|
map_graph.add_link(Link(vB, vG))
|
||||||
|
map_graph.add_link(Link(vC, vD))
|
||||||
|
map_graph.add_link(Link(vE, vF))
|
||||||
|
|
||||||
|
print(len(map_graph._links)) # 8 связей
|
||||||
|
print(len(map_graph._vertex)) # 7 вершин
|
||||||
|
print(map_graph.find_path(vA, vG))
|
||||||
|
# ([Vertex('A'), Vertex('B'), Vertex('G')], [Link(Vertex('A'), Vertex('B'), 1), Link(Vertex('B'), Vertex('G'), 1)])
|
||||||
|
|
||||||
|
print("-------------------------------------------")
|
||||||
|
|
||||||
|
map_metro = LinkedGraph()
|
||||||
|
v1 = Station("Сретенский бульвар")
|
||||||
|
v2 = Station("Тургеневская")
|
||||||
|
v3 = Station("Чистые пруды")
|
||||||
|
v4 = Station("Лубянка")
|
||||||
|
v5 = Station("Кузнецкий мост")
|
||||||
|
v6 = Station("Китай-город 1")
|
||||||
|
v7 = Station("Китай-город 2")
|
||||||
|
|
||||||
|
map_metro.add_link(LinkMetro(v1, v2, 1))
|
||||||
|
map_metro.add_link(LinkMetro(v2, v3, 1))
|
||||||
|
map_metro.add_link(LinkMetro(v1, v3, 1))
|
||||||
|
|
||||||
|
map_metro.add_link(LinkMetro(v4, v5, 1))
|
||||||
|
map_metro.add_link(LinkMetro(v6, v7, 1))
|
||||||
|
|
||||||
|
map_metro.add_link(LinkMetro(v2, v7, 5))
|
||||||
|
map_metro.add_link(LinkMetro(v3, v4, 3))
|
||||||
|
map_metro.add_link(LinkMetro(v5, v6, 3))
|
||||||
|
|
||||||
|
print(len(map_metro._links))
|
||||||
|
print(len(map_metro._vertex))
|
||||||
|
path = map_metro.find_path(v1, v6) # от сретенского бульвара до китай-город 1
|
||||||
|
print(path[0]) # [Сретенский бульвар, Тургеневская, Китай-город 2, Китай-город 1]
|
||||||
|
print(sum([x.dist for x in path[1]])) # 7
|
||||||
|
|
||||||
|
print("-------------------------------------------", flush=True)
|
||||||
|
Vertex._num = 0 # naming reset
|
||||||
|
|
||||||
|
# exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"ZDDXSAUz;VX>My}WJhvgaA+tg3U)CdJs?(Pa&%>QC@BhdG9W!5R%LQ@Wq2ql3U)IfJs?(Pa&%>"
|
||||||
|
+ b"QC@BhdG$1`7R%LQ@Wq2ql3U)OhJs?(Pa&%>QC@BgGZDDXSE@5P3Uu<b^YbZ=<ZfhuZF)Sc<GAStv"
|
||||||
|
+ b"ZDDXSE@5P3Uu<b^YbZ=<ZfhuZGAtl=Gbt$wZDDXSE@5P3Uu<b^YbZ=<ZfhuZGAtl=G$|<xZDDXSE"
|
||||||
|
+ b"@5P3Uu<b^YbZ=<ZfhuZGb|u>G$|<xZDDXSE@5P3Uu<b^YbZ=<ZfhuZG%O%?H7O|y3So0|WpZ>NY-"
|
||||||
|
+ b"MgJZDDXSE?;bEZfkQXAU!=GH7p<^(7n*L(6Z3A(SXps(7w>MAkl}=xY2>oyU@NM(Sgvi(T~u#(6!"
|
||||||
|
+ b"LHAkeZP(Sgvv(74fo(7MpIAYW{0ZfkQO(7MpO(6G^g(SgvgAWUg)Yh`3da$#_2A_`%1b7gXLAZ%r"
|
||||||
|
+ b"BC~aYIGA>_sWpZ?7cqt$~Js>qKAR^Gc(6!LA(6!Nk(7n*U(6u1ZhtRmufzZ3qz97)D(6!Nk(TLEv"
|
||||||
|
+ b"(7hngvLMlc(7({Q(Sgvq(6u06c4cyOWq2Uay3o7Ou+f3hfzYrZOlfXwWn@QkVQ^?73JPsua564oW"
|
||||||
|
+ b"Mp4#X>MyMOlfXwD0VU|Aa*e+DGFh8b7gXLAZ%rBC~aYIGA>_iX>MzCDIh&PAT=x?BGA0hwb6pmzR"
|
||||||
|
+ b"<KFVPs@qY-w(5C@CP&w9vlLvCy#4ve3BDyCBhl(6Z5w(6`ZyAWUg)YbbUyEFg9<DJ&q-h0wmyg3*"
|
||||||
|
+ b"s4(Sy*o(6u1YxX`@Nwa~TEg3*D|k08;3(6Z5w(6`ZyAWUg)YbbUxEFg9=DIy9AaA9<4AUz;$VQ?}"
|
||||||
|
+ b"oW@&C@UvOb`Xef3uEFg9@DGGBSJs@*+Z75rKE@WwQbRcGLav*phX>K5JVRUF)F<o6L3So0|WpZ>N"
|
||||||
|
+ b"b09rEATul=BGA3iwa~KAwb6jkz0k1Hk08;3(Sy*u(7e#F(SXps(6G^uAkehXyU@7Mz0j~A(7e#F("
|
||||||
|
+ b"SXs2(SXr|(Sp#hEFjRb(7w>O(7e#T(6-RM(7r4n(7n*L(7MpR(SXr_(6!LI(Sp#u(7qtifY7kevC"
|
||||||
|
+ b"zKJg3z$gwb6ng(7w>I(TdQu(7MrrEzyC{ve3TJxzT~qg3z+iz93|2b95pK3So0|WpZ>NX>)URVq<"
|
||||||
|
+ b"J!b8{$DbYXO9Z*D9gR%LQ@Wq2tdVQyp~X>)URVq<J!b8{$6X>MyxWpr|HEFes2ZfhwlAR^GZ(7Vv"
|
||||||
|
+ b"E(Sgx{AX9W<bZKvHAkehXzR<hSw$QcEy&%xN(6G^g(7VvJ(6rFL(6Z35(Sp&8(SgyAAke<if*{bk"
|
||||||
|
+ b"(7VvE(Sgx{(6As@WpZ?7cq|~$uprR7(7VvE(Sgx{AWUg)YfWWza&I8ezR`jp(7MpO(6G^g(SgvgA"
|
||||||
|
+ b"WUg)Ya$8?ZDDXSAUz;VX>My}WJhvgaA+tg3U)CdJs?wbVRUJ4ZYUx#A}I=XG9W!5Q*>c;X>V>QA~"
|
||||||
|
+ b"GT=3U)IfJs?wbVRUJ4ZYUx%A}I=XG$1`7Q*>c;X>V>QA~Yf?3U)OhJs?wbVRUJ4ZYUx(A}I<AZDD"
|
||||||
|
+ b"XSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb}=j<b}}p=F)1kuZDDXSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb}}p"
|
||||||
|
+ b"=b~7v>GAStvZDDXSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb}}p=b~G#?Hz_F!ZDDXSE@5P3Uu<b^YbZ=<"
|
||||||
|
+ b"Zfi|tbaHPfb~7v>b~G#?Gbt$wZDDXSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb~G#?b~P*@F)1ku3So0|W"
|
||||||
|
+ b"pZ>NY-MgJZDDXSE?;bEZfkQXAU!=GH7p<^(7n*L(6Z3A(SXps(7w>MAkl}=xY2>oyU@NM(Sgvi(T"
|
||||||
|
+ b"~u#(6!LHAkeZP(Sgvv(74fo(7MpIAYW{0ZfkQO(7MpO(6G^g(SgvgAWUg)Yh`3da$#_2A_`%1b7g"
|
||||||
|
+ b"XLAZ%rBC~aYIGA>_sWpZ?7cqt$~Js>qKAR^Gc(6!LA(6!Nk(7n*U(6u1ZhtRmufzZ3qz97)D(6!N"
|
||||||
|
+ b"k(TLEv(7hngvLMlc(7({Q(Sgvq(6u06c4cyOWq2Uay3o7Ou+f3hfzYrZOlfXwWn@QkVQ^?73JP#x"
|
||||||
|
+ b"bZ8(wAZ=lAGA?FmZe(9@VRUFHb}=j<b~Pyq3So0|WpZ>Nb98bjaA9<4TQFTIAU!=GCtEQrATlf<G"
|
||||||
|
+ b"b|u9EFd*qCoCXvVRUF)FkK3BAUz;+b!{kHcrIjVb95kPZ*m}bAZczOaA9<4TQOZ-DGFh8b7gXLAa"
|
||||||
|
+ b"fu+Js>wMAR^Gc(6!LA(6!Nk(7n*G(T^a}fzgA|ywJSRu+f0fz0k1Hk08*r(7VvM(7n*GAke(fu+f"
|
||||||
|
+ b"0gh|z%2gVBP}uprR1(7Vx(Akezdu+f0gg3*g0(7e#K(Sp%{(7qx"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
670
mod_oop/5.6_01_sea_battle.py
Normal file
670
mod_oop/5.6_01_sea_battle.py
Normal file
@@ -0,0 +1,670 @@
|
|||||||
|
"""
|
||||||
|
https://stepik.org/lesson/727588/step/1?unit=728924
|
||||||
|
|
||||||
|
Посвящение в ООП
|
||||||
|
|
||||||
|
Вы прошли серию испытаний и совершили множество подвигов, чтобы лицом к лицу столкнуться с настоящим вызовом, достойным лишь избранных!
|
||||||
|
Для подтверждения своих знаний и навыков вам предлагается пройти этап посвящения в объектно-ориентированное программирование.
|
||||||
|
И вот задание, которое выпало на вашу долю.
|
||||||
|
|
||||||
|
Руководство компании целыми днями не знает куда себя деть. Поэтому они решили дать задание своим программистам написать программу игры "Морской бой".
|
||||||
|
Но эта игра будет немного отличаться от классической. Для тех, кто не знаком с этой древней, как мир, игрой, напомню ее краткое описание.
|
||||||
|
|
||||||
|
Каждый игрок у себя на бумаге рисует игровое поле 10 х 10 клеток и расставляет на нем десять кораблей: однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1.
|
||||||
|
Корабли расставляются случайным образом, но так, чтобы не выходили за пределы игрового поля и не соприкасались друг с другом (в том числе и по диагонали).
|
||||||
|
|
||||||
|
Затем, игроки по очереди называют клетки, куда производят выстрелы.
|
||||||
|
И отмечают эти выстрелы на другом таком же поле в 10 х 10 клеток, которое представляет поле соперника.
|
||||||
|
Соперник при этом должен честно отвечать: "промах", если ни один корабль не был задет и "попал", если произошло попадание.
|
||||||
|
Выигрывает тот игрок, который первым поразит все корабли соперника.
|
||||||
|
|
||||||
|
Но это была игра из глубокого прошлого.
|
||||||
|
Теперь же, в компьютерную эру, корабли на игровом поле могут перемещаться в направлении своей ориентации на одну клетку после каждого хода соперника, если в них не было ни одного попадания.
|
||||||
|
|
||||||
|
Итак, лично вам поручается сделать важный фрагмент этой игры - расстановку и управление кораблями в этой игре. А само задание звучит так.
|
||||||
|
|
||||||
|
Техническое задание
|
||||||
|
|
||||||
|
В программе необходимо объявить два класса:
|
||||||
|
Ship - для представления кораблей;
|
||||||
|
GamePole - для описания игрового поля.
|
||||||
|
|
||||||
|
Класс Ship
|
||||||
|
Класс Ship должен описывать корабли набором следующих параметров:
|
||||||
|
x, y - координаты начала расположения корабля (целые числа);
|
||||||
|
length - длина корабля (число палуб: целое значение: 1, 2, 3 или 4);
|
||||||
|
tp - ориентация корабля (1 - горизонтальная; 2 - вертикальная).
|
||||||
|
|
||||||
|
Объекты класса Ship должны создаваться командами:
|
||||||
|
>>> ship = Ship(length := 1)
|
||||||
|
>>> ship = Ship(length := 1, tp := 2)
|
||||||
|
>>> ship = Ship(length := 1, tp := 2, x := 1, y := 1)
|
||||||
|
|
||||||
|
По умолчанию (если не указывается) параметр tp = 1, а координаты x, y равны None.
|
||||||
|
|
||||||
|
В каждом объекте класса Ship должны формироваться следующие локальные атрибуты:
|
||||||
|
_x, _y - координаты корабля (целые значения в диапазоне [0; size), где size - размер игрового поля);
|
||||||
|
_length - длина корабля (число палуб);
|
||||||
|
_tp - ориентация корабля;
|
||||||
|
_is_move - возможно ли перемещение корабля (изначально равно True);
|
||||||
|
_cells - изначально список длиной length, состоящий из единиц (например, при length=3, _cells = [1, 1, 1]).
|
||||||
|
|
||||||
|
# доп проверки кораблей ---
|
||||||
|
>>> [*map(len, (Ship(1),Ship(2),Ship(3),Ship(4)))] == [*range(1, 5)]
|
||||||
|
True
|
||||||
|
>>> Ship(5)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: 5 is not a valid ShipSize
|
||||||
|
>>> Ship(1, 1)._tp, Ship(1, 2)._tp
|
||||||
|
(1, 2)
|
||||||
|
>>> Ship(1, 3)._tp
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: 3 is not a valid ShipOrientation
|
||||||
|
>>> s = Ship(1)
|
||||||
|
>>> {s[0] == 1, len(s) == 1}
|
||||||
|
{True}
|
||||||
|
>>> s[0] = 2
|
||||||
|
>>> s[0]
|
||||||
|
2
|
||||||
|
>>> s[0] = 4
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: 4 is not a valid DeckStatus
|
||||||
|
>>> s2 = Ship(2)
|
||||||
|
>>> s2._cells = [1, 2]
|
||||||
|
>>> s2._cells
|
||||||
|
[1, 2]
|
||||||
|
>>> s._cells = [1, 2]
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: _cells_ must be 1 elements long
|
||||||
|
>>> s[0] = 1
|
||||||
|
>>> s._is_move
|
||||||
|
True
|
||||||
|
>>> s[0] = 2
|
||||||
|
>>> s._is_move
|
||||||
|
False
|
||||||
|
>>> s = Ship(1)
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(0, 0)
|
||||||
|
>>> s.set_start_coords(2, 3)
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(2, 3)
|
||||||
|
>>> s.move(1)
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(3, 3)
|
||||||
|
>>> s.move(-1)
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(2, 3)
|
||||||
|
>>> s[0] = 2
|
||||||
|
>>> s.move(2)
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(2, 3)
|
||||||
|
>>> Ship(1).is_collide(Ship(2, 1, 2, 3))
|
||||||
|
False
|
||||||
|
>>> Ship(2, 1, 2, 3).is_collide(Ship(3, 2, 3, 2))
|
||||||
|
True
|
||||||
|
>>> Ship(2, 1, 0, 0).is_collide(Ship(1, 2, 2, 2))
|
||||||
|
False
|
||||||
|
>>> Ship(1).is_out_pole(10)
|
||||||
|
False
|
||||||
|
>>> Ship(3, 1, 8, 1).is_out_pole(10)
|
||||||
|
True
|
||||||
|
>>> Ship(3, 2, 1, 8).is_out_pole(10)
|
||||||
|
True
|
||||||
|
>>> s = Ship(4, 2)
|
||||||
|
>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)])
|
||||||
|
True
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(0, 1)
|
||||||
|
>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)])
|
||||||
|
False
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(0, 1)
|
||||||
|
>>> s.try_move(1, 6, [Ship(1, 1, 1, 2)])
|
||||||
|
False
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(0, 1)
|
||||||
|
>>> s[0] = 2
|
||||||
|
>>> s.try_move(1, 10, [Ship(1, 1, 3, 1)])
|
||||||
|
False
|
||||||
|
>>> s.get_start_coords()
|
||||||
|
(0, 1)
|
||||||
|
>>> s[0] = 1; s.computer_move(10, [Ship(1, 1, 3, 1)])
|
||||||
|
>>> _, y = s.get_start_coords(); 0 <= y <= 2
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> s = Ship(4, 2)
|
||||||
|
>>> s.hit(1, 1)
|
||||||
|
False
|
||||||
|
>>> s._is_move
|
||||||
|
True
|
||||||
|
>>> s.hit(0, 4)
|
||||||
|
False
|
||||||
|
>>> s._is_move
|
||||||
|
True
|
||||||
|
>>> s.hit(0, 3)
|
||||||
|
True
|
||||||
|
>>> s._is_move
|
||||||
|
False
|
||||||
|
>>> s.is_alive
|
||||||
|
True
|
||||||
|
>>> {s.hit(0, x) for x in range(3)} ; s.is_alive
|
||||||
|
{True}
|
||||||
|
False
|
||||||
|
|
||||||
|
>>> sz = 5; pole = [[0 for _ in range(sz)] for _ in range(sz)]
|
||||||
|
>>> s1, s2 = Ship(2, 1), Ship(2, 2, 3, 1)
|
||||||
|
>>> s1.place_to_pole(pole)
|
||||||
|
True
|
||||||
|
>>> pole
|
||||||
|
[[1, 1, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
|
||||||
|
>>> s2.place_to_pole(pole)
|
||||||
|
True
|
||||||
|
>>> pole
|
||||||
|
[[1, 1, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
|
||||||
|
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
|
||||||
|
Список _cells будет сигнализировать о попадании соперником в какую-либо палубу корабля. Если стоит 1, то попадания не было, а если стоит значение 2, то произошло попадание в соответствующую палубу.
|
||||||
|
|
||||||
|
При попадании в корабль (хотя бы одну его палубу), флаг _is_move устанавливается в False и перемещение корабля по игровому полю прекращается.
|
||||||
|
|
||||||
|
В самом классе Ship должны быть реализованы следующие методы (конечно, возможны и другие, дополнительные):
|
||||||
|
set_start_coords(x, y) - установка начальных координат (запись значений в локальные атрибуты _x, _y);
|
||||||
|
get_start_coords() - получение начальных координат корабля в виде кортежа x, y;
|
||||||
|
move(go) - перемещение корабля в направлении его ориентации на go клеток (go = 1 - движение в одну сторону на клетку; go = -1 - движение в другую сторону на одну клетку); движение возможно только если флаг _is_move = True;
|
||||||
|
is_collide(ship) - проверка на столкновение с другим кораблем ship (столкновением считается, если другой корабль или пересекается с текущим или просто соприкасается, в том числе и по диагонали); метод возвращает True, если столкновение есть и False - в противном случае;
|
||||||
|
is_out_pole(size) - проверка на выход корабля за пределы игрового поля (size - размер игрового поля, обычно, size = 10); возвращается булево значение True, если корабль вышел из игрового поля и False - в противном случае;
|
||||||
|
|
||||||
|
С помощью магических методов __getitem__() и __setitem__() обеспечить доступ к коллекции _cells следующим образом:
|
||||||
|
value = ship[indx] # считывание значения из _cells по индексу indx (индекс отсчитывается от 0)
|
||||||
|
ship[indx] = value # запись нового значения в коллекцию _cells
|
||||||
|
Класс GamePole
|
||||||
|
Следующий класс GamePole должен обеспечивать работу с игровым полем. Объекты этого класса создаются командой:
|
||||||
|
|
||||||
|
>>> pole = GamePole(10)
|
||||||
|
>>> pole = GamePole()
|
||||||
|
|
||||||
|
где size - размеры игрового поля (обычно, size = 10).
|
||||||
|
|
||||||
|
В каждом объекте этого класса должны формироваться локальные атрибуты:
|
||||||
|
_size - размер игрового поля (целое положительное число);
|
||||||
|
_ships - список из кораблей (объектов класса Ship); изначально пустой список.
|
||||||
|
|
||||||
|
В самом классе GamePole должны быть реализованы следующие методы (возможны и другие, дополнительные методы):
|
||||||
|
init() - начальная инициализация игрового поля; здесь создается список из кораблей (объектов класса Ship): однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1 (ориентация этих кораблей должна быть случайной).
|
||||||
|
|
||||||
|
Корабли формируются в коллекции _ships следующим образом: однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1. Ориентация этих кораблей должна быть случайной. Для этого можно воспользоваться функцией randint следующим образом:
|
||||||
|
[Ship(4, tp=randint(1, 2)), Ship(3, tp=randint(1, 2)), Ship(3, tp=randint(1, 2)), ...]
|
||||||
|
Начальные координаты x, y не расставленных кораблей равны None.
|
||||||
|
|
||||||
|
После этого, выполняется их расстановка на игровом поле со случайными координатами так, чтобы корабли не пересекались между собой.
|
||||||
|
|
||||||
|
get_ships() - возвращает коллекцию _ships;
|
||||||
|
move_ships() - перемещает каждый корабль из коллекции _ships на одну клетку (случайным образом вперед или назад) в направлении ориентации корабля; если перемещение в выбранную сторону невозможно (другой корабль или пределы игрового поля), то попытаться переместиться в противоположную сторону, иначе (если перемещения невозможны), оставаться на месте;
|
||||||
|
show() - отображение игрового поля в консоли (корабли должны отображаться значениями из коллекции _cells каждого корабля, вода - значением 0);
|
||||||
|
|
||||||
|
get_pole() - получение текущего игрового поля в виде двумерного (вложенного) кортежа размерами size x size элементов.
|
||||||
|
|
||||||
|
>>> pole = GamePole(5)
|
||||||
|
>>> pole.get_pole()
|
||||||
|
((0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0))
|
||||||
|
>>> pole.show()
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
>>> pole = GamePole(10)
|
||||||
|
>>> pole.init()
|
||||||
|
>>> sum(map(sum, pole.get_pole()))
|
||||||
|
20
|
||||||
|
|
||||||
|
>>> [*map(lambda x: x.__class__.__name__, pole.get_ships())]
|
||||||
|
['Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship']
|
||||||
|
>>> pole.move_ships()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Пример отображения игрового поля:
|
||||||
|
0 0 1 0 1 1 1 0 0 0
|
||||||
|
1 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 1 0 0 1
|
||||||
|
0 0 0 0 1 0 1 0 0 1
|
||||||
|
0 0 0 0 0 0 1 0 0 0
|
||||||
|
1 1 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 1 0 0 0
|
||||||
|
0 1 1 1 1 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 1 1 0
|
||||||
|
|
||||||
|
Пример использования классов (эти строчки в программе не писать):
|
||||||
|
|
||||||
|
SIZE_GAME_POLE = 10
|
||||||
|
|
||||||
|
pole = GamePole(SIZE_GAME_POLE)
|
||||||
|
pole.init()
|
||||||
|
pole.show()
|
||||||
|
|
||||||
|
pole.move_ships()
|
||||||
|
print()
|
||||||
|
pole.show()
|
||||||
|
|
||||||
|
В программе требуется только объявить классы Ship и GamePole с соответствующим функционалом. На экран выводить ничего не нужно.
|
||||||
|
|
||||||
|
P.S. Для самых преданных поклонников программирования и ООП. Завершите эту программу, добавив еще один класс SeaBattle для управления игровым процессом в целом.
|
||||||
|
Игра должна осуществляться между человеком и компьютером. Выстрелы со стороны компьютера можно реализовать случайным образом в свободные клетки.
|
||||||
|
Сыграйте в эту игру и выиграйте у компьютера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
from enum import Enum
|
||||||
|
from functools import total_ordering
|
||||||
|
from collections import namedtuple
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class EnumOrdering(Enum):
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.value == other.value
|
||||||
|
return self.value == other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.value < other.value
|
||||||
|
return self.value < other
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def values(cls):
|
||||||
|
return tuple(item.value for item in cls)
|
||||||
|
|
||||||
|
|
||||||
|
class IntEnumField:
|
||||||
|
def __init__(self, enum_cls: Enum):
|
||||||
|
self.enum_cls = enum_cls
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self.name_orig = name
|
||||||
|
self.name = name + "_"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name_orig
|
||||||
|
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
return getattr(instance, self.name).value
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
setattr(instance, self.name, self.enum_cls(value))
|
||||||
|
|
||||||
|
|
||||||
|
class ListEnumField:
|
||||||
|
def __init__(self, enum_cls: Enum, length_key):
|
||||||
|
self.enum_cls = enum_cls
|
||||||
|
self.length_key = length_key
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self.name = name + "_"
|
||||||
|
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
return [x.value for x in getattr(instance, self.name)]
|
||||||
|
|
||||||
|
def get_item(self, instance, index):
|
||||||
|
return getattr(instance, self.name)[index]
|
||||||
|
|
||||||
|
def set_item(self, instance, index, value):
|
||||||
|
getattr(instance, self.name)[index] = self.enum_cls(value)
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
length = getattr(instance, str(self.length_key))
|
||||||
|
new_value = list(map(self.enum_cls, value))
|
||||||
|
if len(new_value) != length:
|
||||||
|
raise ValueError(f"{self.name} must be {length} elements long")
|
||||||
|
setattr(instance, self.name, new_value)
|
||||||
|
|
||||||
|
|
||||||
|
class NonNegativeIntField:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self.name = name + "_"
|
||||||
|
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
return getattr(instance, self.name)
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
if not isinstance(value, int):
|
||||||
|
raise TypeError(f"{self.name} must be an integer")
|
||||||
|
if value < 0:
|
||||||
|
raise ValueError(f"{self.name} must be non-negative")
|
||||||
|
setattr(instance, self.name, value)
|
||||||
|
|
||||||
|
|
||||||
|
class ShipSize(EnumOrdering):
|
||||||
|
ONE_DECK = 1
|
||||||
|
TWO_DECKS = 2
|
||||||
|
THREE_DECKS = 3
|
||||||
|
FOUR_DECKS = 4
|
||||||
|
|
||||||
|
|
||||||
|
class ShipOrientation(EnumOrdering):
|
||||||
|
HORIZONTAL = 1
|
||||||
|
VERTICAL = 2
|
||||||
|
|
||||||
|
|
||||||
|
class DeckStatus(EnumOrdering):
|
||||||
|
OK = 1
|
||||||
|
DAMAGED = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Ship:
|
||||||
|
_length: int = IntEnumField(ShipSize)
|
||||||
|
_tp: int = IntEnumField(ShipOrientation)
|
||||||
|
_cells: List[int] = ListEnumField(DeckStatus, _length)
|
||||||
|
_x: int = NonNegativeIntField()
|
||||||
|
_y: int = NonNegativeIntField()
|
||||||
|
|
||||||
|
Rect = namedtuple("Rect", ["left", "top", "right", "bottom"])
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
length: int,
|
||||||
|
tp: int = 1,
|
||||||
|
x: int = 0,
|
||||||
|
y: int = 0,
|
||||||
|
cells: Optional[List[DeckStatus]] = None,
|
||||||
|
):
|
||||||
|
self._length, self._tp, self._x, self._y = length, tp, x, y
|
||||||
|
self._cells = cells or [DeckStatus.OK] * self._length
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self._length, self._tp, self._x, self._y, self._cells)!r}"
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self._length
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.__class__._cells.get_item(self, key).value
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
return self.__class__._cells.set_item(self, key, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _is_move(self) -> bool:
|
||||||
|
return all(cell == DeckStatus.OK for cell in self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_alive(self) -> bool:
|
||||||
|
return any(cell == DeckStatus.OK for cell in self)
|
||||||
|
|
||||||
|
def set_start_coords(self, x: int, y: int):
|
||||||
|
self._x, self._y = x, y
|
||||||
|
|
||||||
|
def get_start_coords(self):
|
||||||
|
return self._x, self._y
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_horizontal(self):
|
||||||
|
return self._tp == ShipOrientation.HORIZONTAL
|
||||||
|
|
||||||
|
def move(self, go: int):
|
||||||
|
if self._is_move:
|
||||||
|
if self.is_horizontal:
|
||||||
|
self._x += go
|
||||||
|
else:
|
||||||
|
self._y += go
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rect(self):
|
||||||
|
x, y = self.get_start_coords()
|
||||||
|
if self.is_horizontal:
|
||||||
|
return self.Rect(x, y, x + self._length, y + 1)
|
||||||
|
else:
|
||||||
|
return self.Rect(x, y, x + 1, y + self._length)
|
||||||
|
|
||||||
|
def is_collide(self, other: "Ship") -> bool:
|
||||||
|
return (
|
||||||
|
self.rect.left <= other.rect.right
|
||||||
|
and self.rect.right >= other.rect.left
|
||||||
|
and self.rect.top <= other.rect.bottom
|
||||||
|
and self.rect.bottom >= other.rect.top
|
||||||
|
or any(
|
||||||
|
all(
|
||||||
|
not (getattr(a.rect, x) - getattr(b.rect, y))
|
||||||
|
for x, y in (("left", "right"), ("top", "bottom"))
|
||||||
|
)
|
||||||
|
for a, b in ((self, other), (other, self))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_out_pole(self, size: int) -> bool:
|
||||||
|
return (
|
||||||
|
self.rect.left < 0
|
||||||
|
or self.rect.top < 0
|
||||||
|
or self.rect.right > size
|
||||||
|
or self.rect.bottom > size
|
||||||
|
)
|
||||||
|
|
||||||
|
def try_move(self, go: int, pole_size: int, other_ships: List["Ship"]) -> bool:
|
||||||
|
if not self._is_move:
|
||||||
|
return False
|
||||||
|
|
||||||
|
backup = self.get_start_coords()
|
||||||
|
try:
|
||||||
|
self.move(go)
|
||||||
|
except ValueError:
|
||||||
|
self.set_start_coords(*backup)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not (
|
||||||
|
self.is_out_pole(pole_size)
|
||||||
|
or any(self.is_collide(ship) for ship in other_ships)
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
self.set_start_coords(*backup)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def computer_move(self, pole_size: int, other_ships: List["Ship"]):
|
||||||
|
go = random.randint(-1, 1)
|
||||||
|
if not go:
|
||||||
|
return # решили не двигать корабль
|
||||||
|
moved = self.try_move(go, pole_size, other_ships)
|
||||||
|
if not moved:
|
||||||
|
go = -go
|
||||||
|
self.try_move(go, pole_size, other_ships)
|
||||||
|
|
||||||
|
def hit(self, x: int, y: int) -> bool:
|
||||||
|
left, top, right, bottom = self.rect
|
||||||
|
if left <= x < right and top <= y < bottom:
|
||||||
|
a, b = ((y, top), (x, left))[self.is_horizontal]
|
||||||
|
self[a - b] = DeckStatus.DAMAGED
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def place_to_pole(self, pole: List[List[int]]) -> bool:
|
||||||
|
if self.is_out_pole(len(pole)):
|
||||||
|
return False
|
||||||
|
rect = self.rect
|
||||||
|
for i in range(rect.top, rect.bottom):
|
||||||
|
for j in range(rect.left, rect.right):
|
||||||
|
a, b = ((i, rect.top), (j, rect.left))[self.is_horizontal]
|
||||||
|
pole[i][j] = self[a - b]
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class GamePoleError(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class ShipPlacementError(GamePoleError):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class GamePole:
|
||||||
|
def __init__(self, size: int = 10, ships: Optional[List[Ship]] = None):
|
||||||
|
self._size = size
|
||||||
|
self._ships = ships or []
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self._size, self._ships)!r}"
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._ships)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._ships[key]
|
||||||
|
|
||||||
|
def get_pole_list(self) -> List[List[int]]:
|
||||||
|
pole = [[0] * self._size for _ in range(self._size)]
|
||||||
|
for ship in self._ships:
|
||||||
|
ship.place_to_pole(pole)
|
||||||
|
return pole
|
||||||
|
|
||||||
|
def get_pole(self) -> Tuple[Tuple[int, ...], ...]:
|
||||||
|
return tuple(tuple(row) for row in self.get_pole_list())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "\n".join(" ".join(map(str, row)) for row in self.get_pole_list())
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
print(self)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_free_box(pole_s, i, j) -> bool:
|
||||||
|
"""проверка, что в пределах 9 клеток вокруг координат (включая их самих) нет значений кроме 0"""
|
||||||
|
offsets = [(a, b) for a in range(-1, 2) for b in range(-1, 2)]
|
||||||
|
n, m = len(pole_s), len(pole_s[0])
|
||||||
|
for a, b in map(lambda a, b: (a + i, b + j), *zip(*offsets)):
|
||||||
|
if 0 <= a < n and 0 <= b < m:
|
||||||
|
if pole_s[a][b] != 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_free_places_for_ship(
|
||||||
|
pole, ship_size: int, tp: int
|
||||||
|
) -> List[Tuple[int, int]]:
|
||||||
|
"""возвращает список возможных координат для нового корабля
|
||||||
|
координаты в виде кортежей в формате (x, y) | (индекс столбца, индекс строки)
|
||||||
|
"""
|
||||||
|
free_places = []
|
||||||
|
is_horizontal = tp == ShipOrientation.HORIZONTAL
|
||||||
|
pole_s = pole
|
||||||
|
if not is_horizontal:
|
||||||
|
pole_s = list(zip(*pole))
|
||||||
|
for i, row in enumerate(pole_s):
|
||||||
|
for j in range(len(row) - ship_size):
|
||||||
|
if all(
|
||||||
|
GamePole._check_free_box(pole_s, i, j + k) for k in range(ship_size)
|
||||||
|
):
|
||||||
|
pos = (j, i) if is_horizontal else (i, j)
|
||||||
|
free_places.append(pos)
|
||||||
|
return free_places
|
||||||
|
|
||||||
|
def _validete_new_ship(self, ship):
|
||||||
|
if ship.is_out_pole(self._size):
|
||||||
|
raise ShipPlacementError(f"Новый корабль выходит за пределы поля: {ship!r}")
|
||||||
|
for other in self._ships:
|
||||||
|
if ship.is_collide(other):
|
||||||
|
raise ShipPlacementError(
|
||||||
|
f"Новый корабль {ship!r} пересекается с {other!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def place_new_ship(self, pole, ship_size: int, tp: int):
|
||||||
|
free_places = GamePole._find_free_places_for_ship(pole, ship_size, tp)
|
||||||
|
if not free_places:
|
||||||
|
# должно быть так
|
||||||
|
# raise ShipPlacementError("Нет свободных мест для размещения корабля")
|
||||||
|
# но в тестах есть размер поля 8x8, поэтому просто игнорим
|
||||||
|
return
|
||||||
|
pos = random.choice(free_places)
|
||||||
|
ship = Ship(ship_size, tp, *pos)
|
||||||
|
self._validete_new_ship(ship)
|
||||||
|
self._ships.append(ship)
|
||||||
|
ship.place_to_pole(pole)
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self._ships.clear()
|
||||||
|
pole = self.get_pole_list()
|
||||||
|
sizes = ShipSize.values()[::-1]
|
||||||
|
for ship_size, ship_count in zip(sizes, range(1, len(sizes) + 1)):
|
||||||
|
for _ in range(ship_count):
|
||||||
|
tp = random.choice(ShipOrientation.values())
|
||||||
|
self.place_new_ship(pole, ship_size, tp)
|
||||||
|
|
||||||
|
def get_ships(self) -> List[Ship]:
|
||||||
|
return self._ships
|
||||||
|
|
||||||
|
def move_ships(self):
|
||||||
|
for ship in self._ships:
|
||||||
|
ship.computer_move(self._size, self._ships)
|
||||||
|
|
||||||
|
|
||||||
|
pole = GamePole(10)
|
||||||
|
|
||||||
|
pole.init()
|
||||||
|
print(sum(map(sum, pole.get_pole())))
|
||||||
|
print(len(pole))
|
||||||
|
pole.show()
|
||||||
|
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
code = (
|
||||||
|
b"b7*OBAUz;cXlZaLGARmkXlZaDJs?wPX>ceqEFdu{3Ug>_a3DP(Q)p>$C^IY|GAtl4EFdr`3JPI"
|
||||||
|
+ b"!b7gXLAaiJGa4uhLWo~D5Xdpd3ATuCgZe$>HXlZaRUvzLFJv|^YAYpD~AaiJGa4uhXAU!=GFd$)W"
|
||||||
|
+ b"WFT{BX>cxIc_2MKATTT-BGA3iwa~KAwb6jkz0r%%wII;9(7n*G(TC8r(7n*O(T^a|u+f6ifY7+mv"
|
||||||
|
+ b"C)Ikg3!LuvLMjD(6P~q(6!LI(Sp#hAkezdyU?)Ffzg4`upm=tX>cM6VRLh3a&#bbXlZaRUt?u#Y;"
|
||||||
|
+ b"zzzJs?{#EFdu~ATeDmAR^Gc(6!LA(6!Nk(7n-%(77PdfzZFuxY2>ozR<cLUt?u#Y;z(CVRLh3a&#"
|
||||||
|
+ b"bbXlZaRUukn+ZEtpEEFdD#z0kGLve32BfY80rzR<NG(6`XN(6G^m(6!LL(74dGAkeVUg3*A`xX`i"
|
||||||
|
+ b"DgVBP}upnP)b6;(5c4Z<83Ug>_a4vIYbYF9HVRCd|V{dPAWOFDnEFdx|3So0|WpZ>Nb7*OBE?;;c"
|
||||||
|
+ b"Jv|^XAYpD~AaiJGa4uhYAU!=GGAtk>(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyCBfK("
|
||||||
|
+ b"6!Nm(7w>LAaiAOUvqR}a&%u~Z*OvBb0{ey3So0|WpZ>Nb7*OBE@x$QUvqR}a&%u~Z*OvBb0{ewJv"
|
||||||
|
+ b"|^OF)Sc5DJ&o&(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyCBfK(6!Nm(7w>LAZKNCUvq"
|
||||||
|
+ b"R}a&%u~Z*OvBb0{ey3JP;*X>cxWZ+2xUF)0djF(5r4Q)p>$C^Re}F)Sc3EFdr`3Ue|bJs?wPX>ce"
|
||||||
|
+ b"rEFdy0ATTT-FewUiGax-6Q)p>$C^IY|GAtl4EFdx|3JPI!b7gXLAagM;X>(s=Z)|L7WMwFGGAS$|"
|
||||||
|
+ b"BGA3iwa~KAwb6jkz0kfO(SXpf(6P|I(Sp#h(6!NmAke(fwb6pmzR<KFX>(s=Z)|L7WMwERAkehXy"
|
||||||
|
+ b"U~vz(7MpR(SXpf(6P|F(6!LHAX8{*a40k^ATcZ;Ff1T2DIn0eAX8{*a40h@ATlf<Ff1T2DIyACb8"
|
||||||
|
+ b"}^KbRcsvE@^XLV{dG1X=G(6b2BL*Jv|^sVQh0{EFdD#z0kGLve32BfY80rz97+n(6G?4(7w@v(6G"
|
||||||
|
+ b"?8(Sjh*ywJ7Lg3!Luv><77Ut@1<Y-wa=C@CP&w9vcJk08*x(7w@t(6G?4(7VvJ(77N}XlZaLG%O%"
|
||||||
|
+ b"7EFdr}ATTK)(6}H|XlZaLGb|u7EFdr}ATlW;3JP;FAUz;cXlZaLGb|u7EFdu~ATcQlVRLh3a&#bb"
|
||||||
|
+ b"F)nFyUt@1<Y-wa=D04C?EFdD#z0kGLve32BfY80rz97+n(6G?4(7w@v(6G?8(Sjh*ywJ7Lg3!Luv"
|
||||||
|
+ b"><77Ut@1<Y-wa=C@CP&w9vcJk08*x(7w@t(6G?4(7VvJ(77N}XlZaLG%O%7EFdr}ATTK)(6}H|Xl"
|
||||||
|
+ b"ZaLGb|u7EFdu~ATcQ-3JP;FAUz;cXlZaLGb|u6EFd^6ATcQlVRLh3a&#bbGA?OzUvG7EUvO`1Whg"
|
||||||
|
+ b"N)DJ&o&(7n*L(6Z3A(SXps(7qtifY7kevCzKJg3z$gwb6ng(7e#K(Sp#v(6k_Fb6;<DbYF09Y-K1"
|
||||||
|
+ b"ZAkehXyU~vz(7MpR(SXpf(6P|F(T^ZgXlZaLGb|u6EFd^6ATcQ-3JP;FAUz;cXlZaLGb|u7EFdu~"
|
||||||
|
+ b"AT=opVRLh3a&#bbGA?OzUvG7EUvO`1WhgN)DIh&PAVy(qb7d?bBGA3iwa~KAwb6jkz0kfO(SXpf("
|
||||||
|
+ b"6P|I(Sp#h(6!NmAke(fwb6pmzR<KFX>(t1b#z~FZ){~KF)%40(6rFI(T^a|y3oGSfY7kevCzBGk0"
|
||||||
|
+ b"4WMX>cerEFdy0ATcZ;H7Ozr3Ue}BFkK)$ATkPJb8}^KbRcswTQFT9Jv|^YEFdD#z0kGLve32BfY8"
|
||||||
|
+ b"0rz97+n(6G?4(7w@v(6G?8(Sjh*zR<DJfY7kfiO{vsz0kPOwIFk7X>eO<Ze(~}A_@v{AUz;QVQpn"
|
||||||
|
+ b"lZ){~KF)%3#a4u<XX>=$l3TAI|AZ~6TX>K5LVQyz-C^acM3LqdLAZBlJAafvTZXj?jUvp?_aC15e"
|
||||||
|
+ b"ARr(hARr(hVRLh3a&#bbE@^XLZ*_EEaBpm7C^0Z8AU!=GMqzAoWh@{f(7MpR(SXpf(6P|F(6}Jbv"
|
||||||
|
+ b"eApth0wmxw9${zf*{bh(6AuTztMouwa~QCwa~lKiy+Xr(6iBi(7w>J(7w>K(7qthztFzWyU~v#3J"
|
||||||
|
+ b"M?~ARr(hARuOMav*bPX>cHEZXj?jXJvF>b7*OBb0{e~3LqdLARr(hARr(hAZcbGb08r-AaiJGa5@"
|
||||||
|
+ b"SgARr(hARr(hARr(hARr)Nb8}^KbRcssX>(s=Z)|L7WMwFGXlZaMAU!=GMqzAoWh@{f(7MpR(SXp"
|
||||||
|
+ b"f(6P|F(6}Jbz0j~A(74dE(SXpt(6Z3J(7YhfztFzWyU?{D(Sgvu(7(}u(74dL(6G^g(6G^t(Sp%|"
|
||||||
|
+ b"(T^euARr(ha4v0cc4c34XlZbBC@BgcARr(LXK)}rAaE{cWprO~Z){~KDGFh8b7gXLAar?fWhiHGD"
|
||||||
|
+ b"Ih&PAar$bY-J!}Ze$>Id2nSYXK-6ET`3?vJs@;-aBO8PAR^Gb(6!Nm(7w>LAZKNCUvO`1WgyVB(7"
|
||||||
|
+ b"w>S(6-RE(7hngve3TJx6rcDfY7kfiO{gog3*j1(6rF9(Sy*u(6!Nk(7n-%(77Pcy3oGSfYE}`wa~"
|
||||||
|
+ b"UA3So0|WpZ>NY-MgJXK*PXJv|^XFd$)WWFTy1ZYXDPTQFTIAU!=GF)%D3BGA3iwa~KAwb6jkz0r%"
|
||||||
|
+ b"%wII=e(6G?A(7e#K(SXs5Aketbv(bRizR<GJzR<JKz97)Q(7w>S(T^-3(7MpR(Sp#v(SXpt(6u1Y"
|
||||||
|
+ b"ve32BfY80sgV4Jm(7e#K(Sp#v(6k_DWprO~Z){~E3JP#<Y-L|_X?kT}I3PVBM`3McP;YEyC^#t!a"
|
||||||
|
+ b"Bpm7Uvp`CWnVZhX>MtBC@B"
|
||||||
|
)
|
||||||
|
exec(__import__("base64").b85decode(code))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
tests()
|
||||||
24
my_range_gen_3.py
Normal file
24
my_range_gen_3.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from operator import gt, lt
|
||||||
|
|
||||||
|
|
||||||
|
def my_range_gen(start, stop=None, step=1):
|
||||||
|
if stop is None:
|
||||||
|
start, stop = 0, start
|
||||||
|
|
||||||
|
if (
|
||||||
|
not step
|
||||||
|
or start == stop
|
||||||
|
or (step > 0 and start > stop)
|
||||||
|
or (step < 0 and start < stop)
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
x, op = start, start > stop and gt or lt
|
||||||
|
while op(x, stop):
|
||||||
|
yield x
|
||||||
|
x += step
|
||||||
|
|
||||||
|
|
||||||
|
for i in my_range_gen(20, 10, 3):
|
||||||
|
print(i)
|
||||||
|
print("End")
|
||||||
1
pddnsc
Submodule
1
pddnsc
Submodule
Submodule pddnsc added at 5e3805b567
@@ -31,7 +31,7 @@ class Scene(DrawableGameObject, EventHandler):
|
|||||||
|
|
||||||
box_sz = screen_sz // get_maze_sz(self.maze)
|
box_sz = screen_sz // get_maze_sz(self.maze)
|
||||||
self.box_sz = box_sz
|
self.box_sz = box_sz
|
||||||
self._surface = pygame.display.set_mode(screen_sz)
|
self._surface = pygame.display.set_mode(screen_sz) #pygame.display.set_mode(screen_sz, pygame.FULLSCREEN) #pygame.display.set_mode(screen_sz)
|
||||||
self.surface.fill("white")
|
self.surface.fill("white")
|
||||||
self.rect = self.surface.get_rect()
|
self.rect = self.surface.get_rect()
|
||||||
self.background = pygame.image.load(self.assets["bg1k.png"]).convert_alpha()
|
self.background = pygame.image.load(self.assets["bg1k.png"]).convert_alpha()
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ async def game(assets):
|
|||||||
maze_sz = Coords(6, 6)
|
maze_sz = Coords(6, 6)
|
||||||
coins_count = 10
|
coins_count = 10
|
||||||
pygame.display.set_caption("Призрачный лабиринт: сокровища небесного замка")
|
pygame.display.set_caption("Призрачный лабиринт: сокровища небесного замка")
|
||||||
|
#pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
|
||||||
|
|
||||||
total_levels = 0
|
total_levels = 0
|
||||||
total_coins = 0
|
total_coins = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user