123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- """
- Miscellaneous Helpers for NetworkX.
- These are not imported into the base networkx namespace but
- can be accessed, for example, as
- >>> import networkx
- >>> networkx.utils.make_list_of_ints({1, 2, 3})
- [1, 2, 3]
- >>> networkx.utils.arbitrary_element({5, 1, 7}) # doctest: +SKIP
- 1
- """
- import sys
- import uuid
- import warnings
- from collections import defaultdict, deque
- from collections.abc import Iterable, Iterator, Sized
- from itertools import chain, tee
- import networkx as nx
- __all__ = [
- "flatten",
- "make_list_of_ints",
- "dict_to_numpy_array",
- "arbitrary_element",
- "pairwise",
- "groups",
- "create_random_state",
- "create_py_random_state",
- "PythonRandomInterface",
- "nodes_equal",
- "edges_equal",
- "graphs_equal",
- ]
- # some cookbook stuff
- # used in deciding whether something is a bunch of nodes, edges, etc.
- # see G.add_nodes and others in Graph Class in networkx/base.py
- def flatten(obj, result=None):
- """Return flattened version of (possibly nested) iterable object."""
- if not isinstance(obj, (Iterable, Sized)) or isinstance(obj, str):
- return obj
- if result is None:
- result = []
- for item in obj:
- if not isinstance(item, (Iterable, Sized)) or isinstance(item, str):
- result.append(item)
- else:
- flatten(item, result)
- return tuple(result)
- def make_list_of_ints(sequence):
- """Return list of ints from sequence of integral numbers.
- All elements of the sequence must satisfy int(element) == element
- or a ValueError is raised. Sequence is iterated through once.
- If sequence is a list, the non-int values are replaced with ints.
- So, no new list is created
- """
- if not isinstance(sequence, list):
- result = []
- for i in sequence:
- errmsg = f"sequence is not all integers: {i}"
- try:
- ii = int(i)
- except ValueError:
- raise nx.NetworkXError(errmsg) from None
- if ii != i:
- raise nx.NetworkXError(errmsg)
- result.append(ii)
- return result
- # original sequence is a list... in-place conversion to ints
- for indx, i in enumerate(sequence):
- errmsg = f"sequence is not all integers: {i}"
- if isinstance(i, int):
- continue
- try:
- ii = int(i)
- except ValueError:
- raise nx.NetworkXError(errmsg) from None
- if ii != i:
- raise nx.NetworkXError(errmsg)
- sequence[indx] = ii
- return sequence
- def dict_to_numpy_array(d, mapping=None):
- """Convert a dictionary of dictionaries to a numpy array
- with optional mapping."""
- try:
- return _dict_to_numpy_array2(d, mapping)
- except (AttributeError, TypeError):
- # AttributeError is when no mapping was provided and v.keys() fails.
- # TypeError is when a mapping was provided and d[k1][k2] fails.
- return _dict_to_numpy_array1(d, mapping)
- def _dict_to_numpy_array2(d, mapping=None):
- """Convert a dictionary of dictionaries to a 2d numpy array
- with optional mapping.
- """
- import numpy as np
- if mapping is None:
- s = set(d.keys())
- for k, v in d.items():
- s.update(v.keys())
- mapping = dict(zip(s, range(len(s))))
- n = len(mapping)
- a = np.zeros((n, n))
- for k1, i in mapping.items():
- for k2, j in mapping.items():
- try:
- a[i, j] = d[k1][k2]
- except KeyError:
- pass
- return a
- def _dict_to_numpy_array1(d, mapping=None):
- """Convert a dictionary of numbers to a 1d numpy array with optional mapping."""
- import numpy as np
- if mapping is None:
- s = set(d.keys())
- mapping = dict(zip(s, range(len(s))))
- n = len(mapping)
- a = np.zeros(n)
- for k1, i in mapping.items():
- i = mapping[k1]
- a[i] = d[k1]
- return a
- def arbitrary_element(iterable):
- """Returns an arbitrary element of `iterable` without removing it.
- This is most useful for "peeking" at an arbitrary element of a set,
- but can be used for any list, dictionary, etc., as well.
- Parameters
- ----------
- iterable : `abc.collections.Iterable` instance
- Any object that implements ``__iter__``, e.g. set, dict, list, tuple,
- etc.
- Returns
- -------
- The object that results from ``next(iter(iterable))``
- Raises
- ------
- ValueError
- If `iterable` is an iterator (because the current implementation of
- this function would consume an element from the iterator).
- Examples
- --------
- Arbitrary elements from common Iterable objects:
- >>> nx.utils.arbitrary_element([1, 2, 3]) # list
- 1
- >>> nx.utils.arbitrary_element((1, 2, 3)) # tuple
- 1
- >>> nx.utils.arbitrary_element({1, 2, 3}) # set
- 1
- >>> d = {k: v for k, v in zip([1, 2, 3], [3, 2, 1])}
- >>> nx.utils.arbitrary_element(d) # dict_keys
- 1
- >>> nx.utils.arbitrary_element(d.values()) # dict values
- 3
- `str` is also an Iterable:
- >>> nx.utils.arbitrary_element("hello")
- 'h'
- :exc:`ValueError` is raised if `iterable` is an iterator:
- >>> iterator = iter([1, 2, 3]) # Iterator, *not* Iterable
- >>> nx.utils.arbitrary_element(iterator)
- Traceback (most recent call last):
- ...
- ValueError: cannot return an arbitrary item from an iterator
- Notes
- -----
- This function does not return a *random* element. If `iterable` is
- ordered, sequential calls will return the same value::
- >>> l = [1, 2, 3]
- >>> nx.utils.arbitrary_element(l)
- 1
- >>> nx.utils.arbitrary_element(l)
- 1
- """
- if isinstance(iterable, Iterator):
- raise ValueError("cannot return an arbitrary item from an iterator")
- # Another possible implementation is ``for x in iterable: return x``.
- return next(iter(iterable))
- # Recipe from the itertools documentation.
- def pairwise(iterable, cyclic=False):
- "s -> (s0, s1), (s1, s2), (s2, s3), ..."
- a, b = tee(iterable)
- first = next(b, None)
- if cyclic is True:
- return zip(a, chain(b, (first,)))
- return zip(a, b)
- def groups(many_to_one):
- """Converts a many-to-one mapping into a one-to-many mapping.
- `many_to_one` must be a dictionary whose keys and values are all
- :term:`hashable`.
- The return value is a dictionary mapping values from `many_to_one`
- to sets of keys from `many_to_one` that have that value.
- Examples
- --------
- >>> from networkx.utils import groups
- >>> many_to_one = {"a": 1, "b": 1, "c": 2, "d": 3, "e": 3}
- >>> groups(many_to_one) # doctest: +SKIP
- {1: {'a', 'b'}, 2: {'c'}, 3: {'e', 'd'}}
- """
- one_to_many = defaultdict(set)
- for v, k in many_to_one.items():
- one_to_many[k].add(v)
- return dict(one_to_many)
- def create_random_state(random_state=None):
- """Returns a numpy.random.RandomState or numpy.random.Generator instance
- depending on input.
- Parameters
- ----------
- random_state : int or NumPy RandomState or Generator instance, optional (default=None)
- If int, return a numpy.random.RandomState instance set with seed=int.
- if `numpy.random.RandomState` instance, return it.
- if `numpy.random.Generator` instance, return it.
- if None or numpy.random, return the global random number generator used
- by numpy.random.
- """
- import numpy as np
- if random_state is None or random_state is np.random:
- return np.random.mtrand._rand
- if isinstance(random_state, np.random.RandomState):
- return random_state
- if isinstance(random_state, int):
- return np.random.RandomState(random_state)
- if isinstance(random_state, np.random.Generator):
- return random_state
- msg = (
- f"{random_state} cannot be used to create a numpy.random.RandomState or\n"
- "numpy.random.Generator instance"
- )
- raise ValueError(msg)
- class PythonRandomInterface:
- def __init__(self, rng=None):
- try:
- import numpy as np
- except ImportError:
- msg = "numpy not found, only random.random available."
- warnings.warn(msg, ImportWarning)
- if rng is None:
- self._rng = np.random.mtrand._rand
- else:
- self._rng = rng
- def random(self):
- return self._rng.random()
- def uniform(self, a, b):
- return a + (b - a) * self._rng.random()
- def randrange(self, a, b=None):
- import numpy as np
- if isinstance(self._rng, np.random.Generator):
- return self._rng.integers(a, b)
- return self._rng.randint(a, b)
- # NOTE: the numpy implementations of `choice` don't support strings, so
- # this cannot be replaced with self._rng.choice
- def choice(self, seq):
- import numpy as np
- if isinstance(self._rng, np.random.Generator):
- idx = self._rng.integers(0, len(seq))
- else:
- idx = self._rng.randint(0, len(seq))
- return seq[idx]
- def gauss(self, mu, sigma):
- return self._rng.normal(mu, sigma)
- def shuffle(self, seq):
- return self._rng.shuffle(seq)
- # Some methods don't match API for numpy RandomState.
- # Commented out versions are not used by NetworkX
- def sample(self, seq, k):
- return self._rng.choice(list(seq), size=(k,), replace=False)
- def randint(self, a, b):
- import numpy as np
- if isinstance(self._rng, np.random.Generator):
- return self._rng.integers(a, b + 1)
- return self._rng.randint(a, b + 1)
- # exponential as expovariate with 1/argument,
- def expovariate(self, scale):
- return self._rng.exponential(1 / scale)
- # pareto as paretovariate with 1/argument,
- def paretovariate(self, shape):
- return self._rng.pareto(shape)
- # weibull as weibullvariate multiplied by beta,
- # def weibullvariate(self, alpha, beta):
- # return self._rng.weibull(alpha) * beta
- #
- # def triangular(self, low, high, mode):
- # return self._rng.triangular(low, mode, high)
- #
- # def choices(self, seq, weights=None, cum_weights=None, k=1):
- # return self._rng.choice(seq
- def create_py_random_state(random_state=None):
- """Returns a random.Random instance depending on input.
- Parameters
- ----------
- random_state : int or random number generator or None (default=None)
- If int, return a random.Random instance set with seed=int.
- if random.Random instance, return it.
- if None or the `random` package, return the global random number
- generator used by `random`.
- if np.random package, return the global numpy random number
- generator wrapped in a PythonRandomInterface class.
- if np.random.RandomState or np.random.Generator instance, return it
- wrapped in PythonRandomInterface
- if a PythonRandomInterface instance, return it
- """
- import random
- try:
- import numpy as np
- if random_state is np.random:
- return PythonRandomInterface(np.random.mtrand._rand)
- if isinstance(random_state, (np.random.RandomState, np.random.Generator)):
- return PythonRandomInterface(random_state)
- if isinstance(random_state, PythonRandomInterface):
- return random_state
- except ImportError:
- pass
- if random_state is None or random_state is random:
- return random._inst
- if isinstance(random_state, random.Random):
- return random_state
- if isinstance(random_state, int):
- return random.Random(random_state)
- msg = f"{random_state} cannot be used to generate a random.Random instance"
- raise ValueError(msg)
- def nodes_equal(nodes1, nodes2):
- """Check if nodes are equal.
- Equality here means equal as Python objects.
- Node data must match if included.
- The order of nodes is not relevant.
- Parameters
- ----------
- nodes1, nodes2 : iterables of nodes, or (node, datadict) tuples
- Returns
- -------
- bool
- True if nodes are equal, False otherwise.
- """
- nlist1 = list(nodes1)
- nlist2 = list(nodes2)
- try:
- d1 = dict(nlist1)
- d2 = dict(nlist2)
- except (ValueError, TypeError):
- d1 = dict.fromkeys(nlist1)
- d2 = dict.fromkeys(nlist2)
- return d1 == d2
- def edges_equal(edges1, edges2):
- """Check if edges are equal.
- Equality here means equal as Python objects.
- Edge data must match if included.
- The order of the edges is not relevant.
- Parameters
- ----------
- edges1, edges2 : iterables of with u, v nodes as
- edge tuples (u, v), or
- edge tuples with data dicts (u, v, d), or
- edge tuples with keys and data dicts (u, v, k, d)
- Returns
- -------
- bool
- True if edges are equal, False otherwise.
- """
- from collections import defaultdict
- d1 = defaultdict(dict)
- d2 = defaultdict(dict)
- c1 = 0
- for c1, e in enumerate(edges1):
- u, v = e[0], e[1]
- data = [e[2:]]
- if v in d1[u]:
- data = d1[u][v] + data
- d1[u][v] = data
- d1[v][u] = data
- c2 = 0
- for c2, e in enumerate(edges2):
- u, v = e[0], e[1]
- data = [e[2:]]
- if v in d2[u]:
- data = d2[u][v] + data
- d2[u][v] = data
- d2[v][u] = data
- if c1 != c2:
- return False
- # can check one direction because lengths are the same.
- for n, nbrdict in d1.items():
- for nbr, datalist in nbrdict.items():
- if n not in d2:
- return False
- if nbr not in d2[n]:
- return False
- d2datalist = d2[n][nbr]
- for data in datalist:
- if datalist.count(data) != d2datalist.count(data):
- return False
- return True
- def graphs_equal(graph1, graph2):
- """Check if graphs are equal.
- Equality here means equal as Python objects (not isomorphism).
- Node, edge and graph data must match.
- Parameters
- ----------
- graph1, graph2 : graph
- Returns
- -------
- bool
- True if graphs are equal, False otherwise.
- """
- return (
- graph1.adj == graph2.adj
- and graph1.nodes == graph2.nodes
- and graph1.graph == graph2.graph
- )
|