diff --git a/DIRECTORY.md b/DIRECTORY.md index 4a948095..703642a0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -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) * [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 * [DisjointSetUnion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java) * [Node](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/disjointsetunion/Node.java) @@ -528,7 +529,7 @@ * [InfixToPostfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java) * [LargestRectangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/LargestRectangle.java) * [MaximumMinimumWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MaximumMinimumWindow.java) - * [NextGraterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGraterElement.java) + * [NextGreaterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) * [NextSmallerElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) * [PostfixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixToInfix.java) * [StackPostfixNotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackPostfixNotation.java) @@ -621,6 +622,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) * [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 * [DisjointSetUnionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java) * graphs diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java b/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java new file mode 100644 index 00000000..f5e155c2 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java @@ -0,0 +1,84 @@ +package com.thealgorithms.datastructures.crdt; + +import java.util.HashSet; +import java.util.Set; + +/** + * TwoPhaseSet (2P-Set) is a state-based CRDT (Conflict-free Replicated Data Type) designed for managing sets + * with support for both addition and removal operations in a distributed and concurrent environment. + * It combines two G-Sets (grow-only sets) - one set for additions and another set (tombstone set) for removals. + * Once an element is removed and placed in the tombstone set, it cannot be re-added, adhering to "remove-wins" semantics. + * This implementation supports querying the presence of elements, adding elements, removing elements, + * comparing with other 2P-Sets, and merging two 2P-Sets while preserving the remove-wins semantics. + * (https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) + * + * @author itakurah (Niklas Hoefflin) (https://github.com/itakurah) + */ + +public class TwoPSet { + private Set setA; + private Set setR; + + /** + * Constructs an empty Two-Phase Set. + */ + public TwoPSet() { + this.setA = new HashSet<>(); + this.setR = new HashSet<>(); + } + + /** + * Checks if an element is in the set and has not been removed. + * + * @param element The element to be checked. + * @return True if the element is in the set and has not been removed, otherwise false. + */ + public boolean lookup(String element) { + return setA.contains(element) && !setR.contains(element); + } + + /** + * Adds an element to the set. + * + * @param element The element to be added. + */ + public void add(String element) { + setA.add(element); + } + + /** + * Removes an element from the set. The element will be placed in the tombstone set. + * + * @param element The element to be removed. + */ + public void remove(String element) { + if (lookup(element)) { + setR.add(element); + } + } + + /** + * Compares the current 2P-Set with another 2P-Set. + * + * @param otherSet The other 2P-Set to compare with. + * @return True if both SetA and SetR are subset, otherwise false. + */ + public boolean compare(TwoPSet otherSet) { + return otherSet.setA.containsAll(setA) && otherSet.setR.containsAll(setR); + } + + /** + * Merges the current 2P-Set with another 2P-Set. + * + * @param otherSet The other 2P-Set to merge with. + * @return A new 2P-Set containing the merged elements. + */ + public TwoPSet merge(TwoPSet otherSet) { + TwoPSet mergedSet = new TwoPSet(); + mergedSet.setA.addAll(this.setA); + mergedSet.setA.addAll(otherSet.setA); + mergedSet.setR.addAll(this.setR); + mergedSet.setR.addAll(otherSet.setR); + return mergedSet; + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java b/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java new file mode 100644 index 00000000..18ab5c16 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.datastructures.crdt; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TwoPSetTest { + + private TwoPSet set; + + @BeforeEach + void setUp() { + set = new TwoPSet(); + } + + @Test + void testLookup() { + set.add("A"); + assertTrue(set.lookup("A")); + assertFalse(set.lookup("B")); + set.remove("A"); + assertFalse(set.lookup("A")); + } + + @Test + void testAdd() { + set.add("A"); + assertTrue(set.lookup("A")); + } + + @Test + void testRemove() { + set.add("A"); + set.remove("A"); + assertFalse(set.lookup("A")); + } + + @Test + void testCompare() { + TwoPSet set1 = new TwoPSet(); + set1.add("A"); + set1.add("B"); + TwoPSet set2 = new TwoPSet(); + set2.add("A"); + assertFalse(set1.compare(set2)); + set2.add("B"); + assertTrue(set1.compare(set2)); + set1.remove("A"); + assertFalse(set1.compare(set2)); + set2.remove("A"); + assertTrue(set1.compare(set2)); + } + + @Test + void testMerge() { + TwoPSet set1 = new TwoPSet(); + set1.add("A"); + set1.add("B"); + TwoPSet set2 = new TwoPSet(); + set2.add("B"); + set2.add("C"); + TwoPSet mergedSet = set1.merge(set2); + assertTrue(mergedSet.lookup("A")); + assertTrue(mergedSet.lookup("B")); + assertTrue(mergedSet.lookup("C")); + } +}