123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- """
- Union-find data structure.
- """
- from networkx.utils import groups
- class UnionFind:
- """Union-find data structure.
- Each unionFind instance X maintains a family of disjoint sets of
- hashable objects, supporting the following two methods:
- - X[item] returns a name for the set containing the given item.
- Each set is named by an arbitrarily-chosen one of its members; as
- long as the set remains unchanged it will keep the same name. If
- the item is not yet part of a set in X, a new singleton set is
- created for it.
- - X.union(item1, item2, ...) merges the sets containing each item
- into a single larger set. If any item is not yet part of a set
- in X, it is added to X as one of the members of the merged set.
- Union-find data structure. Based on Josiah Carlson's code,
- https://code.activestate.com/recipes/215912/
- with significant additional changes by D. Eppstein.
- http://www.ics.uci.edu/~eppstein/PADS/UnionFind.py
- """
- def __init__(self, elements=None):
- """Create a new empty union-find structure.
- If *elements* is an iterable, this structure will be initialized
- with the discrete partition on the given set of elements.
- """
- if elements is None:
- elements = ()
- self.parents = {}
- self.weights = {}
- for x in elements:
- self.weights[x] = 1
- self.parents[x] = x
- def __getitem__(self, object):
- """Find and return the name of the set containing the object."""
- # check for previously unknown object
- if object not in self.parents:
- self.parents[object] = object
- self.weights[object] = 1
- return object
- # find path of objects leading to the root
- path = []
- root = self.parents[object]
- while root != object:
- path.append(object)
- object = root
- root = self.parents[object]
- # compress the path and return
- for ancestor in path:
- self.parents[ancestor] = root
- return root
- def __iter__(self):
- """Iterate through all items ever found or unioned by this structure."""
- return iter(self.parents)
- def to_sets(self):
- """Iterates over the sets stored in this structure.
- For example::
- >>> partition = UnionFind("xyz")
- >>> sorted(map(sorted, partition.to_sets()))
- [['x'], ['y'], ['z']]
- >>> partition.union("x", "y")
- >>> sorted(map(sorted, partition.to_sets()))
- [['x', 'y'], ['z']]
- """
- # Ensure fully pruned paths
- for x in self.parents:
- _ = self[x] # Evaluated for side-effect only
- yield from groups(self.parents).values()
- def union(self, *objects):
- """Find the sets containing the objects and merge them all."""
- # Find the heaviest root according to its weight.
- roots = iter(
- sorted(
- {self[x] for x in objects}, key=lambda r: self.weights[r], reverse=True
- )
- )
- try:
- root = next(roots)
- except StopIteration:
- return
- for r in roots:
- self.weights[root] += self.weights[r]
- self.parents[r] = root
|