+ 4.8_01 LinkedGraph
This commit is contained in:
parent
1b7e8b8147
commit
e96d20343b
418
mod_oop/4.8_01_linked_graph.py
Normal file
418
mod_oop/4.8_01_linked_graph.py
Normal file
@ -0,0 +1,418 @@
|
||||
"""
|
||||
https://stepik.org/lesson/724551/step/1?unit=725686
|
||||
|
||||
Испытание "Бремя наследия"
|
||||
|
||||
Необходимо написать универсальную основу для представления ненаправленных связных графов и поиска в них кратчайших маршрутов.
|
||||
Далее, этот алгоритм предполагается применять для прокладки маршрутов: на картах, в метро и так далее.
|
||||
|
||||
Для универсального описания графов, вам требуется объявить в программе следующие классы:
|
||||
Vertex - для представления вершин графа (на карте это могут быть: здания, остановки, достопримечательности и т.п.);
|
||||
Link - для описания связи между двумя произвольными вершинами графа (на карте: маршруты, время в пути и т.п.);
|
||||
LinkedGraph - для представления связного графа в целом (карта целиком).
|
||||
|
||||
Объекты класса Vertex должны создаваться командой:
|
||||
v = Vertex()
|
||||
и содержать локальный атрибут:
|
||||
_links - список связей с другими вершинами графа (список объектов класса Link).
|
||||
|
||||
Также в этом классе должно быть объект-свойство (property):
|
||||
links - для получения ссылки на список _links.
|
||||
|
||||
Объекты следующего класса Link должны создаваться командой:
|
||||
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() и указанием нужного условия для отбора объектов.
|
||||
|
||||
Пример использования классов, применительно к схеме метро (эти строчки в программе писать не нужно):
|
||||
|
||||
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))
|
||||
|
||||
print(len(map_graph._links)) # 8 связей
|
||||
print(len(map_graph._vertex)) # 7 вершин
|
||||
path = map_graph.find_path(v1, v6)
|
||||
|
||||
Однако, в таком виде применять классы для схемы карты метро не очень удобно.
|
||||
Например, здесь нет указаний названий станций, а также длина каждого сегмента равна 1, что не соответствует действительности.
|
||||
Чтобы поправить этот момент и реализовать программу поиска кратчайшего пути в метро между двумя произвольными станциями, объявите еще два дочерних класса:
|
||||
|
||||
class Station(Vertex): ... - для описания станций метро;
|
||||
class LinkMetro(Link): ... - для описания связей между станциями метро.
|
||||
|
||||
Объекты класса Station должны создаваться командой:
|
||||
st = Station(name)
|
||||
где name - название станции (строка). В каждом объекте класса Station должен дополнительно формироваться локальный атрибут:
|
||||
name - название станции метро.
|
||||
|
||||
(Не забудьте в инициализаторе дочернего класса вызывать инициализатор базового класса).
|
||||
В самом классе Station переопределите магические методы __str__() и __repr__(), чтобы они возвращали название станции метро (локальный атрибут name).
|
||||
|
||||
Объекты второго класса LinkMetro должны создаваться командой:
|
||||
link = LinkMetro(v1, v2, dist)
|
||||
где 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))
|
||||
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
|
||||
|
||||
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, 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 = 0
|
||||
name: str
|
||||
|
||||
@staticmethod
|
||||
def column_code(num):
|
||||
def gen(n):
|
||||
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=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):
|
||||
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=None, links=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):
|
||||
if v not in self._vertex:
|
||||
self._vertex.append(v)
|
||||
|
||||
def add_link(self, link):
|
||||
if link not in self._links:
|
||||
self._links.append(link)
|
||||
for v in link:
|
||||
self.add_vertex(v)
|
||||
|
||||
def dijkstras(self, start, stop=None):
|
||||
paths = defaultdict(LinksPath)
|
||||
paths[start].is_start = True
|
||||
remaining, current = set(self.vertex), start
|
||||
while remaining:
|
||||
remaining.discard(current)
|
||||
for link in current:
|
||||
for v in link:
|
||||
if v not in remaining:
|
||||
continue
|
||||
new_path = paths[current].copy().add_link(link)
|
||||
if new_path < paths[v]:
|
||||
paths[v] = new_path
|
||||
if current == stop:
|
||||
break
|
||||
if remaining:
|
||||
current = min(remaining, key=lambda x: paths[x])
|
||||
return paths
|
||||
|
||||
def find_path(self, start_v, stop_v):
|
||||
path = self.dijkstras(start_v, stop_v)[stop_v]
|
||||
return path.vertex, path.links
|
||||
|
||||
|
||||
class Station(Vertex):
|
||||
def __init__(self, name: str):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class LinkMetro(Link):
|
||||
...
|
||||
|
||||
|
||||
vA = Vertex("A")
|
||||
vB = Vertex("B")
|
||||
vC = Vertex("C")
|
||||
vD = Vertex("D")
|
||||
vE = Vertex("E")
|
||||
vF = Vertex("F")
|
||||
vG = Vertex("G")
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
# 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()
|
Loading…
Reference in New Issue
Block a user