mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-06 00:54:32 +08:00
Refactor LWWElementSet (#6164)
This commit is contained in:
@ -1,53 +1,33 @@
|
||||
package com.thealgorithms.datastructures.crdt;
|
||||
|
||||
import java.time.Instant;
|
||||
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.
|
||||
* 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. The LWWElementSet ensures that the most recent operation (based on the timestamp)
|
||||
* wins in the case of concurrent operations.
|
||||
*
|
||||
* @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>
|
||||
* @param <T> The type of the elements in the LWWElementSet.
|
||||
* @author <a href="https://github.com/itakurah">itakurah (GitHub)</a>, <a
|
||||
* href="https://www.linkedin.com/in/niklashoefflin/">Niklas Hoefflin (LinkedIn)</a>
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">Conflict free
|
||||
* replicated data type (Wikipedia)</a>
|
||||
* @see <a href="https://inria.hal.science/inria-00555588v1/document">A comprehensive study of
|
||||
* Convergent and Commutative Replicated Data Types</a>
|
||||
*/
|
||||
|
||||
class Element {
|
||||
String key;
|
||||
int timestamp;
|
||||
Bias bias;
|
||||
class LWWElementSet<T> {
|
||||
final Map<T, Element<T>> addSet;
|
||||
final Map<T, Element<T>> removeSet;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
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.
|
||||
* Constructs an empty LWWElementSet. This constructor initializes the addSet and removeSet as
|
||||
* empty HashMaps. The addSet stores elements that are added, and the removeSet stores elements
|
||||
* that are removed.
|
||||
*/
|
||||
LWWElementSet() {
|
||||
this.addSet = new HashMap<>();
|
||||
@ -55,84 +35,92 @@ class LWWElementSet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to the addSet.
|
||||
* Adds an element to the addSet with the current timestamp. This method stores the element in the
|
||||
* addSet, ensuring that the element is added to the set with an associated timestamp that
|
||||
* represents the time of the addition.
|
||||
*
|
||||
* @param e The element to be added.
|
||||
* @param key The key of the element to be added.
|
||||
*/
|
||||
public void add(Element e) {
|
||||
addSet.put(e.key, e);
|
||||
public void add(T key) {
|
||||
addSet.put(key, new Element<>(key, Instant.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an element from the removeSet.
|
||||
* Removes an element by adding it to the removeSet with the current timestamp. This method adds
|
||||
* the element to the removeSet, marking it as removed with the current timestamp.
|
||||
*
|
||||
* @param e The element to be removed.
|
||||
* @param key The key of the element to be removed.
|
||||
*/
|
||||
public void remove(Element e) {
|
||||
if (lookup(e)) {
|
||||
removeSet.put(e.key, e);
|
||||
public void remove(T key) {
|
||||
removeSet.put(key, new Element<>(key, Instant.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element is in the LWWElementSet. An element is considered present if it exists in
|
||||
* the addSet and either does not exist in the removeSet, or its add timestamp is later than any
|
||||
* corresponding remove timestamp.
|
||||
*
|
||||
* @param key The key of the element to be checked.
|
||||
* @return {@code true} if the element is present in the set (i.e., its add timestamp is later
|
||||
* than its remove timestamp, or it is not in the remove set), {@code false} otherwise (i.e.,
|
||||
* the element has been removed or its remove timestamp is later than its add timestamp).
|
||||
*/
|
||||
public boolean lookup(T key) {
|
||||
Element<T> inAddSet = addSet.get(key);
|
||||
Element<T> inRemoveSet = removeSet.get(key);
|
||||
|
||||
return inAddSet != null && (inRemoveSet == null || inAddSet.timestamp.isAfter(inRemoveSet.timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges another LWWElementSet into this set. This method takes the union of both the add-sets
|
||||
* and remove-sets from the two sets, resolving conflicts by keeping the element with the latest
|
||||
* timestamp. If an element appears in both the add-set and remove-set of both sets, the one with
|
||||
* the later timestamp will be retained.
|
||||
*
|
||||
* @param other The LWWElementSet to merge with the current set.
|
||||
*/
|
||||
public void merge(LWWElementSet<T> other) {
|
||||
for (Map.Entry<T, Element<T>> entry : other.addSet.entrySet()) {
|
||||
addSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
|
||||
}
|
||||
for (Map.Entry<T, Element<T>> entry : other.removeSet.entrySet()) {
|
||||
removeSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet.
|
||||
* Resolves conflicts between two elements by selecting the one with the later timestamp. This
|
||||
* method is used when merging two LWWElementSets to ensure that the most recent operation (based
|
||||
* on timestamps) is kept.
|
||||
*
|
||||
* @param e The element to be checked.
|
||||
* @return True if the element is present, false otherwise.
|
||||
* @param e1 The first element.
|
||||
* @param e2 The second element.
|
||||
* @return The element with the later timestamp.
|
||||
*/
|
||||
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 != 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 != Bias.ADDS;
|
||||
}
|
||||
return timestampComparison < 0;
|
||||
private Element<T> resolveConflict(Element<T> e1, Element<T> e2) {
|
||||
return e1.timestamp.isAfter(e2.timestamp) ? e1 : e2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an element in the LWWElementSet, consisting of a key and a timestamp. This class is
|
||||
* used to store the elements in both the add and remove sets with their respective timestamps.
|
||||
*
|
||||
* @param <T> The type of the key associated with the element.
|
||||
*/
|
||||
class Element<T> {
|
||||
T key;
|
||||
Instant timestamp;
|
||||
|
||||
/**
|
||||
* Constructs a new Element with the specified key and timestamp.
|
||||
*
|
||||
* @param key The key of the element.
|
||||
* @param timestamp The timestamp associated with the element.
|
||||
*/
|
||||
Element(T key, Instant timestamp) {
|
||||
this.key = key;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user