1"""
2********************
3Bipartite Edge Lists
4********************
5Read and write NetworkX graphs as bipartite edge lists.
6
7Format
8------
9You can read or write three formats of edge lists with these functions.
10
11Node pairs with no data::
12
13 1 2
14
15Python dictionary as data::
16
17 1 2 {'weight':7, 'color':'green'}
18
19Arbitrary data::
20
21 1 2 7 green
22
23For each edge (u, v) the node u is assigned to part 0 and the node v to part 1.
24"""
25
26__all__ = ["generate_edgelist", "write_edgelist", "parse_edgelist", "read_edgelist"]
27
28import networkx as nx
29from networkx.utils import not_implemented_for, open_file
30
31
32@open_file(1, mode="wb")
33def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"):
34 """Write a bipartite graph as a list of edges.
35
36 Parameters
37 ----------
38 G : Graph
39 A NetworkX bipartite graph
40 path : file or string
41 File or filename to write. If a file is provided, it must be
42 opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed.
43 comments : string, optional
44 The character used to indicate the start of a comment
45 delimiter : string, optional
46 The string used to separate values. The default is whitespace.
47 data : bool or list, optional
48 If False write no edge data.
49 If True write a string representation of the edge data dictionary..
50 If a list (or other iterable) is provided, write the keys specified
51 in the list.
52 encoding: string, optional
53 Specify which encoding to use when writing file.
54
55 Examples
56 --------
57 >>> G = nx.path_graph(4)
58 >>> G.add_nodes_from([0, 2], bipartite=0)
59 >>> G.add_nodes_from([1, 3], bipartite=1)
60 >>> nx.write_edgelist(G, "test.edgelist")
61 >>> fh = open("test.edgelist_open", "wb")
62 >>> nx.write_edgelist(G, fh)
63 >>> nx.write_edgelist(G, "test.edgelist.gz")
64 >>> nx.write_edgelist(G, "test.edgelist_nodata.gz", data=False)
65
66 >>> G = nx.Graph()
67 >>> G.add_edge(1, 2, weight=7, color="red")
68 >>> nx.write_edgelist(G, "test.edgelist_bigger_nodata", data=False)
69 >>> nx.write_edgelist(G, "test.edgelist_color", data=["color"])
70 >>> nx.write_edgelist(G, "test.edgelist_color_weight", data=["color", "weight"])
71
72 See Also
73 --------
74 write_edgelist
75 generate_edgelist
76 """
77 for line in generate_edgelist(G, delimiter, data):
78 line += "\n"
79 path.write(line.encode(encoding))
80
81
82@not_implemented_for("directed")
83def generate_edgelist(G, delimiter=" ", data=True):
84 """Generate a single line of the bipartite graph G in edge list format.
85
86 Parameters
87 ----------
88 G : NetworkX graph
89 The graph is assumed to have node attribute `part` set to 0,1 representing
90 the two graph parts
91
92 delimiter : string, optional
93 Separator for node labels
94
95 data : bool or list of keys
96 If False generate no edge data. If True use a dictionary
97 representation of edge data. If a list of keys use a list of data
98 values corresponding to the keys.
99
100 Returns
101 -------
102 lines : string
103 Lines of data in adjlist format.
104
105 Examples
106 --------
107 >>> from networkx.algorithms import bipartite
108 >>> G = nx.path_graph(4)
109 >>> G.add_nodes_from([0, 2], bipartite=0)
110 >>> G.add_nodes_from([1, 3], bipartite=1)
111 >>> G[1][2]["weight"] = 3
112 >>> G[2][3]["capacity"] = 12
113 >>> for line in bipartite.generate_edgelist(G, data=False):
114 ... print(line)
115 0 1
116 2 1
117 2 3
118
119 >>> for line in bipartite.generate_edgelist(G):
120 ... print(line)
121 0 1 {}
122 2 1 {'weight': 3}
123 2 3 {'capacity': 12}
124
125 >>> for line in bipartite.generate_edgelist(G, data=["weight"]):
126 ... print(line)
127 0 1
128 2 1 3
129 2 3
130 """
131 try:
132 part0 = [n for n, d in G.nodes.items() if d["bipartite"] == 0]
133 except BaseException as err:
134 raise AttributeError("Missing node attribute `bipartite`") from err
135 if data is True or data is False:
136 for n in part0:
137 for edge in G.edges(n, data=data):
138 yield delimiter.join(map(str, edge))
139 else:
140 for n in part0:
141 for u, v, d in G.edges(n, data=True):
142 edge = [u, v]
143 try:
144 edge.extend(d[k] for k in data)
145 except KeyError:
146 pass # missing data for this edge, should warn?
147 yield delimiter.join(map(str, edge))
148
149
150@nx._dispatchable(name="bipartite_parse_edgelist", graphs=None, returns_graph=True)
151def parse_edgelist(
152 lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True
153):
154 """Parse lines of an edge list representation of a bipartite graph.
155
156 Parameters
157 ----------
158 lines : list or iterator of strings
159 Input data in edgelist format
160 comments : string, optional
161 Marker for comment lines
162 delimiter : string, optional
163 Separator for node labels
164 create_using: NetworkX graph container, optional
165 Use given NetworkX graph for holding nodes or edges.
166 nodetype : Python type, optional
167 Convert nodes to this type.
168 data : bool or list of (label,type) tuples
169 If False generate no edge data or if True use a dictionary
170 representation of edge data or a list tuples specifying dictionary
171 key names and types for edge data.
172
173 Returns
174 -------
175 G: NetworkX Graph
176 The bipartite graph corresponding to lines
177
178 Examples
179 --------
180 Edgelist with no data:
181
182 >>> from networkx.algorithms import bipartite
183 >>> lines = ["1 2", "2 3", "3 4"]
184 >>> G = bipartite.parse_edgelist(lines, nodetype=int)
185 >>> sorted(G.nodes())
186 [1, 2, 3, 4]
187 >>> sorted(G.nodes(data=True))
188 [(1, {'bipartite': 0}), (2, {'bipartite': 0}), (3, {'bipartite': 0}), (4, {'bipartite': 1})]
189 >>> sorted(G.edges())
190 [(1, 2), (2, 3), (3, 4)]
191
192 Edgelist with data in Python dictionary representation:
193
194 >>> lines = ["1 2 {'weight':3}", "2 3 {'weight':27}", "3 4 {'weight':3.0}"]
195 >>> G = bipartite.parse_edgelist(lines, nodetype=int)
196 >>> sorted(G.nodes())
197 [1, 2, 3, 4]
198 >>> sorted(G.edges(data=True))
199 [(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})]
200
201 Edgelist with data in a list:
202
203 >>> lines = ["1 2 3", "2 3 27", "3 4 3.0"]
204 >>> G = bipartite.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
205 >>> sorted(G.nodes())
206 [1, 2, 3, 4]
207 >>> sorted(G.edges(data=True))
208 [(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})]
209
210 See Also
211 --------
212 """
213 from ast import literal_eval
214
215 G = nx.empty_graph(0, create_using)
216 for line in lines:
217 p = line.find(comments)
218 if p >= 0:
219 line = line[:p]
220 if not len(line):
221 continue
222 # split line, should have 2 or more
223 s = line.rstrip("\n").split(delimiter)
224 if len(s) < 2:
225 continue
226 u = s.pop(0)
227 v = s.pop(0)
228 d = s
229 if nodetype is not None:
230 try:
231 u = nodetype(u)
232 v = nodetype(v)
233 except BaseException as err:
234 raise TypeError(
235 f"Failed to convert nodes {u},{v} to type {nodetype}."
236 ) from err
237
238 if len(d) == 0 or data is False:
239 # no data or data type specified
240 edgedata = {}
241 elif data is True:
242 # no edge types specified
243 try: # try to evaluate as dictionary
244 edgedata = dict(literal_eval(" ".join(d)))
245 except BaseException as err:
246 raise TypeError(
247 f"Failed to convert edge data ({d}) to dictionary."
248 ) from err
249 else:
250 # convert edge data to dictionary with specified keys and type
251 if len(d) != len(data):
252 raise IndexError(
253 f"Edge data {d} and data_keys {data} are not the same length"
254 )
255 edgedata = {}
256 for (edge_key, edge_type), edge_value in zip(data, d):
257 try:
258 edge_value = edge_type(edge_value)
259 except BaseException as err:
260 raise TypeError(
261 f"Failed to convert {edge_key} data "
262 f"{edge_value} to type {edge_type}."
263 ) from err
264 edgedata.update({edge_key: edge_value})
265 G.add_node(u, bipartite=0)
266 G.add_node(v, bipartite=1)
267 G.add_edge(u, v, **edgedata)
268 return G
269
270
271@open_file(0, mode="rb")
272@nx._dispatchable(name="bipartite_read_edgelist", graphs=None, returns_graph=True)
273def read_edgelist(
274 path,
275 comments="#",
276 delimiter=None,
277 create_using=None,
278 nodetype=None,
279 data=True,
280 edgetype=None,
281 encoding="utf-8",
282):
283 """Read a bipartite graph from a list of edges.
284
285 Parameters
286 ----------
287 path : file or string
288 File or filename to read. If a file is provided, it must be
289 opened in 'rb' mode.
290 Filenames ending in .gz or .bz2 will be decompressed.
291 comments : string, optional
292 The character used to indicate the start of a comment.
293 delimiter : string, optional
294 The string used to separate values. The default is whitespace.
295 create_using : Graph container, optional,
296 Use specified container to build graph. The default is networkx.Graph,
297 an undirected graph.
298 nodetype : int, float, str, Python type, optional
299 Convert node data from strings to specified type
300 data : bool or list of (label,type) tuples
301 Tuples specifying dictionary key names and types for edge data
302 edgetype : int, float, str, Python type, optional OBSOLETE
303 Convert edge data from strings to specified type and use as 'weight'
304 encoding: string, optional
305 Specify which encoding to use when reading file.
306
307 Returns
308 -------
309 G : graph
310 A networkx Graph or other type specified with create_using
311
312 Examples
313 --------
314 >>> from networkx.algorithms import bipartite
315 >>> G = nx.path_graph(4)
316 >>> G.add_nodes_from([0, 2], bipartite=0)
317 >>> G.add_nodes_from([1, 3], bipartite=1)
318 >>> bipartite.write_edgelist(G, "test.edgelist")
319 >>> G = bipartite.read_edgelist("test.edgelist")
320
321 >>> fh = open("test.edgelist", "rb")
322 >>> G = bipartite.read_edgelist(fh)
323 >>> fh.close()
324
325 >>> G = bipartite.read_edgelist("test.edgelist", nodetype=int)
326
327 Edgelist with data in a list:
328
329 >>> textline = "1 2 3"
330 >>> fh = open("test.edgelist", "w")
331 >>> d = fh.write(textline)
332 >>> fh.close()
333 >>> G = bipartite.read_edgelist(
334 ... "test.edgelist", nodetype=int, data=(("weight", float),)
335 ... )
336 >>> list(G)
337 [1, 2]
338 >>> list(G.edges(data=True))
339 [(1, 2, {'weight': 3.0})]
340
341 See parse_edgelist() for more examples of formatting.
342
343 See Also
344 --------
345 parse_edgelist
346
347 Notes
348 -----
349 Since nodes must be hashable, the function nodetype must return hashable
350 types (e.g. int, float, str, frozenset - or tuples of those, etc.)
351 """
352 lines = (line.decode(encoding) for line in path)
353 return parse_edgelist(
354 lines,
355 comments=comments,
356 delimiter=delimiter,
357 create_using=create_using,
358 nodetype=nodetype,
359 data=data,
360 )