Skip to content

Commit c0b1683

Browse files
author
Release Manager
committed
gh-35903: Add parameter key to methods multiple_edges and edge_boundary Part of #35902. ### 📚 Description We add parameter `key` to methods `multiple_edges` and `edge_boundary`. This is useful when users manipulate graphs with edges of incomparable types. On the way, we avoid some calls to `multiple_edges` with `sort=True` in method `is_tree`. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. It should be `[x]` not `[x ]`. --> - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - #12345: short description why this is a dependency - #34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: #35903 Reported by: David Coudert Reviewer(s): Dima Pasechnik
2 parents d5a3fd1 + ee2084f commit c0b1683

File tree

2 files changed

+90
-14
lines changed

2 files changed

+90
-14
lines changed

src/sage/graphs/generic_graph.py

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3515,7 +3515,7 @@ def allow_multiple_edges(self, new, check=True, keep_label='any'):
35153515

35163516
self._backend.multiple_edges(new)
35173517

3518-
def multiple_edges(self, to_undirected=False, labels=True, sort=False):
3518+
def multiple_edges(self, to_undirected=False, labels=True, sort=False, key=None):
35193519
"""
35203520
Return any multiple edges in the (di)graph.
35213521

@@ -3525,7 +3525,11 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False):
35253525

35263526
- ``labels`` -- boolean (default: ``True``); whether to include labels
35273527

3528-
- ``sort`` - boolean (default: ``False``); whether to sort the result
3528+
- ``sort`` -- boolean (default: ``False``); whether to sort the result
3529+
3530+
- ``key`` -- a function (default: ``None``); a function that takes an
3531+
edge as its one argument and returns a value that can be used for
3532+
comparisons in the sorting algorithm (we must have ``sort=True``)
35293533

35303534
EXAMPLES::
35313535

@@ -3574,7 +3578,36 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False):
35743578
[]
35753579
sage: G.multiple_edges(to_undirected=True, sort=True)
35763580
[(1, 2, 'h'), (2, 1, 'g')]
3581+
3582+
Using the ``key`` argument to order multiple edges of incomparable
3583+
types (see :trac:`35903`)::
3584+
3585+
sage: G = Graph([('A', 'B', 3), (1, 2, 1), ('A', 'B', 4), (1, 2, 2)], multiedges=True)
3586+
sage: G.multiple_edges(sort=True)
3587+
Traceback (most recent call last):
3588+
...
3589+
TypeError: unsupported operand parent(s) for <: 'Integer Ring' and '<class 'str'>'
3590+
sage: G.multiple_edges(labels=False, sort=True, key=str)
3591+
[('A', 'B'), ('A', 'B'), (1, 2), (1, 2)]
3592+
sage: G.multiple_edges(sort=True, key=str)
3593+
[('A', 'B', 3), ('A', 'B', 4), (1, 2, 1), (1, 2, 2)]
3594+
sage: G.multiple_edges(labels=True, sort=True, key=lambda e:e[2])
3595+
[(1, 2, 1), (1, 2, 2), ('A', 'B', 3), ('A', 'B', 4)]
3596+
sage: G.multiple_edges(labels=False, sort=True, key=lambda e:e[2])
3597+
Traceback (most recent call last):
3598+
...
3599+
IndexError: tuple index out of range
3600+
3601+
TESTS::
3602+
3603+
sage: Graph().multiple_edges(sort=False, key=str)
3604+
Traceback (most recent call last):
3605+
...
3606+
ValueError: sort keyword is False, yet a key function is given
35773607
"""
3608+
if (not sort) and key:
3609+
raise ValueError('sort keyword is False, yet a key function is given')
3610+
35783611
multi_edges = []
35793612
seen = set()
35803613

@@ -3647,7 +3680,7 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False):
36473680
multi_edges.extend((u, v) for _ in L)
36483681

36493682
if sort:
3650-
multi_edges.sort()
3683+
return sorted(multi_edges, key=key)
36513684
return multi_edges
36523685

