""" 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@5P3UuG$|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"K5JVRUF)FN" + 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>)URVqMyxWpr|HEFes2ZfhwlAR^GZ(7Vv" + b"E(Sgx{AX9WMy}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}IGAStvZDDXSE@5P3Uub~G#?Gbt$wZDDXSE@5P3UuNY-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}=jNb98bjaA9<4TQFTIAU!=GCtEQrATlfwMAR^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()