Add OR-Set (Observed-Remove Set) (#4980)
This commit is contained in:
parent
4aa8e6a0eb
commit
e26fd9da71
@ -86,6 +86,7 @@
|
||||
* [GCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java)
|
||||
* [GSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/GSet.java)
|
||||
* [LWWElementSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java)
|
||||
* [ORSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java)
|
||||
* [PNCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java)
|
||||
* [TwoPSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java)
|
||||
* disjointsetunion
|
||||
@ -623,6 +624,7 @@
|
||||
* [GCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java)
|
||||
* [GSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GSetTest.java)
|
||||
* [LWWElementSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java)
|
||||
* [ORSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java)
|
||||
* [PNCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/PNCounterTest.java)
|
||||
* [TwoPSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java)
|
||||
* disjointsetunion
|
||||
|
191
src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java
Normal file
191
src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java
Normal file
@ -0,0 +1,191 @@
|
||||
package com.thealgorithms.datastructures.crdt;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* ORSet (Observed-Removed Set) is a state-based CRDT (Conflict-free Replicated Data Type)
|
||||
* that supports both addition and removal of elements. This particular implementation follows
|
||||
* the Add-Wins strategy, meaning that in case of conflicting add and remove operations,
|
||||
* the add operation takes precedence. The merge operation of two OR-Sets ensures that
|
||||
* elements added at any replica are eventually observed at all replicas. Removed elements,
|
||||
* once observed, are never reintroduced.
|
||||
* This OR-Set implementation provides methods for adding elements, removing elements,
|
||||
* checking for element existence, retrieving the set of elements, comparing with other OR-Sets,
|
||||
* and merging with another OR-Set to create a new OR-Set containing all unique elements
|
||||
* from both sets.
|
||||
*
|
||||
* @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>
|
||||
*/
|
||||
|
||||
public class ORSet<T> {
|
||||
|
||||
private final Set<Pair<T>> elements;
|
||||
private final Set<Pair<T>> tombstones;
|
||||
|
||||
/**
|
||||
* Constructs an empty OR-Set.
|
||||
*/
|
||||
public ORSet() {
|
||||
this.elements = new HashSet<>();
|
||||
this.tombstones = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the set contains the specified element.
|
||||
*
|
||||
* @param element the element to check for
|
||||
* @return true if the set contains the element, false otherwise
|
||||
*/
|
||||
public boolean contains(T element) {
|
||||
return elements.stream().anyMatch(pair -> pair.getElement().equals(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the elements in the set.
|
||||
*
|
||||
* @return a set containing the elements
|
||||
*/
|
||||
public Set<T> elements() {
|
||||
Set<T> result = new HashSet<>();
|
||||
elements.forEach(pair -> result.add(pair.getElement()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the set.
|
||||
*
|
||||
* @param element the element to add
|
||||
*/
|
||||
public void add(T element) {
|
||||
String n = prepare();
|
||||
effect(element, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified element from the set.
|
||||
*
|
||||
* @param element the element to remove
|
||||
*/
|
||||
public void remove(T element) {
|
||||
Set<Pair<T>> pairsToRemove = prepare(element);
|
||||
effect(pairsToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all pairs with the specified element.
|
||||
*
|
||||
* @param element the element to collect pairs for
|
||||
* @return a set of pairs with the specified element to be removed
|
||||
*/
|
||||
private Set<Pair<T>> prepare(T element) {
|
||||
Set<Pair<T>> pairsToRemove = new HashSet<>();
|
||||
for (Pair<T> pair : elements) {
|
||||
if (pair.getElement().equals(element)) {
|
||||
pairsToRemove.add(pair);
|
||||
}
|
||||
}
|
||||
return pairsToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique tag for the element.
|
||||
*
|
||||
* @return the unique tag
|
||||
*/
|
||||
private String prepare() {
|
||||
return generateUniqueTag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the element with the specified unique tag to the set.
|
||||
*
|
||||
* @param element the element to add
|
||||
* @param n the unique tag associated with the element
|
||||
*/
|
||||
private void effect(T element, String n) {
|
||||
Pair<T> pair = new Pair<>(element, n);
|
||||
elements.add(pair);
|
||||
elements.removeAll(tombstones);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified pairs from the set.
|
||||
*
|
||||
* @param pairsToRemove the pairs to remove
|
||||
*/
|
||||
private void effect(Set<Pair<T>> pairsToRemove) {
|
||||
elements.removeAll(pairsToRemove);
|
||||
tombstones.addAll(pairsToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique tag.
|
||||
*
|
||||
* @return the unique tag
|
||||
*/
|
||||
private String generateUniqueTag() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this Add-Wins OR-Set with another OR-Set to check if elements and tombstones are a subset.
|
||||
*
|
||||
* @param other the other OR-Set to compare
|
||||
* @return true if the sets are subset, false otherwise
|
||||
*/
|
||||
public boolean compare(ORSet<T> other) {
|
||||
Set<Pair<T>> union = new HashSet<>(elements);
|
||||
union.addAll(tombstones);
|
||||
|
||||
Set<Pair<T>> otherUnion = new HashSet<>(other.elements);
|
||||
otherUnion.addAll(other.tombstones);
|
||||
|
||||
return otherUnion.containsAll(union) && other.tombstones.containsAll(tombstones);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges this Add-Wins OR-Set with another OR-Set.
|
||||
*
|
||||
* @param other the other OR-Set to merge
|
||||
*/
|
||||
public void merge(ORSet<T> other) {
|
||||
elements.removeAll(other.tombstones);
|
||||
other.elements.removeAll(tombstones);
|
||||
elements.addAll(other.elements);
|
||||
tombstones.addAll(other.tombstones);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a pair containing an element and a unique tag.
|
||||
*
|
||||
* @param <T> the type of the element in the pair
|
||||
*/
|
||||
public static class Pair<T> {
|
||||
private final T element;
|
||||
private final String uniqueTag;
|
||||
|
||||
/**
|
||||
* Constructs a pair with the specified element and unique tag.
|
||||
*
|
||||
* @param element the element in the pair
|
||||
* @param uniqueTag the unique tag associated with the element
|
||||
*/
|
||||
public Pair(T element, String uniqueTag) {
|
||||
this.element = element;
|
||||
this.uniqueTag = uniqueTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the element from the pair.
|
||||
*
|
||||
* @return the element
|
||||
*/
|
||||
public T getElement() {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.thealgorithms.datastructures.crdt;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ORSetTest {
|
||||
|
||||
@Test
|
||||
void testContains() {
|
||||
ORSet<String> orSet = new ORSet<>();
|
||||
orSet.add("A");
|
||||
assertTrue(orSet.contains("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdd() {
|
||||
ORSet<String> orSet = new ORSet<>();
|
||||
orSet.add("A");
|
||||
assertTrue(orSet.contains("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemove() {
|
||||
ORSet<String> orSet = new ORSet<>();
|
||||
orSet.add("A");
|
||||
orSet.add("A");
|
||||
orSet.remove("A");
|
||||
assertFalse(orSet.contains("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testElements() {
|
||||
ORSet<String> orSet = new ORSet<>();
|
||||
orSet.add("A");
|
||||
orSet.add("B");
|
||||
assertEquals(Set.of("A", "B"), orSet.elements());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompareEqualSets() {
|
||||
ORSet<String> orSet1 = new ORSet<>();
|
||||
ORSet<String> orSet2 = new ORSet<>();
|
||||
|
||||
orSet1.add("A");
|
||||
orSet2.add("A");
|
||||
orSet2.add("B");
|
||||
orSet2.add("C");
|
||||
orSet2.remove("C");
|
||||
orSet1.merge(orSet2);
|
||||
orSet2.merge(orSet1);
|
||||
orSet2.remove("B");
|
||||
|
||||
assertTrue(orSet1.compare(orSet2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompareDifferentSets() {
|
||||
ORSet<String> orSet1 = new ORSet<>();
|
||||
ORSet<String> orSet2 = new ORSet<>();
|
||||
|
||||
orSet1.add("A");
|
||||
orSet2.add("B");
|
||||
|
||||
assertFalse(orSet1.compare(orSet2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMerge() {
|
||||
ORSet<String> orSet1 = new ORSet<>();
|
||||
ORSet<String> orSet2 = new ORSet<>();
|
||||
|
||||
orSet1.add("A");
|
||||
orSet1.add("A");
|
||||
orSet1.add("B");
|
||||
orSet1.remove("B");
|
||||
orSet2.add("B");
|
||||
orSet2.add("C");
|
||||
orSet2.remove("C");
|
||||
orSet1.merge(orSet2);
|
||||
|
||||
assertTrue(orSet1.contains("A"));
|
||||
assertTrue(orSet1.contains("B"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user