36533686
def name(self, new=None):
@@ -5092,7 +5125,7 @@ def cycle_basis(self, output='vertex'):
50925125
sage: G.cycle_basis() # needs networkx
50935126
[[0, 2], [2, 1, 0]]
50945127
sage: G.cycle_basis(output='edge') # needs networkx
5095-
[[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]]
5128+
[[(0, 2, 'b'), (2, 0, 'a')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]]
50965129
sage: H = Graph([(1, 2), (2, 3), (2, 3), (3, 4), (1, 4),
50975130
....: (1, 4), (4, 5), (5, 6), (4, 6), (6, 7)], multiedges=True)
50985131
sage: H.cycle_basis() # needs networkx
@@ -5134,10 +5167,9 @@ def cycle_basis(self, output='vertex'):
51345167
sage: G.cycle_basis() # needs networkx
51355168
[[2, 3], [4, 3, 2, 1], [4, 3, 2, 1]]
51365169
sage: G.cycle_basis(output='edge') # needs networkx
5137-
[[(2, 3, 'b'), (3, 2, 'c')],
5170+
[[(2, 3, 'c'), (3, 2, 'b')],
51385171
[(4, 3, 'd'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')],
51395172
[(4, 3, 'e'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')]]
5140-
51415173
"""
51425174
if output not in ['vertex', 'edge']:
51435175
raise ValueError('output must be either vertex or edge')
@@ -12568,7 +12600,7 @@ def edges(self, vertices=None, labels=True, sort=None, key=None,
1256812600
return EdgesView(self, vertices=vertices, labels=labels, sort=sort, key=key,
1256912601
ignore_direction=ignore_direction, sort_vertices=sort_vertices)
1257012602

12571-
def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
12603+
def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False, key=None):
1257212604
r"""
1257312605
Return a list of edges ``(u,v,l)`` with ``u`` in ``vertices1``
1257412606
and ``v`` in ``vertices2``.
@@ -12586,6 +12618,10 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
1258612618

1258712619
- ``sort`` -- boolean (default: ``False``); whether to sort the result
1258812620

12621+
- ``key`` -- a function (default: ``None``); a function that takes an
12622+
edge as its one argument and returns a value that can be used for
12623+
comparisons in the sorting algorithm (we must have ``sort=True``)
12624+
1258912625
EXAMPLES::
1259012626

1259112627
sage: K = graphs.CompleteBipartiteGraph(9, 3)
@@ -12610,6 +12646,23 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
1261012646
sage: D.edge_boundary([0], labels=False, sort=True)
1261112647
[(0, 1), (0, 2)]
1261212648

12649+
Using the ``key`` argument to order multiple edges of incomparable
12650+
types (see :trac:`35903`)::
12651+
12652+
sage: G = Graph([(1, 'A', 4), (1, 2, 3)])
12653+
sage: G.edge_boundary([1], sort=True)
12654+
Traceback (most recent call last):
12655+
...
12656+
TypeError: unsupported operand parent(s) for <: 'Integer Ring' and '<class 'str'>'
12657+
sage: G.edge_boundary([1], sort=True, key=str)
12658+
[('A', 1, 4), (1, 2, 3)]
12659+
sage: G.edge_boundary([1], sort=True, key=lambda e:e[2])
12660+
[(1, 2, 3), ('A', 1, 4)]
12661+
sage: G.edge_boundary([1], labels=False, sort=True, key=lambda e:e[2])
12662+
Traceback (most recent call last):
12663+
...
12664+
IndexError: tuple index out of range
12665+
1261312666
TESTS::
1261412667

1261512668
sage: G = graphs.DiamondGraph()
@@ -12619,7 +12672,14 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
1261912672
[]
1262012673
sage: G.edge_boundary([2], [0])
1262112674
[(0, 2, None)]
12675+
sage: G.edge_boundary([2], [0], sort=False, key=str)
12676+
Traceback (most recent call last):
12677+
...
12678+
ValueError: sort keyword is False, yet a key function is given
1262212679
"""
12680+
if (not sort) and key:
12681+
raise ValueError('sort keyword is False, yet a key function is given')
12682+
1262312683
vertices1 = set(v for v in vertices1 if v in self)
1262412684
if self._directed:
1262512685
if vertices2 is not None:
@@ -12639,7 +12699,7 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
1263912699
output = [e for e in self.edges(vertices=vertices1, labels=labels, sort=False)
1264012700
if e[1] not in vertices1 or e[0] not in vertices1]
1264112701
if sort:
12642-
output.sort()
12702+
return sorted(output, key=key)
1264312703
return output
1264412704

1264512705
def edge_iterator(self, vertices=None, labels=True, ignore_direction=False, sort_vertices=True):

src/sage/graphs/graph.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,7 +1524,7 @@ def is_tree(self, certificate=False, output='vertex'):
15241524
sage: G.is_tree(certificate=True)
15251525
(False, [1, 2])
15261526
sage: G.is_tree(certificate=True, output='edge')
1527-
(False, [(1, 2, 'a'), (2, 1, 'b')])
1527+
(False, [(1, 2, 'b'), (2, 1, 'a')])
15281528
15291529
TESTS:
15301530
@@ -1552,6 +1552,16 @@ def is_tree(self, certificate=False, output='vertex'):
15521552
(False, [0])
15531553
sage: G.is_tree(certificate=True, output='edge')
15541554
(False, [(0, 0, None)])
1555+
1556+
Case of edges with incomparable types (see :trac:`35903`)::
1557+
1558+
sage: G = Graph(multiedges=True)
1559+
sage: G.add_cycle(['A', 1, 2, 3])
1560+
sage: G.add_cycle(['A', 1, 2, 3])
1561+
sage: G.is_tree(certificate=True, output='vertex')
1562+
(False, ['A', 1])
1563+
sage: G.is_tree(certificate=True, output='edge')
1564+
(False, [('A', 1, None), (1, 'A', None)])
15551565
"""
15561566
if output not in ['vertex', 'edge']:
15571567
raise ValueError('output must be either vertex or edge')
@@ -1569,12 +1579,18 @@ def is_tree(self, certificate=False, output='vertex'):
15691579
return False, L[:1]
15701580

15711581
if self.has_multiple_edges():
1582+
multiple_edges = self.multiple_edges(sort=False)
15721583
if output == 'vertex':
1573-
return (False, list(self.multiple_edges(sort=True)[0][:2]))
1574-
edge1, edge2 = self.multiple_edges(sort=True)[:2]
1575-
if edge1[0] != edge2[0]:
1576-
return (False, [edge1, edge2])
1577-
return (False, [edge1, (edge2[1], edge2[0], edge2[2])])
1584+
return (False, list(multiple_edges[0][:2]))
1585+
# Search for 2 edges between u and v.
1586+
# We do this way to handle the case of edges with incomparable
1587+
# types
1588+
u1, v1, w1 = multiple_edges[0]
1589+
for u2, v2, w2 in multiple_edges[1:]:
1590+
if u1 == u2 and v1 == v2:
1591+
return (False, [(u1, v1, w1), (v2, u2, w2)])
1592+
elif u1 == v2 and v1 == u2:
1593+
return (False, [(u1, v1, w1), (u2, v2, w2)])
15781594

15791595
if output == 'edge':
15801596
if self.allows_multiple_edges():

0 commit comments

Comments
 (0)