diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 0c2dc0a79df..47a00b9c3f0 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -3515,7 +3515,7 @@ def allow_multiple_edges(self, new, check=True, keep_label='any'): self._backend.multiple_edges(new) - def multiple_edges(self, to_undirected=False, labels=True, sort=False): + def multiple_edges(self, to_undirected=False, labels=True, sort=False, key=None): """ Return any multiple edges in the (di)graph. @@ -3525,7 +3525,11 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False): - ``labels`` -- boolean (default: ``True``); whether to include labels - - ``sort`` - boolean (default: ``False``); whether to sort the result + - ``sort`` -- boolean (default: ``False``); whether to sort the result + + - ``key`` -- a function (default: ``None``); a function that takes an + edge as its one argument and returns a value that can be used for + comparisons in the sorting algorithm (we must have ``sort=True``) EXAMPLES:: @@ -3574,7 +3578,36 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False): [] sage: G.multiple_edges(to_undirected=True, sort=True) [(1, 2, 'h'), (2, 1, 'g')] + + Using the ``key`` argument to order multiple edges of incomparable + types (see :trac:`35903`):: + + sage: G = Graph([('A', 'B', 3), (1, 2, 1), ('A', 'B', 4), (1, 2, 2)], multiedges=True) + sage: G.multiple_edges(sort=True) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for <: 'Integer Ring' and '' + sage: G.multiple_edges(labels=False, sort=True, key=str) + [('A', 'B'), ('A', 'B'), (1, 2), (1, 2)] + sage: G.multiple_edges(sort=True, key=str) + [('A', 'B', 3), ('A', 'B', 4), (1, 2, 1), (1, 2, 2)] + sage: G.multiple_edges(labels=True, sort=True, key=lambda e:e[2]) + [(1, 2, 1), (1, 2, 2), ('A', 'B', 3), ('A', 'B', 4)] + sage: G.multiple_edges(labels=False, sort=True, key=lambda e:e[2]) + Traceback (most recent call last): + ... + IndexError: tuple index out of range + + TESTS:: + + sage: Graph().multiple_edges(sort=False, key=str) + Traceback (most recent call last): + ... + ValueError: sort keyword is False, yet a key function is given """ + if (not sort) and key: + raise ValueError('sort keyword is False, yet a key function is given') + multi_edges = [] seen = set() @@ -3647,7 +3680,7 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False): multi_edges.extend((u, v) for _ in L) if sort: - multi_edges.sort() + return sorted(multi_edges, key=key) return multi_edges def name(self, new=None): @@ -5092,7 +5125,7 @@ def cycle_basis(self, output='vertex'): sage: G.cycle_basis() # needs networkx [[0, 2], [2, 1, 0]] sage: G.cycle_basis(output='edge') # needs networkx - [[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]] + [[(0, 2, 'b'), (2, 0, 'a')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]] sage: H = Graph([(1, 2), (2, 3), (2, 3), (3, 4), (1, 4), ....: (1, 4), (4, 5), (5, 6), (4, 6), (6, 7)], multiedges=True) sage: H.cycle_basis() # needs networkx @@ -5134,10 +5167,9 @@ def cycle_basis(self, output='vertex'): sage: G.cycle_basis() # needs networkx [[2, 3], [4, 3, 2, 1], [4, 3, 2, 1]] sage: G.cycle_basis(output='edge') # needs networkx - [[(2, 3, 'b'), (3, 2, 'c')], + [[(2, 3, 'c'), (3, 2, 'b')], [(4, 3, 'd'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')], [(4, 3, 'e'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')]] - """ if output not in ['vertex', 'edge']: raise ValueError('output must be either vertex or edge') @@ -12568,7 +12600,7 @@ def edges(self, vertices=None, labels=True, sort=None, key=None, return EdgesView(self, vertices=vertices, labels=labels, sort=sort, key=key, ignore_direction=ignore_direction, sort_vertices=sort_vertices) - def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): + def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False, key=None): r""" Return a list of edges ``(u,v,l)`` with ``u`` in ``vertices1`` and ``v`` in ``vertices2``. @@ -12586,6 +12618,10 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): - ``sort`` -- boolean (default: ``False``); whether to sort the result + - ``key`` -- a function (default: ``None``); a function that takes an + edge as its one argument and returns a value that can be used for + comparisons in the sorting algorithm (we must have ``sort=True``) + EXAMPLES:: sage: K = graphs.CompleteBipartiteGraph(9, 3) @@ -12610,6 +12646,23 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): sage: D.edge_boundary([0], labels=False, sort=True) [(0, 1), (0, 2)] + Using the ``key`` argument to order multiple edges of incomparable + types (see :trac:`35903`):: + + sage: G = Graph([(1, 'A', 4), (1, 2, 3)]) + sage: G.edge_boundary([1], sort=True) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for <: 'Integer Ring' and '' + sage: G.edge_boundary([1], sort=True, key=str) + [('A', 1, 4), (1, 2, 3)] + sage: G.edge_boundary([1], sort=True, key=lambda e:e[2]) + [(1, 2, 3), ('A', 1, 4)] + sage: G.edge_boundary([1], labels=False, sort=True, key=lambda e:e[2]) + Traceback (most recent call last): + ... + IndexError: tuple index out of range + TESTS:: sage: G = graphs.DiamondGraph() @@ -12619,7 +12672,14 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): [] sage: G.edge_boundary([2], [0]) [(0, 2, None)] + sage: G.edge_boundary([2], [0], sort=False, key=str) + Traceback (most recent call last): + ... + ValueError: sort keyword is False, yet a key function is given """ + if (not sort) and key: + raise ValueError('sort keyword is False, yet a key function is given') + vertices1 = set(v for v in vertices1 if v in self) if self._directed: if vertices2 is not None: @@ -12639,7 +12699,7 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): output = [e for e in self.edges(vertices=vertices1, labels=labels, sort=False) if e[1] not in vertices1 or e[0] not in vertices1] if sort: - output.sort() + return sorted(output, key=key) return output def edge_iterator(self, vertices=None, labels=True, ignore_direction=False, sort_vertices=True): diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 30a63bff8a7..548965c9e09 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1524,7 +1524,7 @@ def is_tree(self, certificate=False, output='vertex'): sage: G.is_tree(certificate=True) (False, [1, 2]) sage: G.is_tree(certificate=True, output='edge') - (False, [(1, 2, 'a'), (2, 1, 'b')]) + (False, [(1, 2, 'b'), (2, 1, 'a')]) TESTS: @@ -1552,6 +1552,16 @@ def is_tree(self, certificate=False, output='vertex'): (False, [0]) sage: G.is_tree(certificate=True, output='edge') (False, [(0, 0, None)]) + + Case of edges with incomparable types (see :trac:`35903`):: + + sage: G = Graph(multiedges=True) + sage: G.add_cycle(['A', 1, 2, 3]) + sage: G.add_cycle(['A', 1, 2, 3]) + sage: G.is_tree(certificate=True, output='vertex') + (False, ['A', 1]) + sage: G.is_tree(certificate=True, output='edge') + (False, [('A', 1, None), (1, 'A', None)]) """ if output not in ['vertex', 'edge']: raise ValueError('output must be either vertex or edge') @@ -1569,12 +1579,18 @@ def is_tree(self, certificate=False, output='vertex'): return False, L[:1] if self.has_multiple_edges(): + multiple_edges = self.multiple_edges(sort=False) if output == 'vertex': - return (False, list(self.multiple_edges(sort=True)[0][:2])) - edge1, edge2 = self.multiple_edges(sort=True)[:2] - if edge1[0] != edge2[0]: - return (False, [edge1, edge2]) - return (False, [edge1, (edge2[1], edge2[0], edge2[2])]) + return (False, list(multiple_edges[0][:2])) + # Search for 2 edges between u and v. + # We do this way to handle the case of edges with incomparable + # types + u1, v1, w1 = multiple_edges[0] + for u2, v2, w2 in multiple_edges[1:]: + if u1 == u2 and v1 == v2: + return (False, [(u1, v1, w1), (v2, u2, w2)]) + elif u1 == v2 and v1 == u2: + return (False, [(u1, v1, w1), (u2, v2, w2)]) if output == 'edge': if self.allows_multiple_edges():