Skip to content

Commit b8b1dea

Browse files
authored
Add LWW Element Set (Last Write Wins Element Set) (TheAlgorithms#4979)
1 parent 92131de commit b8b1dea

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
* crdt
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)
88+
* [LWWElementSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java)
8889
* [PNCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java)
8990
* [TwoPSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java)
9091
* disjointsetunion
@@ -621,6 +622,7 @@
621622
* crdt
622623
* [GCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java)
623624
* [GSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GSetTest.java)
625+
* [LWWElementSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java)
624626
* [PNCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/PNCounterTest.java)
625627
* [TwoPSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java)
626628
* disjointsetunion
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.thealgorithms.datastructures.crdt;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
/**
7+
* Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type)
8+
* designed for managing sets in a distributed and concurrent environment. It supports the addition and removal
9+
* of elements, using timestamps to determine the order of operations. The set is split into two subsets:
10+
* the add set for elements to be added and the remove set for elements to be removed.
11+
*
12+
* @author itakurah (Niklas Hoefflin) (https://github.com/itakurah)
13+
* @see <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">Conflict-free_replicated_data_type</a>
14+
* @see <a href="https://github.com/itakurah">itakurah (Niklas Hoefflin)</a>
15+
*/
16+
17+
class Element {
18+
String key;
19+
int timestamp;
20+
Bias bias;
21+
22+
/**
23+
* Constructs a new Element with the specified key, timestamp and bias.
24+
*
25+
* @param key The key of the element.
26+
* @param timestamp The timestamp associated with the element.
27+
* @param bias The bias of the element (ADDS or REMOVALS).
28+
*/
29+
public Element(String key, int timestamp, Bias bias) {
30+
this.key = key;
31+
this.timestamp = timestamp;
32+
this.bias = bias;
33+
}
34+
}
35+
36+
enum Bias {
37+
/**
38+
* ADDS bias for the add set.
39+
* REMOVALS bias for the remove set.
40+
*/
41+
ADDS,
42+
REMOVALS
43+
}
44+
45+
class LWWElementSet {
46+
private final Map<String, Element> addSet;
47+
private final Map<String, Element> removeSet;
48+
49+
/**
50+
* Constructs an empty LWWElementSet.
51+
*/
52+
public LWWElementSet() {
53+
this.addSet = new HashMap<>();
54+
this.removeSet = new HashMap<>();
55+
}
56+
57+
/**
58+
* Adds an element to the addSet.
59+
*
60+
* @param e The element to be added.
61+
*/
62+
public void add(Element e) {
63+
addSet.put(e.key, e);
64+
}
65+
66+
/**
67+
* Removes an element from the removeSet.
68+
*
69+
* @param e The element to be removed.
70+
*/
71+
public void remove(Element e) {
72+
if (lookup(e)) {
73+
removeSet.put(e.key, e);
74+
}
75+
}
76+
77+
/**
78+
* Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet.
79+
*
80+
* @param e The element to be checked.
81+
* @return True if the element is present, false otherwise.
82+
*/
83+
public boolean lookup(Element e) {
84+
Element inAddSet = addSet.get(e.key);
85+
Element inRemoveSet = removeSet.get(e.key);
86+
87+
return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp));
88+
}
89+
90+
/**
91+
* Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset.
92+
*
93+
* @param other The LWWElementSet to compare.
94+
* @return True if the set is subset, false otherwise.
95+
*/
96+
public boolean compare(LWWElementSet other) {
97+
return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet());
98+
}
99+
100+
/**
101+
* Merges another LWWElementSet into this set by resolving conflicts based on timestamps.
102+
*
103+
* @param other The LWWElementSet to merge.
104+
*/
105+
public void merge(LWWElementSet other) {
106+
for (Element e : other.addSet.values()) {
107+
if (!addSet.containsKey(e.key) || compareTimestamps(addSet.get(e.key), e)) {
108+
addSet.put(e.key, e);
109+
}
110+
}
111+
112+
for (Element e : other.removeSet.values()) {
113+
if (!removeSet.containsKey(e.key) || compareTimestamps(removeSet.get(e.key), e)) {
114+
removeSet.put(e.key, e);
115+
}
116+
}
117+
}
118+
119+
/**
120+
* Compares timestamps of two elements based on their bias (ADDS or REMOVALS).
121+
*
122+
* @param e The first element.
123+
* @param other The second element.
124+
* @return True if the first element's timestamp is greater or the bias is ADDS and timestamps are equal.
125+
*/
126+
public boolean compareTimestamps(Element e, Element other) {
127+
if (!e.bias.equals(other.bias)) {
128+
throw new IllegalArgumentException("Invalid bias value");
129+
}
130+
Bias bias = e.bias;
131+
int timestampComparison = Integer.compare(e.timestamp, other.timestamp);
132+
133+
if (timestampComparison == 0) {
134+
return !bias.equals(Bias.ADDS);
135+
}
136+
return timestampComparison < 0;
137+
}
138+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.thealgorithms.datastructures.crdt;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
8+
class LWWElementSetTest {
9+
10+
private LWWElementSet set;
11+
private final Bias bias = Bias.ADDS;
12+
13+
@BeforeEach
14+
void setUp() {
15+
set = new LWWElementSet();
16+
}
17+
18+
@Test
19+
void testAdd() {
20+
Element element = new Element("key1", 1, bias);
21+
set.add(element);
22+
23+
assertTrue(set.lookup(element));
24+
}
25+
26+
@Test
27+
void testRemove() {
28+
Element element = new Element("key1", 1, bias);
29+
set.add(element);
30+
set.remove(element);
31+
32+
assertFalse(set.lookup(element));
33+
}
34+
35+
@Test
36+
void testRemoveNonexistentElement() {
37+
Element element = new Element("key1", 1, bias);
38+
set.remove(element);
39+
40+
assertFalse(set.lookup(element));
41+
}
42+
43+
@Test
44+
void testLookupNonexistentElement() {
45+
Element element = new Element("key1", 1, bias);
46+
47+
assertFalse(set.lookup(element));
48+
}
49+
50+
@Test
51+
void testCompareEqualSets() {
52+
LWWElementSet otherSet = new LWWElementSet();
53+
54+
Element element = new Element("key1", 1, bias);
55+
set.add(element);
56+
otherSet.add(element);
57+
58+
assertTrue(set.compare(otherSet));
59+
60+
otherSet.add(new Element("key2", 2, bias));
61+
assertTrue(set.compare(otherSet));
62+
}
63+
64+
@Test
65+
void testCompareDifferentSets() {
66+
LWWElementSet otherSet = new LWWElementSet();
67+
68+
Element element1 = new Element("key1", 1, bias);
69+
Element element2 = new Element("key2", 2, bias);
70+
71+
set.add(element1);
72+
otherSet.add(element2);
73+
74+
assertFalse(set.compare(otherSet));
75+
}
76+
77+
@Test
78+
void testMerge() {
79+
LWWElementSet otherSet = new LWWElementSet();
80+
81+
Element element1 = new Element("key1", 1, bias);
82+
Element element2 = new Element("key2", 2, bias);
83+
84+
set.add(element1);
85+
otherSet.add(element2);
86+
87+
set.merge(otherSet);
88+
89+
assertTrue(set.lookup(element1));
90+
assertTrue(set.lookup(element2));
91+
}
92+
93+
@Test
94+
void testCompareTimestampsEqualTimestamps() {
95+
LWWElementSet lwwElementSet = new LWWElementSet();
96+
97+
Element e1 = new Element("key1", 10, Bias.REMOVALS);
98+
Element e2 = new Element("key1", 10, Bias.REMOVALS);
99+
100+
assertTrue(lwwElementSet.compareTimestamps(e1, e2));
101+
102+
e1 = new Element("key1", 10, Bias.ADDS);
103+
e2 = new Element("key1", 10, Bias.ADDS);
104+
105+
assertFalse(lwwElementSet.compareTimestamps(e1, e2));
106+
}
107+
}

0 commit comments

Comments
 (0)