diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index b5d76a2c287..d87fc9d369d 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -14,7 +14,7 @@ Graph objects and methods sage/graphs/graph sage/graphs/digraph sage/graphs/bipartite_graph - + sage/graphs/views Constructors and databases -------------------------- diff --git a/src/module_list.py b/src/module_list.py index c9196016f76..ee505319d20 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -446,6 +446,9 @@ def uname_specific(name, value, alternative): Extension('sage.graphs.base.boost_graph', sources = ['sage/graphs/base/boost_graph.pyx']), + Extension('sage.graphs.views', + sources = ['sage/graphs/views.pyx']), + ################################ ## ## sage.groups diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver.py b/src/sage/combinat/cluster_algebra_quiver/quiver.py index 6c75ad21c99..b09d574344d 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver.py @@ -43,6 +43,7 @@ from copy import copy from sage.rings.all import ZZ, CC, infinity from sage.graphs.all import Graph, DiGraph +from sage.graphs.views import EdgesView from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType, QuiverMutationType_Irreducible, QuiverMutationType_Reducible, _edge_list_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part, _digraph_mutate, _matrix_to_digraph, _dg_canonical_form, _mutation_class_iter, _digraph_to_dig6, _dig6_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type, _mutation_type_from_data, is_mutation_finite @@ -453,7 +454,7 @@ def __init__(self, data, frozen=None, user_labels=None): # if data is a list of edges, the appropriate digraph is constructed. - elif isinstance(data, list) and all(isinstance(x, (list,tuple)) for x in data): + elif isinstance(data, (list, EdgesView)) and all(isinstance(x, (list,tuple)) for x in data): dg = DiGraph(data) self.__init__(data=dg, frozen=frozen) diff --git a/src/sage/combinat/root_system/type_C.py b/src/sage/combinat/root_system/type_C.py index 60643bd6cc7..b170d9eed2a 100644 --- a/src/sage/combinat/root_system/type_C.py +++ b/src/sage/combinat/root_system/type_C.py @@ -221,7 +221,7 @@ def dynkin_diagram(self): O---O=<=O 1 2 3 C3 - sage: e = c.edges(); e.sort(); e + sage: c.edges(sort=True) [(1, 2, 1), (2, 1, 1), (2, 3, 1), (3, 2, 2)] sage: b = CartanType(['C',1]).dynkin_diagram() diff --git a/src/sage/graphs/comparability.pyx b/src/sage/graphs/comparability.pyx index 0306ea0350c..2d9e41bbefe 100644 --- a/src/sage/graphs/comparability.pyx +++ b/src/sage/graphs/comparability.pyx @@ -707,7 +707,7 @@ def is_permutation(g, algorithm="greedy", certificate=False, check=True, return False, co_certif # Building the two orderings - tmp = co_certif.edges(labels=False, sort=False) + tmp = list(co_certif.edges(labels=False, sort=False)) for u,v in certif.edge_iterator(labels=False): co_certif.add_edge(v,u) certif.add_edges(tmp) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 04a2c29eb37..c458c26cb7f 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2261,7 +2261,7 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): sage: T = G.spqr_tree(algorithm="cleave") sage: Counter(u[0] for u in T) Counter({'R': 1}) - sage: for u,v in G.edges(labels=False, sort=False): + sage: for u,v in list(G.edges(labels=False, sort=False)): ....: G.add_path([u, G.add_vertex(), G.add_vertex(), v]) sage: T = G.spqr_tree(algorithm="Hopcroft_Tarjan") sage: sorted(Counter(u[0] for u in T).items()) @@ -2269,7 +2269,7 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): sage: T = G.spqr_tree(algorithm="cleave") sage: sorted(Counter(u[0] for u in T).items()) [('P', 15), ('R', 1), ('S', 15)] - sage: for u,v in G.edges(labels=False, sort=False): + sage: for u,v in list(G.edges(labels=False, sort=False)): ....: G.add_path([u, G.add_vertex(), G.add_vertex(), v]) sage: T = G.spqr_tree(algorithm="Hopcroft_Tarjan") sage: sorted(Counter(u[0] for u in T).items()) diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 50e27e473d0..ac15e97395d 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -168,6 +168,7 @@ from sage.graphs.dot2tex_utils import have_dot2tex from sage.misc.misc_c import prod from sage.categories.cartesian_product import cartesian_product +from sage.graphs.views import EdgesView class DiGraph(GenericGraph): r""" @@ -668,9 +669,10 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if (format is None and isinstance(data, list) and len(data) == 2 and - isinstance(data[0], list) and # a list of two lists, the second of - isinstance(data[1], list) and # which contains iterables (the edges) - (not data[1] or callable(getattr(data[1][0], "__iter__", None)))): + isinstance(data[0], list) and # a list of two lists, the second of + ((isinstance(data[1], list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0], "__iter__", None)))) or + (isinstance(data[1], EdgesView)))): format = "vertices_and_edges" if format is None and isinstance(data, dict): @@ -706,8 +708,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'int' data = 0 - # Input is a list of edges - if format is None and isinstance(data,list): + # Input is a list of edges or an EdgesView + if format is None and isinstance(data, (list, EdgesView)): format = "list_of_edges" if weighted is None: weighted = False diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index ca6823ac360..72da4c2d4ea 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -71,7 +71,7 @@ :meth:`~GenericGraph.delete_multiedge` | Delete all edges from ``u`` to ``v``. :meth:`~GenericGraph.set_edge_label` | Set the edge label of a given edge. :meth:`~GenericGraph.has_edge` | Check whether ``(u, v)`` is an edge of the (di)graph. - :meth:`~GenericGraph.edges` | Return a list of edges. + :meth:`~GenericGraph.edges` | Return a :class:`~EdgesView` of edges. :meth:`~GenericGraph.edge_boundary` | Return a list of edges ``(u,v,l)`` with ``u`` in ``vertices1`` :meth:`~GenericGraph.edge_iterator` | Return an iterator over edges. :meth:`~GenericGraph.edges_incident` | Return incident edges to some vertices. @@ -425,6 +425,7 @@ from copy import copy +from sage.graphs.views import EdgesView from .generic_graph_pyx import GenericGraph_pyx, spring_layout_fast from .dot2tex_utils import assert_have_dot2tex @@ -4356,7 +4357,7 @@ def weight_function(e): v = next(self.vertex_iterator()) else: v = starting_vertex - sorted_edges = self.edges(key=wfunction_float) + sorted_edges = sorted(self.edges(sort=False), key=wfunction_float) tree = set([v]) edges = [] for _ in range(self.order() - 1): @@ -11483,7 +11484,7 @@ def has_edge(self, u, v=None, label=None): def edges(self, labels=True, sort=True, key=None): r""" - Return a list of edges. + Return a :class:`~EdgesView` of edges. Each edge is a triple ``(u, v, l)`` where ``u`` and ``v`` are vertices and ``l`` is a label. If the parameter ``labels`` is ``False`` then a @@ -11503,7 +11504,7 @@ def edges(self, labels=True, sort=True, key=None): one argument and returns a value that can be used for comparisons in the sorting algorithm - OUTPUT: A list of tuples. It is safe to change the returned list. + OUTPUT: A :class:`~EdgesView`. .. WARNING:: @@ -11562,7 +11563,7 @@ def edges(self, labels=True, sort=True, key=None): sage: P.edges(sort=False, key=lambda x: x) Traceback (most recent call last): ... - ValueError: sort keyword is False, yet a key function is given + ValueError: sort keyword is not True, yet a key function is given sage: G = Graph() sage: G.add_edge(0, 1, [7]) @@ -11570,13 +11571,19 @@ def edges(self, labels=True, sort=True, key=None): sage: G.edge_label(0, 1)[0] += 1 sage: G.edges() [(0, 1, [8]), (0, 2, [7])] + + Deprecation warning for ``sort=None`` (:trac:`27408`):: + + sage: G = graphs.HouseGraph() + sage: G.edges(sort=None) + doctest:...: DeprecationWarning: parameter 'sort' will be set to False by default in the future + See https://trac.sagemath.org/27408 for details. + [(0, 1, None), (0, 2, None), (1, 3, None), (2, 3, None), (2, 4, None), (3, 4, None)] """ - if (not sort) and key: - raise ValueError('sort keyword is False, yet a key function is given') - L = list(self.edge_iterator(labels=labels)) - if sort: - L.sort(key=key) - return L + if sort is None: + deprecation(27408, "parameter 'sort' will be set to False by default in the future") + sort = True + return EdgesView(self, labels=labels, sort=sort, key=key) def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): r""" @@ -16714,9 +16721,9 @@ def weight_function(e): else: # Needed to remove labels. if self.is_directed(): - G = networkx.DiGraph(self.edges(labels=False, sort=False)) + G = networkx.DiGraph(list(self.edges(labels=False, sort=False))) else: - G = networkx.Graph(self.edges(labels=False, sort=False)) + G = networkx.Graph(list(self.edges(labels=False, sort=False))) G.add_nodes_from(self) return networkx.single_source_dijkstra_path(G, u) @@ -16937,9 +16944,9 @@ def weight_function(e): else: # Needed to remove labels. if self.is_directed(): - G = networkx.DiGraph(self.edges(labels=False, sort=False)) + G = networkx.DiGraph(list(self.edges(labels=False, sort=False))) else: - G = networkx.Graph(self.edges(labels=False, sort=False)) + G = networkx.Graph(list(self.edges(labels=False, sort=False))) G.add_nodes_from(self) return networkx.single_source_dijkstra_path_length(G, u) @@ -17840,7 +17847,6 @@ def depth_first_search(self, start, ignore_direction=False, [0, 2, 1] """ - from sage.misc.superseded import deprecation if distance is not None: deprecation(19227, "Parameter 'distance' is broken. Do not use.") diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 5fe317f572d..a42c3869605 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -410,6 +410,7 @@ from __future__ import print_function, absolute_import import six from six.moves import range +import itertools from copy import copy from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -421,6 +422,7 @@ from sage.graphs.digraph import DiGraph from sage.graphs.independent_sets import IndependentSets from sage.misc.rest_index_of_methods import doc_index, gen_thematic_rest_table_index +from sage.graphs.views import EdgesView class Graph(GenericGraph): @@ -1073,9 +1075,10 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if (format is None and isinstance(data, list) and len(data) == 2 and - isinstance(data[0], list) and # a list of two lists, the second of - isinstance(data[1], list) and # which contains iterables (the edges) - (not data[1] or callable(getattr(data[1][0], "__iter__", None)))): + isinstance(data[0], list) and # a list of two lists, the second of + ((isinstance(data[1], list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0], "__iter__", None)))) or + (isinstance(data[1], EdgesView)))): format = "vertices_and_edges" if format is None and isinstance(data, dict): @@ -1083,7 +1086,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'dict_of_dicts' else: val = next(iter(data.values())) - if isinstance(val, list): + if isinstance(val, (list, EdgesView)): format = 'dict_of_lists' elif isinstance(val, dict): format = 'dict_of_dicts' @@ -1111,8 +1114,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'int' data = 0 - # Input is a list of edges - if format is None and isinstance(data, list): + # Input is a list of edges or an EdgesView + if format is None and isinstance(data, (list, EdgesView)): format = "list_of_edges" if weighted is None: weighted = False @@ -1214,9 +1217,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if weighted is None: weighted = False self.allow_loops(loops, check=False) self.allow_multiple_edges(True if multiedges else False, check=False) - from itertools import combinations self.add_vertices(verts) - self.add_edges(e for e in combinations(verts,2) if f(*e)) + self.add_edges(e for e in itertools.combinations(verts,2) if f(*e)) if loops: self.add_edges((v,v) for v in verts if f(v,v)) @@ -2846,8 +2848,8 @@ def treewidth(self, k=None, certificate=False, algorithm=None): return all(cc.treewidth(k) for cc in g.connected_components_subgraphs()) else: T = [cc.treewidth(certificate=True) for cc in g.connected_components_subgraphs()] - tree = Graph([sum([list(t) for t in T], []), - sum([t.edges(labels=False, sort=False) for t in T], [])], + tree = Graph([list(itertools.chain(*T)), + list(itertools.chain(*[t.edges(labels=False, sort=False) for t in T]))], format='vertices_and_edges', name="Tree decomposition") v = next(T[0].vertex_iterator()) for t in T[1:]: @@ -3883,11 +3885,10 @@ def orientations(self, data_structure=None, sparse=None): yield D return - from itertools import product E = [[(u,v,label), (v,u,label)] if u != v else [(u,v,label)] for u,v,label in self.edge_iterator()] verts = self.vertices() - for edges in product(*E): + for edges in itertools.product(*E): D = DiGraph(data=[verts, edges], format='vertices_and_edges', name=name, @@ -5544,10 +5545,9 @@ def seidel_switching(self, s, inplace=True): sage: G == H True """ - from itertools import product G = self if inplace else copy(self) boundary = self.edge_boundary(s) - G.add_edges(product(s, set(self).difference(s))) + G.add_edges(itertools.product(s, set(self).difference(s))) G.delete_edges(boundary) if not inplace: return G @@ -7869,7 +7869,8 @@ def kirchhoff_symanzik_polynomial(self, name='t'): from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - edges = self.edges() + # The order of the vertices in each tuple matters, so use a list + edges = list(self.edges(sort=False)) cycles = self.cycle_basis(output='edge') edge2int = {e: j for j, e in enumerate(edges)} @@ -8028,7 +8029,7 @@ def ihara_zeta_function_inverse(self): from sage.matrix.constructor import matrix H = self.subgraph(vertices=self.cores(k=2)[1]) - E = H.edges() + E = list(H.edges(sort=False)) m = len(E) # compute (Hashimoto) edge matrix T T = matrix(ZZ, 2 * m, 2 * m, 0) diff --git a/src/sage/graphs/graph_decompositions/graph_products.pyx b/src/sage/graphs/graph_decompositions/graph_products.pyx index e4ebe69547e..e567c9b485e 100644 --- a/src/sage/graphs/graph_decompositions/graph_products.pyx +++ b/src/sage/graphs/graph_decompositions/graph_products.pyx @@ -300,7 +300,7 @@ def is_cartesian_product(g, certificate=False, relabeling=False): # Edges uv and u'v' such that d(u,u')+d(v,v') != d(u,v')+d(v,u') are also # equivalent - cdef list edges = g_int.edges(labels=False, sort=False) + cdef list edges = list(g_int.edges(labels=False, sort=False)) cdef dict d = g_int.distance_all_pairs() cdef int uu, vv for i, (u, v) in enumerate(edges): diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index a6c2dafddfa..fd05c5b08d9 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -196,7 +196,7 @@ def _strong_orientations_of_a_mixed_graph(Dg, V, E): sage: from sage.graphs.orientations import _strong_orientations_of_a_mixed_graph sage: g = graphs.CycleGraph(5) sage: Dg = DiGraph(g) # all edges of g will be doubly oriented - sage: it = _strong_orientations_of_a_mixed_graph(Dg, g.vertices(), g.edges(labels=False)) + sage: it = _strong_orientations_of_a_mixed_graph(Dg, list(g), list(g.edges(labels=False, sort=False))) sage: len(list(it)) # there are two orientations of this multigraph 2 """ diff --git a/src/sage/graphs/spanning_tree.pyx b/src/sage/graphs/spanning_tree.pyx index 8095df4dfde..6a824238af6 100644 --- a/src/sage/graphs/spanning_tree.pyx +++ b/src/sage/graphs/spanning_tree.pyx @@ -697,7 +697,7 @@ cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): If the input graph is a tree, then return its edges:: sage: T = graphs.RandomTree(randint(1, 10)) - sage: T.edges() == sorted(boruvka(T, check=True)) + sage: list(T.edges(sort=True)) == sorted(boruvka(T, check=True)) True Check if the weight of MST returned by Prim's and Boruvka's is the same:: diff --git a/src/sage/graphs/tutte_polynomial.py b/src/sage/graphs/tutte_polynomial.py index 9b94881dd04..23ac1ab4d86 100644 --- a/src/sage/graphs/tutte_polynomial.py +++ b/src/sage/graphs/tutte_polynomial.py @@ -448,10 +448,9 @@ def __call__(self, graph): (0, 1, None) """ degrees = dict(graph.degree_iterator(labels=True)) - edges = graph.edges(labels=True) - edges.sort(key=lambda x: degrees[x[0]]+degrees[x[1]]) # Sort by degree - for e in edges: - return e + edges = graph.edges(labels=True, sort=False) + if edges: + return min(edges, key=lambda x: degrees[x[0]] + degrees[x[1]]) raise RuntimeError("no edges left to select") @@ -463,15 +462,15 @@ def __call__(self, graph): sage: from sage.graphs.tutte_polynomial import MaximizeDegree sage: G = graphs.PathGraph(6) sage: MaximizeDegree()(G) - (3, 4, None) + (1, 2, None) """ degrees = dict(graph.degree_iterator(labels=True)) - edges = graph.edges(labels=True) - edges.sort(key=lambda x: degrees[x[0]]+degrees[x[1]]) # Sort by degree - for e in reversed(edges): - return e + edges = graph.edges(labels=True, sort=False) + if edges: + return max(edges, key=lambda x: degrees[x[0]] + degrees[x[1]]) raise RuntimeError("no edges left to select") + ########### # Caching # ########### diff --git a/src/sage/graphs/views.pyx b/src/sage/graphs/views.pyx new file mode 100644 index 00000000000..22e82619b1e --- /dev/null +++ b/src/sage/graphs/views.pyx @@ -0,0 +1,693 @@ +r""" +View classes + +This module implements views for (di)graphs. A view is a read-only iterable +container enabling operations like ``for e in E`` and ``e in E``. It is updated +as the graph is updated. Hence, the graph should not be updated while iterating +through a view. Views can be iterated multiple times. + +.. TODO:: + + - View of neighborhood to get open/close neighborhood of a vertex/set of + vertices, and also the vertex boundary + +Classes +------- +""" +# **************************************************************************** +# Copyright (C) 2019 David Coudert +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from __future__ import print_function, absolute_import + +from itertools import islice +from sys import maxsize as sys_maxsize +from sage.graphs.generic_graph_pyx cimport GenericGraph_pyx + +cdef class EdgesView: + r""" + EdgesView class. + + This class implements a read-only iterable container of edges enabling + operations like ``for e in E`` and ``e in E``. An :class:`EdgesView` can be + iterated multiple times, and checking membership is done in constant + time. It avoids the construction of edge lists and so consumes little + memory. It is updated as the graph is updated. Hence, the graph should not + be updated while iterating through an :class:`EdgesView`. + + INPUT: + + - ``G`` -- a (di)graph + + - ``vertices`` -- list (default: ``None``); an iterable container of + vertices or ``None``. When set, consider only edges incident to specified + vertices. + + - ``labels`` -- boolean (default: ``True``); if ``False``, each edge is + simply a pair ``(u, v)`` of vertices + + - ``ignore_direction`` -- boolean (default: ``False``); only applies to + directed graphs. If ``True``, searches across edges in either direction. + + - ``sort`` -- boolean (default: ``None``); whether to sort edges + + - if ``None``, sort edges according to the default ordering and give a + deprecation warning as sorting will be set to ``False`` by default in + the future + + - if ``True``, edges are sorted according the ordering specified with + parameter ``key`` + + - if ``False``, edges are not sorted. This is the fastest and less memory + consuming method for iterating over edges. This will become the default + behavior in the future. + + - ``key`` -- a function (default: ``None``); a function that takes an edge + (a pair or a triple, according to the ``labels`` keyword) as its one + argument and returns a value that can be used for comparisons in the + sorting algorithm. This parameter is ignored when ``sort = False``. + + .. WARNING:: + + Since any object may be a vertex, there is no guarantee that any two + vertices will be comparable, and thus no guarantee how two edges may + compare. With default objects for vertices (all integers), or when all + the vertices are of the same simple type, then there should not be a + problem with how the vertices will be sorted. However, if you need to + guarantee a total order for the sorting of the edges, use the ``key`` + argument, as illustrated in the examples below. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = Graph([(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')]) + sage: E = EdgesView(G, sort=True); E + [(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')] + sage: (1, 2) in E + False + sage: (1, 2, 'B') in E + True + sage: E = EdgesView(G, labels=False, sort=True); E + [(0, 1), (0, 2), (1, 2)] + sage: (1, 2) in E + True + sage: (1, 2, 'B') in E + False + sage: [e for e in E] + [(0, 1), (0, 2), (1, 2)] + + An :class:`EdgesView` can be iterated multiple times:: + + sage: G = graphs.CycleGraph(3) + sage: print(E) + [(0, 1), (0, 2), (1, 2)] + sage: print(E) + [(0, 1), (0, 2), (1, 2)] + sage: for e in E: + ....: for ee in E: + ....: print((e, ee)) + ((0, 1), (0, 1)) + ((0, 1), (0, 2)) + ((0, 1), (1, 2)) + ((0, 2), (0, 1)) + ((0, 2), (0, 2)) + ((0, 2), (1, 2)) + ((1, 2), (0, 1)) + ((1, 2), (0, 2)) + ((1, 2), (1, 2)) + + We can check if a view is empty:: + + sage: E = EdgesView(graphs.CycleGraph(3), sort=False) + sage: if E: + ....: print('not empty') + not empty + sage: E = EdgesView(Graph(), sort=False) + sage: if not E: + ....: print('empty') + empty + + When ``sort`` is ``True``, edges are sorted by default in the default + fashion:: + + sage: G = Graph([(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')]) + sage: E = EdgesView(G, sort=True); E + [(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')] + + This can be overridden by specifying a key function. This first example just + ignores the labels in the third component of the triple:: + + sage: G = Graph([(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')]) + sage: E = EdgesView(G, sort=True, key=lambda x: (x[1], -x[0])); E + [(0, 1, 'C'), (1, 2, 'B'), (0, 2, 'A')] + + We can also sort according to the labels:: + + sage: G = Graph([(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')]) + sage: E = EdgesView(G, sort=True, key=lambda x: x[2]); E + [(0, 2, 'A'), (1, 2, 'B'), (0, 1, 'C')] + + With a directed graph:: + + sage: G = digraphs.DeBruijn(2, 2) + sage: E = EdgesView(G, labels=False, sort=True); E + [('00', '00'), ('00', '01'), ('01', '10'), ('01', '11'), + ('10', '00'), ('10', '01'), ('11', '10'), ('11', '11')] + sage: E = EdgesView(G, labels=False, sort=True, key=lambda e:(e[1], e[0])); E + [('00', '00'), ('10', '00'), ('00', '01'), ('10', '01'), + ('01', '10'), ('11', '10'), ('01', '11'), ('11', '11')] + + We can consider only edges incident to a specified set of vertices:: + + sage: G = graphs.CycleGraph(5) + sage: E = EdgesView(G, vertices=[0, 1], labels=False, sort=True); E + [(0, 1), (0, 4), (1, 2)] + sage: E = EdgesView(G, vertices=[0], labels=False, sort=True); E + [(0, 1), (0, 4)] + sage: E = EdgesView(G, vertices=None, labels=False, sort=True); E + [(0, 1), (0, 4), (1, 2), (2, 3), (3, 4)] + + sage: G = digraphs.Circuit(5) + sage: E = EdgesView(G, vertices=[0, 1], labels=False, sort=True); E + [(0, 1), (1, 2)] + + We can ignore the direction of the edges of a directed graph, in which case + we search accross edges in either direction:: + + sage: G = digraphs.Circuit(5) + sage: E = EdgesView(G, vertices=[0, 1], labels=False, sort=True, ignore_direction=False); E + [(0, 1), (1, 2)] + sage: (1, 0) in E + False + sage: E = EdgesView(G, vertices=[0, 1], labels=False, sort=True, ignore_direction=True); E + [(0, 1), (0, 1), (1, 2), (4, 0)] + sage: (1, 0) in E + True + sage: G.has_edge(1, 0) + False + + A view is updated as the graph is updated:: + + sage: G = Graph() + sage: E = EdgesView(G, vertices=[0, 3], labels=False, sort=True); E + [] + sage: G.add_edges([(0, 1), (1, 2)]) + sage: E + [(0, 1)] + sage: G.add_edge(2, 3) + sage: E + [(0, 1), (2, 3)] + + Hence, the graph should not be updated while iterating through a view:: + + sage: G = Graph([('a', 'b'), ('b', 'c')]) + sage: E = EdgesView(G, labels=False, sort=False); E + [('a', 'b'), ('b', 'c')] + sage: for u, v in E: + ....: G.add_edge(u + u, v + v) + Traceback (most recent call last): + ... + RuntimeError: dictionary changed size during iteration + + Two :class:`EdgesView` are considered equal if they report either both + directed, or both undirected edges, they have the same settings for + ``ignore_direction``, they have the same settings for ``labels``, and they + report the same edges in the same order:: + + sage: G = graphs.HouseGraph() + sage: EG = EdgesView(G, sort=False) + sage: H = Graph(EG) + sage: EH = EdgesView(H, sort=False) + sage: EG == EH + True + sage: G.add_edge(0, 10) + sage: EG = EdgesView(G, sort=False) + sage: EG == EH + False + sage: H.add_edge(0, 10) + sage: EH = EdgesView(H, sort=False) + sage: EG == EH + True + sage: H = G.strong_orientation() + sage: EH = EdgesView(H, sort=False) + sage: EG == EH + False + + The sum of two :class:`EdgesView` is a list containing the edges in both + :class:`EdgesView`:: + + sage: E1 = EdgesView(Graph([(0, 1)]), labels=False, sort=False) + sage: E2 = EdgesView(Graph([(2, 3)]), labels=False, sort=False) + sage: E1 + E2 + [(0, 1), (2, 3)] + sage: E2 + E1 + [(2, 3), (0, 1)] + + Recall that a :class:`EdgesView` is read-only and that this method + returns a list:: + + sage: E1 += E2 + sage: type(E1) is list + True + + It is also possible to get the sum a :class:`EdgesView` with itself `n` + times:: + + sage: E = EdgesView(Graph([(0, 1), (2, 3)]), labels=False, sort=True) + sage: E * 3 + [(0, 1), (2, 3), (0, 1), (2, 3), (0, 1), (2, 3)] + sage: 3 * E + [(0, 1), (2, 3), (0, 1), (2, 3), (0, 1), (2, 3)] + + Recall that a :class:`EdgesView` is read-only and that this method + returns a list:: + + sage: E *= 2 + sage: type(E) is list + True + + We can ask for the `i`-th edge, or a slice of the edges as a list:: + + sage: E = EdgesView(graphs.HouseGraph(), labels=False, sort=True) + sage: E[0] + (0, 1) + sage: E[2] + (1, 3) + sage: E[-1] + (3, 4) + sage: E[1:-1] + [(0, 2), (1, 3), (2, 3), (2, 4)] + sage: E[::-1] + [(3, 4), (2, 4), (2, 3), (1, 3), (0, 2), (0, 1)] + """ + cdef readonly GenericGraph_pyx _graph + cdef list _vertices + cdef frozenset _vertex_set + cdef readonly bint _labels + cdef readonly bint _ignore_direction + cdef bint _sort_edges + cdef _sort_edges_key + + def __init__(self, G, vertices=None, labels=True, ignore_direction=False, + sort=None, key=None): + """ + Construction of this :class:`EdgesView`. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = Graph([(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')]) + sage: E = EdgesView(G, sort=True); E + [(0, 1, 'C'), (0, 2, 'A'), (1, 2, 'B')] + + TESTS:: + + sage: EdgesView(Graph(), sort=False, key=lambda x:0) + Traceback (most recent call last): + ... + ValueError: sort keyword is not True, yet a key function is given + """ + self._graph = G + + if vertices is None: + self._vertices = None + self._vertex_set = None + else: + self._vertices = list(vertices) + self._vertex_set = frozenset(self._vertices) + + # None and 0 are interpreted as False, 1 as True + self._labels = labels + self._ignore_direction = ignore_direction + self._sort_edges = sort + if not sort and key is not None: + raise ValueError('sort keyword is not True, yet a key function is given') + self._sort_edges_key = key + + def __len__(self): + """ + Return the number of edges in ``self``. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = graphs.HouseGraph() + sage: E = EdgesView(G) + sage: len(E) + 6 + sage: len(E) == G.size() + True + sage: E = EdgesView(G, vertices=[0], labels=False, sort=True); E + [(0, 1), (0, 2)] + sage: len(E) + 2 + sage: G = digraphs.Circuit(4) + sage: len(EdgesView(G, ignore_direction=False, sort=False)) + 4 + sage: len(EdgesView(G, ignore_direction=True, sort=False)) + 8 + """ + if self._vertices is self._graph: + if self._graph._directed and self._ignore_direction: + return 2 * self._graph.size() + return self._graph.size() + else: + return sum(1 for _ in self) + + def __repr__(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = graphs.HouseGraph() + sage: E = EdgesView(G, labels=False) + sage: repr(E) + '[(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]' + sage: E + [(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)] + """ + return "[%s]" % ', '.join(map(repr, self)) + + def _iter_unsorted(self, vertices): + """ + Iterator over the unsorted edges in ``self``. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = graphs.HouseGraph() + sage: E = EdgesView(G, labels=False, sort=False) + sage: list(E) + [(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)] + """ + if self._graph._directed: + yield from self._graph._backend.iterator_out_edges(vertices, self._labels) + if self._ignore_direction: + yield from self._graph._backend.iterator_in_edges(vertices, self._labels) + else: + yield from self._graph._backend.iterator_edges(vertices, self._labels) + + def __iter__(self): + """ + Iterator over the edges in ``self``. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = graphs.HouseGraph() + sage: E = EdgesView(G, labels=False, sort=True) + sage: list(E) + [(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)] + sage: sum(1 for e in E for ee in E) == len(E) * len(E) + True + sage: E = EdgesView(G, labels=False) + sage: sum(1 for e in E for ee in E) == G.size() * G.size() + True + """ + if self._vertices is None: + vertices = self._graph + else: + vertices = self._vertices + if self._sort_edges: + yield from sorted(self._iter_unsorted(vertices), key=self._sort_edges_key) + else: + yield from self._iter_unsorted(vertices) + + def __bool__(self): + """ + Check whether ``self`` is empty. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: E = EdgesView(Graph()) + sage: bool(E) + False + sage: E = EdgesView(graphs.HouseGraph()) + sage: bool(E) + True + """ + if self._vertices is None: + vertices = self._graph + else: + vertices = self._vertices + if self._graph._directed: + for _ in self._graph._backend.iterator_out_edges(vertices, False): + return True + if self._ignore_direction: + for _ in self._graph._backend.iterator_in_edges(vertices, False): + return True + else: + for _ in self._graph._backend.iterator_edges(vertices, False): + return True + return False + + def __eq__(self, right): + """ + Check whether ``self`` and ``right`` are equal. + + Do not call this method directly. That is, for ``E1.__eq__(E2)`` write + ``E1 == E2``. + + Two :class:`EdgesView` are considered equal if the following hold: + - they report either both directed, or both undirected edges; + - they have the same settings for ``ignore_direction``; + - they have the same settings for ``labels``; + - they report the same edges in the same order. + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = graphs.HouseGraph() + sage: EG = EdgesView(G) + sage: H = Graph(EG) + sage: EH = EdgesView(H) + sage: EG == EH + True + sage: G.add_edge(0, 10) + sage: EG = EdgesView(G) + sage: EG == EH + False + sage: H.add_edge(0, 10) + sage: EH = EdgesView(H) + sage: EG == EH + True + sage: H = G.strong_orientation() + sage: EH = EdgesView(H) + sage: EG == EH + False + + TESTS:: + + sage: from sage.graphs.views import EdgesView + sage: G = graphs.HouseGraph() + sage: E = EdgesView(G) + sage: E == E + True + sage: E == G + False + sage: G == E + False + """ + if not isinstance(right, EdgesView): + return NotImplemented + cdef EdgesView other = right + if self is other: + return True + # Check parameters + if (self._graph._directed != other._graph._directed or + self._ignore_direction != other._ignore_direction or + self._labels != other._labels): + return False + # Check that the same edges are reported in the same order + return all(es == eo for es, eo in zip(self, other)) + + def __contains__(self, e): + """ + Check whether edge ``e`` is part of ``self``. + + INPUT: + + - ``e`` -- tuple; when ``self.labels`` is ``True``, the expected form is + ``(u, v, label)``, while when ``self.labels`` is ``False``, the + expected form is ``(u, v)`` + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: G = Graph([(0, 1)]) + sage: E = EdgesView(G, labels=False) + sage: print(E) + [(0, 1)] + sage: (0, 1) in E + True + sage: (0, 1, None) in E + False + sage: E = EdgesView(G, labels=True) + sage: print(E) + [(0, 1, None)] + sage: (0, 1) in E + False + sage: (0, 1, None) in E + True + """ + if self._labels: + try: + u, v, label = e + except Exception: + return False + else: + try: + u, v = e + except Exception: + return False + label = None + if (self._vertex_set is not None + and u not in self._vertex_set and v not in self._vertex_set): + return False + if self._graph._directed and self._ignore_direction: + return (self._graph._backend.has_edge(u, v, label) + or self._graph._backend.has_edge(v, u, label)) + return self._graph._backend.has_edge(u, v, label) + + def __getitem__(self, i): + r""" + Return the `i`-th edge in ``self``. + + This method takes time `O(i)`. When several calls to this method are + done, prefer making ``list`` from ``self`` before querying items. + + INPUT: + + - ``i`` -- integer or slice + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: E = EdgesView(graphs.HouseGraph(), labels=False) + sage: E[0] + (0, 1) + sage: E[2] + (1, 3) + sage: E[1:-1] + [(0, 2), (1, 3), (2, 3), (2, 4)] + sage: E[-1] + (3, 4) + + TESTS:: + + sage: from sage.graphs.views import EdgesView + sage: E = EdgesView(graphs.HouseGraph(), labels=False) + sage: E[10] + Traceback (most recent call last): + ... + IndexError: index out of range + """ + cdef Py_ssize_t start, stop, step + if isinstance(i, slice): + start, stop, step = i.start or 0, i.stop or sys_maxsize, i.step or 1 + if start >= 0 and stop >= 0 and step >= 0: + return list(islice(self, start, stop, step)) + else: + return list(self)[i] + elif i < 0: + return list(self)[i] + else: + try: + return next(islice(self, i, i + 1, 1)) + except StopIteration: + raise IndexError('index out of range') + + def __add__(left, right): + """ + Return a list containing the edges of ``left`` and ``right``. + + The returned list contains the edges of ``left`` with prescribed order + followed by the edges of ``right`` in prescribed order. + + INPUT: + + - ``left,right`` -- :class:`EdgesView` or list of edges + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: E1 = EdgesView(Graph([(0, 1)]), labels=False) + sage: E2 = EdgesView(Graph([(2, 3)]), labels=False) + sage: E1 + E2 + [(0, 1), (2, 3)] + sage: E2 + E1 + [(2, 3), (0, 1)] + sage: E1 + E2 + E1 + [(0, 1), (2, 3), (0, 1)] + + Recall that a :class:`EdgesView` is read-only and that this method + returns a list:: + + sage: E1 += E2 + sage: type(E1) is list + True + + TESTS:: + + sage: from sage.graphs.views import EdgesView + sage: E = EdgesView(graphs.HouseGraph()) + sage: E + 'foo' + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'sage.graphs.views.EdgesView' and 'str' + """ + if not isinstance(left, (list, EdgesView)) or not isinstance(right, (list, EdgesView)): + return NotImplemented + cdef list L = list(left) + L.extend(right) + return L + + def __mul__(left, right): + r""" + Return the sum of ``left`` with itself ``right`` times. + + INPUT: + + - ``right`` -- integer + + EXAMPLES:: + + sage: from sage.graphs.views import EdgesView + sage: E = EdgesView(Graph([(0, 1), (2, 3)]), labels=False, sort=True) + sage: E * 3 + [(0, 1), (2, 3), (0, 1), (2, 3), (0, 1), (2, 3)] + sage: 3 * E + [(0, 1), (2, 3), (0, 1), (2, 3), (0, 1), (2, 3)] + + Recall that a :class:`EdgesView` is read-only and that this method + returns a list:: + + sage: E *= 2 + sage: type(E) is list + True + + TESTS:: + + sage: from sage.graphs.views import EdgesView + sage: E = EdgesView(Graph([(0, 1)])) + sage: E * (-1) + [] + sage: E * 1.5 + Traceback (most recent call last): + ... + TypeError: can't multiply sequence by non-int of type 'sage.rings.real_mpfr.RealLiteral' + """ + if isinstance(left, EdgesView): + return list(left) * right + else: + # Case __rmul__ + return list(right) * left diff --git a/src/sage/matroids/utilities.py b/src/sage/matroids/utilities.py index e3011441325..3b9c45e54f9 100644 --- a/src/sage/matroids/utilities.py +++ b/src/sage/matroids/utilities.py @@ -300,7 +300,7 @@ def make_regular_matroid_from_matroid(matroid): for f in C.difference([e]): A[dB[f], dNB[e]] = 1 # Change some entries from -1 to 1 - entries = BipartiteGraph(A.transpose()).edges(labels=False) + entries = list(BipartiteGraph(A.transpose()).edges(labels=False, sort=False)) while entries: L = [G.shortest_path(u, v) for u, v in entries] mindex, minval = min(enumerate(L), key=lambda x: len(x[1])) @@ -756,15 +756,13 @@ def split_vertex(G, u, v=None, edges=None): sage: from sage.matroids.utilities import split_vertex sage: G = graphs.BullGraph() - sage: split_vertex(G, u = 1, v = 'a', edges = [(1, 3)]) + sage: split_vertex(G, u=1, v=55, edges=[(1, 3)]) Traceback (most recent call last): ... ValueError: the edges are not all incident with u - sage: split_vertex(G, u = 1, v = 'a', edges = [(1, 3, None)]) - sage: G.edges() # py2 - [(0, 1, None), (0, 2, None), (1, 2, None), (2, 4, None), (3, 'a', None)] - sage: [a for a in G.edge_iterator()] # py3 random - [(0, 1, None), (0, 2, None), (1, 2, None), (2, 4, None), ('a', 3, None)] + sage: split_vertex(G, u=1, v=55, edges=[(1, 3, None)]) + sage: list(G.edges(sort=True)) + [(0, 1, None), (0, 2, None), (1, 2, None), (2, 4, None), (3, 55, None)] """ if v is None: v = G.add_vertex()