Repository: TheAlgorithms/Java
Pull request #4980 (2023-12-10 12:48:03)

Introduce OR-Set (Observed-Remove Set) for managing sets in a distributed and concurrent environment
<!--
Thank you for your contribution!
In order to reduce the number of notifications sent to the maintainers, please:
- create your PR as draft, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests,
- make sure that all of the CI checks pass,
- mark your PR as ready for review, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#marking-a-pull-request-as-ready-for-review
-->

<!-- For completed items, change [ ] to [x] -->

This PR consists of an implementation of a OR-Set (Observed-Remove Set), which is a state-based CRDT (Conflict-free Replicated Data Type) for managing sets in a distributed and concurrent environment.

See: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type

- [X] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Java/blob/master/CONTRIBUTING.md).
- [X] This pull request is all my own work -- I have not plagiarized it.
- [X] All filenames are in PascalCase.
- [X] All functions and variable names follow Java naming conventions.
- [X] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.
- [X] All new code is formatted with `clang-format -i --style=file path/to/your/file.java

itakurah: > Looks good, thanks!

Could you merge?
siriak: Sure, I just got distracted and forgot about it :)

Patch:
diff --git a/DIRECTORY.md b/DIRECTORY.md
index f033416d..b769250e 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)
               * [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
diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java b/src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java
new file mode 100644
index 00000000..a4cc2ffd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java
@@ -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;
+        }
+    }
+}


Test patch:
diff --git a/src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java b/src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java
new file mode 100644
index 00000000..f12c38f1
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java
@@ -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"));
+    }
+}