Skip to content

Commit e26fd9d

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

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
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
Lines changed: 191 additions & 0 deletions
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+
}
Lines changed: 86 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy