Skip to content

Commit e26fd9d

Browse files
authored
Add OR-Set (Observed-Remove Set) (#4980)
1 parent 4aa8e6a commit e26fd9d

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed

DIRECTORY.md

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
* [GCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java)
8787
* [GSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/GSet.java)
8888
* [LWWElementSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java)
89+
* [ORSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java)
8990
* [PNCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java)
9091
* [TwoPSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java)
9192
* disjointsetunion
@@ -623,6 +624,7 @@
623624
* [GCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java)
624625
* [GSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GSetTest.java)
625626
* [LWWElementSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java)
627+
* [ORSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java)
626628
* [PNCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/PNCounterTest.java)
627629
* [TwoPSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java)
628630
* disjointsetunion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.thealgorithms.datastructures.crdt;
2+
3+
import java.util.HashSet;
4+
import java.util.Set;
5+
import java.util.UUID;
6+
7+
/**
8+
* ORSet (Observed-Removed Set) is a state-based CRDT (Conflict-free Replicated Data Type)
9+
* that supports both addition and removal of elements. This particular implementation follows
10+
* the Add-Wins strategy, meaning that in case of conflicting add and remove operations,
11+
* the add operation takes precedence. The merge operation of two OR-Sets ensures that
12+
* elements added at any replica are eventually observed at all replicas. Removed elements,
13+
* once observed, are never reintroduced.
14+
* This OR-Set implementation provides methods for adding elements, removing elements,
15+
* checking for element existence, retrieving the set of elements, comparing with other OR-Sets,
16+
* and merging with another OR-Set to create a new OR-Set containing all unique elements
17+
* from both sets.
18+
*
19+
* @author itakurah (Niklas Hoefflin) (https://github.com/itakurah)
20+
* @see <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">Conflict-free_replicated_data_type</a>
21+
* @see <a href="https://github.com/itakurah">itakurah (Niklas Hoefflin)</a>
22+
*/
23+
24+
public class ORSet<T> {
25+
26+
private final Set<Pair<T>> elements;
27+
private final Set<Pair<T>> tombstones;
28+
29+
/**
30+
* Constructs an empty OR-Set.
31+
*/
32+
public ORSet() {
33+
this.elements = new HashSet<>();
34+
this.tombstones = new HashSet<>();
35+
}
36+
37+
/**
38+
* Checks if the set contains the specified element.
39+
*
40+
* @param element the element to check for
41+
* @return true if the set contains the element, false otherwise
42+
*/
43+
public boolean contains(T element) {
44+
return elements.stream().anyMatch(pair -> pair.getElement().equals(element));
45+
}
46+
47+
/**
48+
* Retrieves the elements in the set.
49+
*
50+
* @return a set containing the elements
51+
*/
52+
public Set<T> elements() {
53+
Set<T> result = new HashSet<>();
54+
elements.forEach(pair -> result.add(pair.getElement()));
55+
return result;
56+
}
57+
58+
/**
59+
* Adds the specified element to the set.
60+
*
61+
* @param element the element to add
62+
*/
63+
public void add(T element) {
64+
String n = prepare();
65+
effect(element, n);
66+
}
67+
68+
/**
69+
* Removes the specified element from the set.
70+
*
71+
* @param element the element to remove
72+
*/
73+
public void remove(T element) {
74+
Set<Pair<T>> pairsToRemove = prepare(element);
75+
effect(pairsToRemove);
76+
}
77+
78+
/**
79+
* Collect all pairs with the specified element.
80+
*
81+
* @param element the element to collect pairs for
82+
* @return a set of pairs with the specified element to be removed
83+
*/
84+
private Set<Pair<T>> prepare(T element) {
85+
Set<Pair<T>> pairsToRemove = new HashSet<>();
86+
for (Pair<T> pair : elements) {
87+
if (pair.getElement().equals(element)) {
88+
pairsToRemove.add(pair);
89+
}
90+
}
91+
return pairsToRemove;
92+
}
93+
94+
/**
95+
* Generates a unique tag for the element.
96+
*
97+
* @return the unique tag
98+
*/
99+
private String prepare() {
100+
return generateUniqueTag();
101+
}
102+
103+
/**
104+
* Adds the element with the specified unique tag to the set.
105+
*
106+
* @param element the element to add
107+
* @param n the unique tag associated with the element
108+
*/
109+
private void effect(T element, String n) {
110+
Pair<T> pair = new Pair<>(element, n);
111+
elements.add(pair);
112+
elements.removeAll(tombstones);
113+
}
114+
115+
/**
116+
* Removes the specified pairs from the set.
117+
*
118+
* @param pairsToRemove the pairs to remove
119+
*/
120+
private void effect(Set<Pair<T>> pairsToRemove) {
121+
elements.removeAll(pairsToRemove);
122+
tombstones.addAll(pairsToRemove);
123+
}
124+
125+
/**
126+
* Generates a unique tag.
127+
*
128+
* @return the unique tag
129+
*/
130+
private String generateUniqueTag() {
131+
return UUID.randomUUID().toString();
132+
}
133+
134+
/**
135+
* Compares this Add-Wins OR-Set with another OR-Set to check if elements and tombstones are a subset.
136+
*
137+
* @param other the other OR-Set to compare
138+
* @return true if the sets are subset, false otherwise
139+
*/
140+
public boolean compare(ORSet<T> other) {
141+
Set<Pair<T>> union = new HashSet<>(elements);
142+
union.addAll(tombstones);
143+
144+
Set<Pair<T>> otherUnion = new HashSet<>(other.elements);
145+
otherUnion.addAll(other.tombstones);
146+
147+
return otherUnion.containsAll(union) && other.tombstones.containsAll(tombstones);
148+
}
149+
150+
/**
151+
* Merges this Add-Wins OR-Set with another OR-Set.
152+
*
153+
* @param other the other OR-Set to merge
154+
*/
155+
public void merge(ORSet<T> other) {
156+
elements.removeAll(other.tombstones);
157+
other.elements.removeAll(tombstones);
158+
elements.addAll(other.elements);
159+
tombstones.addAll(other.tombstones);
160+
}
161+
162+
/**
163+
* Represents a pair containing an element and a unique tag.
164+
*
165+
* @param <T> the type of the element in the pair
166+
*/
167+
public static class Pair<T> {
168+
private final T element;
169+
private final String uniqueTag;
170+
171+
/**
172+
* Constructs a pair with the specified element and unique tag.
173+
*
174+
* @param element the element in the pair
175+
* @param uniqueTag the unique tag associated with the element
176+
*/
177+
public Pair(T element, String uniqueTag) {
178+
this.element = element;
179+
this.uniqueTag = uniqueTag;
180+
}
181+
182+
/**
183+
* Gets the element from the pair.
184+
*
185+
* @return the element
186+
*/
187+
public T getElement() {
188+
return element;
189+
}
190+
}
191+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.thealgorithms.datastructures.crdt;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.util.Set;
6+
import org.junit.jupiter.api.Test;
7+
8+
class ORSetTest {
9+
10+
@Test
11+
void testContains() {
12+
ORSet<String> orSet = new ORSet<>();
13+
orSet.add("A");
14+
assertTrue(orSet.contains("A"));
15+
}
16+
17+
@Test
18+
void testAdd() {
19+
ORSet<String> orSet = new ORSet<>();
20+
orSet.add("A");
21+
assertTrue(orSet.contains("A"));
22+
}
23+
24+
@Test
25+
void testRemove() {
26+
ORSet<String> orSet = new ORSet<>();
27+
orSet.add("A");
28+
orSet.add("A");
29+
orSet.remove("A");
30+
assertFalse(orSet.contains("A"));
31+
}
32+
33+
@Test
34+
void testElements() {
35+
ORSet<String> orSet = new ORSet<>();
36+
orSet.add("A");
37+
orSet.add("B");
38+
assertEquals(Set.of("A", "B"), orSet.elements());
39+
}
40+
41+
@Test
42+
void testCompareEqualSets() {
43+
ORSet<String> orSet1 = new ORSet<>();
44+
ORSet<String> orSet2 = new ORSet<>();
45+
46+
orSet1.add("A");
47+
orSet2.add("A");
48+
orSet2.add("B");
49+
orSet2.add("C");
50+
orSet2.remove("C");
51+
orSet1.merge(orSet2);
52+
orSet2.merge(orSet1);
53+
orSet2.remove("B");
54+
55+
assertTrue(orSet1.compare(orSet2));
56+
}
57+
58+
@Test
59+
void testCompareDifferentSets() {
60+
ORSet<String> orSet1 = new ORSet<>();
61+
ORSet<String> orSet2 = new ORSet<>();
62+
63+
orSet1.add("A");
64+
orSet2.add("B");
65+
66+
assertFalse(orSet1.compare(orSet2));
67+
}
68+
69+
@Test
70+
void testMerge() {
71+
ORSet<String> orSet1 = new ORSet<>();
72+
ORSet<String> orSet2 = new ORSet<>();
73+
74+
orSet1.add("A");
75+
orSet1.add("A");
76+
orSet1.add("B");
77+
orSet1.remove("B");
78+
orSet2.add("B");
79+
orSet2.add("C");
80+
orSet2.remove("C");
81+
orSet1.merge(orSet2);
82+
83+
assertTrue(orSet1.contains("A"));
84+
assertTrue(orSet1.contains("B"));
85+
}
86+
}

0 commit comments

Comments
 (0)