123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- """
- *************************
- Multi-line Adjacency List
- *************************
- Read and write NetworkX graphs as multi-line adjacency lists.
- The multi-line adjacency list format is useful for graphs with
- nodes that can be meaningfully represented as strings. With this format
- simple edge data can be stored but node or graph data is not.
- Format
- ------
- The first label in a line is the source node label followed by the node degree
- d. The next d lines are target node labels and optional edge data.
- That pattern repeats for all nodes in the graph.
- The graph with edges a-b, a-c, d-e can be represented as the following
- adjacency list (anything following the # in a line is a comment)::
- # example.multiline-adjlist
- a 2
- b
- c
- d 1
- e
- """
- __all__ = [
- "generate_multiline_adjlist",
- "write_multiline_adjlist",
- "parse_multiline_adjlist",
- "read_multiline_adjlist",
- ]
- import networkx as nx
- from networkx.utils import open_file
- def generate_multiline_adjlist(G, delimiter=" "):
- """Generate a single line of the graph G in multiline adjacency list format.
- Parameters
- ----------
- G : NetworkX graph
- delimiter : string, optional
- Separator for node labels
- Returns
- -------
- lines : string
- Lines of data in multiline adjlist format.
- Examples
- --------
- >>> G = nx.lollipop_graph(4, 3)
- >>> for line in nx.generate_multiline_adjlist(G):
- ... print(line)
- 0 3
- 1 {}
- 2 {}
- 3 {}
- 1 2
- 2 {}
- 3 {}
- 2 1
- 3 {}
- 3 1
- 4 {}
- 4 1
- 5 {}
- 5 1
- 6 {}
- 6 0
- See Also
- --------
- write_multiline_adjlist, read_multiline_adjlist
- """
- if G.is_directed():
- if G.is_multigraph():
- for s, nbrs in G.adjacency():
- nbr_edges = [
- (u, data)
- for u, datadict in nbrs.items()
- for key, data in datadict.items()
- ]
- deg = len(nbr_edges)
- yield str(s) + delimiter + str(deg)
- for u, d in nbr_edges:
- if d is None:
- yield str(u)
- else:
- yield str(u) + delimiter + str(d)
- else: # directed single edges
- for s, nbrs in G.adjacency():
- deg = len(nbrs)
- yield str(s) + delimiter + str(deg)
- for u, d in nbrs.items():
- if d is None:
- yield str(u)
- else:
- yield str(u) + delimiter + str(d)
- else: # undirected
- if G.is_multigraph():
- seen = set() # helper dict used to avoid duplicate edges
- for s, nbrs in G.adjacency():
- nbr_edges = [
- (u, data)
- for u, datadict in nbrs.items()
- if u not in seen
- for key, data in datadict.items()
- ]
- deg = len(nbr_edges)
- yield str(s) + delimiter + str(deg)
- for u, d in nbr_edges:
- if d is None:
- yield str(u)
- else:
- yield str(u) + delimiter + str(d)
- seen.add(s)
- else: # undirected single edges
- seen = set() # helper dict used to avoid duplicate edges
- for s, nbrs in G.adjacency():
- nbr_edges = [(u, d) for u, d in nbrs.items() if u not in seen]
- deg = len(nbr_edges)
- yield str(s) + delimiter + str(deg)
- for u, d in nbr_edges:
- if d is None:
- yield str(u)
- else:
- yield str(u) + delimiter + str(d)
- seen.add(s)
- @open_file(1, mode="wb")
- def write_multiline_adjlist(G, path, delimiter=" ", comments="#", encoding="utf-8"):
- """Write the graph G in multiline adjacency list format to path
- Parameters
- ----------
- G : NetworkX graph
- path : string or file
- Filename or file handle to write to.
- Filenames ending in .gz or .bz2 will be compressed.
- comments : string, optional
- Marker for comment lines
- delimiter : string, optional
- Separator for node labels
- encoding : string, optional
- Text encoding.
- Examples
- --------
- >>> G = nx.path_graph(4)
- >>> nx.write_multiline_adjlist(G, "test.adjlist")
- The path can be a file handle or a string with the name of the file. If a
- file handle is provided, it has to be opened in 'wb' mode.
- >>> fh = open("test.adjlist", "wb")
- >>> nx.write_multiline_adjlist(G, fh)
- Filenames ending in .gz or .bz2 will be compressed.
- >>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
- See Also
- --------
- read_multiline_adjlist
- """
- import sys
- import time
- pargs = comments + " ".join(sys.argv)
- header = (
- f"{pargs}\n"
- + comments
- + f" GMT {time.asctime(time.gmtime())}\n"
- + comments
- + f" {G.name}\n"
- )
- path.write(header.encode(encoding))
- for multiline in generate_multiline_adjlist(G, delimiter):
- multiline += "\n"
- path.write(multiline.encode(encoding))
- def parse_multiline_adjlist(
- lines, comments="#", delimiter=None, create_using=None, nodetype=None, edgetype=None
- ):
- """Parse lines of a multiline adjacency list representation of a graph.
- Parameters
- ----------
- lines : list or iterator of strings
- Input data in multiline adjlist format
- create_using : NetworkX graph constructor, optional (default=nx.Graph)
- Graph type to create. If graph instance, then cleared before populated.
- nodetype : Python type, optional
- Convert nodes to this type.
- edgetype : Python type, optional
- Convert edges to this type.
- comments : string, optional
- Marker for comment lines
- delimiter : string, optional
- Separator for node labels. The default is whitespace.
- Returns
- -------
- G: NetworkX graph
- The graph corresponding to the lines in multiline adjacency list format.
- Examples
- --------
- >>> lines = [
- ... "1 2",
- ... "2 {'weight':3, 'name': 'Frodo'}",
- ... "3 {}",
- ... "2 1",
- ... "5 {'weight':6, 'name': 'Saruman'}",
- ... ]
- >>> G = nx.parse_multiline_adjlist(iter(lines), nodetype=int)
- >>> list(G)
- [1, 2, 3, 5]
- """
- from ast import literal_eval
- G = nx.empty_graph(0, create_using)
- for line in lines:
- p = line.find(comments)
- if p >= 0:
- line = line[:p]
- if not line:
- continue
- try:
- (u, deg) = line.strip().split(delimiter)
- deg = int(deg)
- except BaseException as err:
- raise TypeError(f"Failed to read node and degree on line ({line})") from err
- if nodetype is not None:
- try:
- u = nodetype(u)
- except BaseException as err:
- raise TypeError(
- f"Failed to convert node ({u}) to " f"type {nodetype}"
- ) from err
- G.add_node(u)
- for i in range(deg):
- while True:
- try:
- line = next(lines)
- except StopIteration as err:
- msg = f"Failed to find neighbor for node ({u})"
- raise TypeError(msg) from err
- p = line.find(comments)
- if p >= 0:
- line = line[:p]
- if line:
- break
- vlist = line.strip().split(delimiter)
- numb = len(vlist)
- if numb < 1:
- continue # isolated node
- v = vlist.pop(0)
- data = "".join(vlist)
- if nodetype is not None:
- try:
- v = nodetype(v)
- except BaseException as err:
- raise TypeError(
- f"Failed to convert node ({v}) " f"to type {nodetype}"
- ) from err
- if edgetype is not None:
- try:
- edgedata = {"weight": edgetype(data)}
- except BaseException as err:
- raise TypeError(
- f"Failed to convert edge data ({data}) " f"to type {edgetype}"
- ) from err
- else:
- try: # try to evaluate
- edgedata = literal_eval(data)
- except:
- edgedata = {}
- G.add_edge(u, v, **edgedata)
- return G
- @open_file(0, mode="rb")
- def read_multiline_adjlist(
- path,
- comments="#",
- delimiter=None,
- create_using=None,
- nodetype=None,
- edgetype=None,
- encoding="utf-8",
- ):
- """Read graph in multi-line adjacency list format from path.
- Parameters
- ----------
- path : string or file
- Filename or file handle to read.
- Filenames ending in .gz or .bz2 will be uncompressed.
- create_using : NetworkX graph constructor, optional (default=nx.Graph)
- Graph type to create. If graph instance, then cleared before populated.
- nodetype : Python type, optional
- Convert nodes to this type.
- edgetype : Python type, optional
- Convert edge data to this type.
- comments : string, optional
- Marker for comment lines
- delimiter : string, optional
- Separator for node labels. The default is whitespace.
- Returns
- -------
- G: NetworkX graph
- Examples
- --------
- >>> G = nx.path_graph(4)
- >>> nx.write_multiline_adjlist(G, "test.adjlist")
- >>> G = nx.read_multiline_adjlist("test.adjlist")
- The path can be a file or a string with the name of the file. If a
- file s provided, it has to be opened in 'rb' mode.
- >>> fh = open("test.adjlist", "rb")
- >>> G = nx.read_multiline_adjlist(fh)
- Filenames ending in .gz or .bz2 will be compressed.
- >>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
- >>> G = nx.read_multiline_adjlist("test.adjlist.gz")
- The optional nodetype is a function to convert node strings to nodetype.
- For example
- >>> G = nx.read_multiline_adjlist("test.adjlist", nodetype=int)
- will attempt to convert all nodes to integer type.
- The optional edgetype is a function to convert edge data strings to
- edgetype.
- >>> G = nx.read_multiline_adjlist("test.adjlist")
- The optional create_using parameter is a NetworkX graph container.
- The default is Graph(), an undirected graph. To read the data as
- a directed graph use
- >>> G = nx.read_multiline_adjlist("test.adjlist", create_using=nx.DiGraph)
- Notes
- -----
- This format does not store graph, node, or edge data.
- See Also
- --------
- write_multiline_adjlist
- """
- lines = (line.decode(encoding) for line in path)
- return parse_multiline_adjlist(
- lines,
- comments=comments,
- delimiter=delimiter,
- create_using=create_using,
- nodetype=nodetype,
- edgetype=edgetype,
- )
|