Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 42 additions & 35 deletions src/pgraph/PGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,15 +323,38 @@ def __str__(self) -> str:
>>> g.add_edge(v2, v3)
>>> str(g)

:seealso: :meth:`show`
:seealso: :meth:`__repr__` :meth:`show`
"""
s = f"{self.__class__.__name__}: {self.n:,} {'vertex' if self.n==1 else 'vertices'}, {self.ne:,} edge{'s'[:self.ne^1]}, {self.nc:,} component{'s'[:self.nc^1]}"
return s

def __repr__(self) -> str:
# NOTE: this is shadowed by the __repr__ defined further below in
# this class, which is the one actually used -- kept as-is here to
# avoid changing behaviour as part of a typing-only pass.
"""
Representation of the graph, the same one-line summary as :meth:`__str__`

:return: one-line summary of vertex/edge/component counts
:rtype: str

The representation of a graph does not list its vertices, since that
would be as long as the graph, and a ``repr`` is what appears in a
notebook cell, a debugger, a log or an error message. Use
:meth:`show` to list them.

.. runblock:: pycon

>>> from pgraph import UGraph
>>> g = UGraph()
>>> v1 = g.add_vertex(coord=[0,0], name='v1')
>>> v2 = g.add_vertex(coord=[1,1], name='v2')
>>> g.add_edge(v1, v2)
>>> repr(g)

.. versionchanged:: 1.1
Was one line per vertex, with its name, coordinate and component;
that is now printed by :meth:`show`.

:seealso: :meth:`__str__` :meth:`show`
"""
return str(self)

@classmethod
Expand Down Expand Up @@ -1501,7 +1524,11 @@ def remove(self, x: Edge | Vertex) -> None:

def show(self) -> None:
"""
Print a summary of all vertices and edges to stdout
Print a listing of all vertices and edges to stdout

Each vertex is shown with its name, coordinate and component number,
and each edge by its endpoints, cost and data. This is as long as the
graph, unlike :meth:`__str__` and :meth:`__repr__`, which are one line.

.. runblock:: pycon

Expand All @@ -1512,11 +1539,18 @@ def show(self) -> None:
>>> g.add_edge(v1, v2)
>>> g.show()

:seealso: :meth:`__str__`
.. versionchanged:: 1.1
Each vertex is shown with its coordinate and component number,
which were what ``repr(g)`` listed before it became one line.

:seealso: :meth:`__str__` :meth:`plot`
"""
print("vertices:")
for v in self._vertexlist:
print(" " + str(v))
line = f" {v.name} at {v.coord}"
if v.label is not None:
line += f" component={v.label}"
print(line)
print("edges:")
for e in self._edges:
print(" " + str(e))
Expand Down Expand Up @@ -1808,33 +1842,6 @@ def heuristic(self, heuristic: Callable[[NDArray], float] | str) -> None:
"""
self._heuristic = self._metricfunc(heuristic)

def __repr__(self) -> str: # type: ignore[no-redef]
"""
Detailed representation of the graph, one line per vertex

:return: one line per vertex showing its name, coordinate and component
:rtype: str

.. runblock:: pycon

>>> from pgraph import UGraph
>>> g = UGraph()
>>> v1 = g.add_vertex(coord=[0,0], name='v1')
>>> v2 = g.add_vertex(coord=[1,1], name='v2')
>>> v3 = g.add_vertex(coord=[2,2], name='v3')
>>> g.add_edge(v1, v2)
>>> g.add_edge(v2, v3)
>>> repr(g)

"""
s = [f"{self.__class__.__name__}:"]
for vertex in self:
ss = f" {vertex.name} at {vertex.coord}"
if vertex.label is not None:
ss += f" component={vertex.label}"
s.append(ss)
return "\n".join(s)

def __getitem__(self, i: int | str | Vertex) -> Vertex:
"""
Get vertex (base class method)
Expand Down Expand Up @@ -2528,7 +2535,7 @@ def plot(
... g.add_edge(a, b);
>>> g.plot(block=None)

:seealso: :meth:`highlight_path` :meth:`highlight_edge`
:seealso: :meth:`highlight_path` :meth:`highlight_edge` :meth:`show`
"""
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
Expand Down
52 changes: 48 additions & 4 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import contextlib
import io
import unittest
import numpy as np
import numpy.testing as nt
Expand Down Expand Up @@ -45,11 +47,53 @@ def test_str(self):
self.assertEqual(str(g), "DGraph: 2 vertices, 1 edge, 1 component")


# the repr is the one-line summary, not a listing of the vertices
self.assertEqual(repr(g), "DGraph: 2 vertices, 1 edge, 1 component")

def test_repr_is_one_line_however_big_the_graph(self):
g = UGraph()
for i in range(500):
g.add_vertex([i, i], name=f"v{i}")
s = repr(g)
self.assertIsInstance(s, str)
lines = s.split('\n')
self.assertEqual(len(lines), 3) # class-name header + one line per vertex
self.assertEqual(lines[0], "DGraph:")
self.assertNotIn("\n", s)
self.assertEqual(s, str(g))
self.assertEqual(s, "UGraph: 500 vertices, 0 edges, 500 components")

def test_show_lists_vertices_and_edges(self):
g = UGraph()
a = g.add_vertex([1, 2], name="a")
b = g.add_vertex([3, 4], name="b")
g.add_vertex([5, 6], name="c")
g.add_edge(a, b, cost=2.5)

out = io.StringIO()
with contextlib.redirect_stdout(out):
g.show()
lines = out.getvalue().splitlines()

# a vertex line has the name, the coordinate and the component number,
# which are what the repr used to list; c is a component of its own
self.assertEqual(
lines,
[
"vertices:",
" a at [1 2] component=0",
" b at [3 4] component=0",
" c at [5 6] component=1",
"edges:",
" Edge{[a] -- [b], cost=2.5}",
],
)

def test_show_a_vertex_without_a_coordinate(self):
g = UGraph()
g.add_vertex(name="x")

out = io.StringIO()
with contextlib.redirect_stdout(out):
g.show()

self.assertIn(" x at None", out.getvalue())


def test_str_has_thousands_separators(self):
Expand Down
Loading