package com.thealgorithms.datastructures.crdt;
import java.util.HashMap;
import java.util.Map;
/**
* Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type)
* designed for managing sets in a distributed and concurrent environment. It supports the addition and removal
* of elements, using timestamps to determine the order of operations. The set is split into two subsets:
* the add set for elements to be added and the remove set for elements to be removed.
*
* @author itakurah (Niklas Hoefflin) (https://github.com/itakurah)
* @see <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">Conflict-free_replicated_data_type</a>
* @see <a href="https://github.com/itakurah">itakurah (Niklas Hoefflin)</a>
*/
class Element {
String key;
int timestamp;
Bias bias;
/**
* Constructs a new Element with the specified key, timestamp and bias.
*
* @param key The key of the element.
* @param timestamp The timestamp associated with the element.
* @param bias The bias of the element (ADDS or REMOVALS).
*/
public Element(String key, int timestamp, Bias bias) {
this.key = key;
this.timestamp = timestamp;
this.bias = bias;
}
}
enum Bias {
/**
* ADDS bias for the add set.
* REMOVALS bias for the remove set.
*/
ADDS,
REMOVALS
}
class LWWElementSet {
private final Map<String, Element> addSet;
private final Map<String, Element> removeSet;
/**
* Constructs an empty LWWElementSet.
*/
public LWWElementSet() {
this.addSet = new HashMap<>();
this.removeSet = new HashMap<>();
}
/**
* Adds an element to the addSet.
*
* @param e The element to be added.
*/
public void add(Element e) {
addSet.put(e.key, e);
}
/**
* Removes an element from the removeSet.
*
* @param e The element to be removed.
*/
public void remove(Element e) {
if (lookup(e)) {
removeSet.put(e.key, e);
}
}
/**
* Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet.
*
* @param e The element to be checked.
* @return True if the element is present, false otherwise.
*/
public boolean lookup(Element e) {
Element inAddSet = addSet.get(e.key);
Element inRemoveSet = removeSet.get(e.key);
return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp));
}
/**
* Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset.
*
* @param other The LWWElementSet to compare.
* @return True if the set is subset, false otherwise.
*/
public boolean compare(LWWElementSet other) {
return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet());
}
/**
* Merges another LWWElementSet into this set by resolving conflicts based on timestamps.
*
* @param other The LWWElementSet to merge.
*/
public void merge(LWWElementSet other) {
for (Element e : other.addSet.values()) {
if (!addSet.containsKey(e.key) || compareTimestamps(addSet.get(e.key), e)) {
addSet.put(e.key, e);
}
}
for (Element e : other.removeSet.values()) {
if (!removeSet.containsKey(e.key) || compareTimestamps(removeSet.get(e.key), e)) {
removeSet.put(e.key, e);
}
}
}
/**
* Compares timestamps of two elements based on their bias (ADDS or REMOVALS).
*
* @param e The first element.
* @param other The second element.
* @return True if the first element's timestamp is greater or the bias is ADDS and timestamps are equal.
*/
public boolean compareTimestamps(Element e, Element other) {
if (!e.bias.equals(other.bias)) {
throw new IllegalArgumentException("Invalid bias value");
}
Bias bias = e.bias;
int timestampComparison = Integer.compare(e.timestamp, other.timestamp);
if (timestampComparison == 0) {
return !bias.equals(Bias.ADDS);
}
return timestampComparison < 0;
}
}