fr.boreal.model.partition.Partition Maven / Gradle / Ivy
The newest version!
package fr.boreal.model.partition;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Guillaume Pérution-Kihli
*
* This class represents the partition of a set which contains elements of type E
* The data structure used to manage the partition is union-find, which is optimal
* in terms of algorithmic complexity for this problem
*
* @param the type of elements of the partition
*
*/
public class Partition {
private final Map nodes;
private final Set representatives;
private Integer hashCode = null;
private Comparator comparator = null;
/**
* Creates a new empty Partition
*/
public Partition () {
nodes = new HashMap<>();
representatives = new HashSet<>();
}
/**
* Creates a new empty Partition with the given comparator
* @param comparator the comparator to choose the representative of a class
*/
public Partition(Comparator comparator) {
this.comparator = comparator;
nodes = new HashMap<>();
representatives = new HashSet<>();
}
/**
* Creates a new Partition with the given elements, each in its own class
* @param initialElements the initial elements of this partition
*/
public Partition (Set initialElements) {
this(initialElements, null);
}
/**
* Creates a new Partition with the given elements and comparator
* @param initialElements the initial elements of this partition
* @param comparator the comparator to choose the representative of a class
*/
public Partition (Set initialElements, Comparator comparator) {
this(comparator);
initialElements.forEach(this::addNode);
}
/**
* Creates a new Partition with the given classes
* @param partition the initial elements represented as multiple partitions
*/
public Partition (Collection> partition) {
this(partition, null);
}
/**
* Creates a new Partition with the given classes and comparator
* @param partition the initial elements represented as multiple partitions
* @param comparator the comparator to choose the representative of a class
*/
public Partition (Collection> partition, Comparator comparator) {
this(comparator);
partition.forEach(this::addClass);
}
/**
* Creates a new Partition copying the given one
* @param toCopy partition to copy
*/
public Partition (Partition toCopy) {
this(toCopy.comparator);
this.join(toCopy);
}
/**
* Creates a new Partition copying the given one but overriding its comparator
* @param toCopy partition to copy
* @param comparator the comparator to choose the representative of a class
*/
public Partition (Partition toCopy, Comparator comparator) {
this(comparator);
this.join(toCopy);
}
////////////////////
// PUBLIC METHODS //
////////////////////
/**
* Add a class to the partition
* If there are some common elements with existing classes,
* the class will be merged with these existing classes
* @param c : the class to add
*/
public void addClass (Set c) {
eraseMemoizedValues();
if (!c.isEmpty()) {
var it = c.iterator();
Node root = getNode(it.next());
while (it.hasNext()) {
union(getNode(it.next()), root);
}
}
}
/**
* Returns a class' representative of x
* If x is not already in the partition, it is added in its own class
* The returned representative is always the same if the partition
* is not modified
* @param x : the element from which we want the class' representative
* @return a class' representative of x
*/
public E getRepresentative (E x) {
return find(getNode(x)).value;
}
/**
* Returns an immutable Set containing all the class'
* elements of x
* @param x : the element from which we want the class
* @return an iterator on the class' elements of x
*/
public Set getClass (E x) {
return new ClassView(x);
}
/**
* Merge the classes of x and y
* If x or y are not yet in the partition, they are be added to it
* @param x : the first element of the couple
* @param y : the second element of the couple
*/
public void union (E x, E y) {
eraseMemoizedValues();
union(getNode(x), getNode(y));
}
/**
* Join the class of another partition
* This operation consists of merge the classes of this partition and the
* other one when they share a common element
* @param other : the partition we want to join
*/
public void join (Partition other) {
eraseMemoizedValues();
for (E e : other.nodes.keySet()) {
union(e, other.getRepresentative(e));
}
}
/**
* Return all the classes of the partition
* @return a list of immutable sets that is the list of all classes
*/
public List> getClasses () {
return getRepresentatives().stream()
.map(r -> new ClassView(r))
.collect(Collectors.toList());
}
/**
* Return all the elements contained in the partition
* @return an immutable set containing all the elements of the partition
*/
public Set getElements () {
return Collections.unmodifiableSet(this.nodes.keySet());
}
@Override
public synchronized int hashCode () {
if (hashCode == null) {
hashCode = getClasses().stream()
/* We multiply the hash code of each class by its
* size in order that two different partitions with
* the same elements does not always have the same
* hash codes (it avoids some collisions)
*/
.map(c -> c.hashCode() * c.size())
.reduce(0, Integer::sum);
}
return hashCode;
}
@Override
public boolean equals (Object o) {
if (o == this)
return true;
if (!(o instanceof Partition> other))
return false;
if (nodes.size() != other.nodes.size())
return false;
if (!nodes.keySet().equals(other.nodes.keySet()))
return false;
List extends Set>> otherClasses = other.getClasses();
return new HashSet<>(otherClasses).containsAll(this.getClasses());
}
@Override
public String toString () {
return getClasses().toString();
}
/////////////////////
// PRIVATE METHODS //
/////////////////////
private Set getRepresentatives () {
return representatives.stream().map(n -> n.value).collect(Collectors.toSet());
}
private void addNode (E x) {
if (!nodes.containsKey(x)) {
nodes.put(x, new Node(x));
}
}
private Node getNode(E x) {
Node node = nodes.get(x);
if (node == null) {
addNode(x);
node = nodes.get(x);
}
return node;
}
private void union (Node x, Node y) {
link(find(x), find(y));
}
private void link (Node x, Node y) {
if (x != y) {
if (x.size > y.size) {
this.order(x, y);
y.parent = x;
representatives.remove(y);
x.children.add(y);
x.size += y.size;
} else {
this.order(y, x);
x.parent = y;
representatives.remove(x);
y.children.add(x);
y.size += x.size;
}
}
}
private void order(Node x, Node y) {
if(this.comparator != null && this.comparator.compare(x.value, y.value) > 0) {
E aux = x.value;
x.value = y.value;
y.value = aux;
}
}
private Node find (Node x) {
if (x.parent != x.parent.parent) {
x.parent.children.remove(x);
var old_parent = x.parent;
x.parent = find(x.parent);
old_parent.size -= x.size;
x.parent.children.add(x);
}
return x.parent;
}
/**
* Erase the memoized values
*/
protected void eraseMemoizedValues () {
hashCode = null;
}
class Node {
int size;
Node parent;
final Set children;
E value;
Node (E value) {
this.size = 1;
this.parent = this;
children = new HashSet<>();
this.value = value;
representatives.add(this);
}
@Override
public String toString() {
return "(size : "+size+", parent: "+parent.value+", children: "
+(children.stream().map(c -> c.value).toList())+", value: "+value+")";
}
}
class ClassIterator implements Iterator {
final Queue queue;
ClassIterator (E x) {
this.queue = new LinkedList<>();
queue.add(find(getNode(x)));
}
@Override
public boolean hasNext() {
return !queue.isEmpty();
}
@Override
public E next() {
Node n = queue.poll();
queue.addAll(n.children);
return n.value;
}
}
class ClassView extends AbstractSet {
final E x;
ClassView (E x) {
this.x = x;
}
@Override
public boolean contains(Object o) {
if (!nodes.containsKey(o)) {
return false;
}
return find(getNode(x)).equals(find(nodes.get(o)));
}
@Override
public Iterator iterator() {
return new ClassIterator(x);
}
@Override
public int size() {
return find(getNode(x)).size;
}
}
}