Skip to content

Commit cbbc46c

Browse files
committed
Refactor LWWElementSet: Use Instant for higher precision & remove bias comparison
- Replaced int timestamps with Instant for improved precision - Refactored class to be generic, allowing support for different types of elements - Removed bias-based comparison to align with LWW-Element-Set - Reference: https://inria.hal.science/inria-00555588v1/document
1 parent 8579856 commit cbbc46c

File tree

1 file changed

+72
-86
lines changed

1 file changed

+72
-86
lines changed
Lines changed: 72 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,124 @@
11
package com.thealgorithms.datastructures.crdt;
22

3+
import java.time.Instant;
34
import java.util.HashMap;
45
import java.util.Map;
56

67
/**
78
* Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type)
89
* designed for managing sets in a distributed and concurrent environment. It supports the addition and removal
910
* 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+
* the add set for elements to be added and the remove set for elements to be removed. The LWWElementSet ensures
12+
* that the most recent operation (based on the timestamp) wins in the case of concurrent operations.
1113
*
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>
14+
* @param <T> The type of the elements in the LWWElementSet.
15+
* @author <a href="https://github.com/itakurah">itakurah (GitHub)</a>,
16+
* <a href="https://www.linkedin.com/in/niklashoefflin/"> Niklas Hoefflin (LinkedIn)</a>
17+
* @see <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">Conflict free replicated data type (Wikipedia)</a>
18+
* @see <a href="https://inria.hal.science/inria-00555588v1/document">A comprehensive study of
19+
* Convergent and Commutative Replicated Data Types</a>
1520
*/
1621

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-
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;
22+
class LWWElementSet<T> {
23+
final Map<T, Element<T>> addSet;
24+
final Map<T, Element<T>> removeSet;
4825

4926
/**
5027
* Constructs an empty LWWElementSet.
28+
* This constructor initializes the addSet and removeSet as empty HashMaps.
29+
* The addSet stores elements that are added, and the removeSet stores elements that are removed.
5130
*/
5231
LWWElementSet() {
5332
this.addSet = new HashMap<>();
5433
this.removeSet = new HashMap<>();
5534
}
5635

5736
/**
58-
* Adds an element to the addSet.
37+
* Adds an element to the addSet with the current timestamp.
38+
* This method stores the element in the addSet, ensuring that the element is added to the set
39+
* with an associated timestamp that represents the time of the addition.
5940
*
60-
* @param e The element to be added.
41+
* @param key The key of the element to be added.
6142
*/
62-
public void add(Element e) {
63-
addSet.put(e.key, e);
43+
public void add(T key) {
44+
addSet.put(key, new Element<>(key, Instant.now()));
6445
}
6546

6647
/**
67-
* Removes an element from the removeSet.
48+
* Removes an element by adding it to the removeSet with the current timestamp.
49+
* This method adds the element to the removeSet, marking it as removed with the current timestamp.
6850
*
69-
* @param e The element to be removed.
51+
* @param key The key of the element to be removed.
7052
*/
71-
public void remove(Element e) {
72-
if (lookup(e)) {
73-
removeSet.put(e.key, e);
74-
}
53+
public void remove(T key) {
54+
removeSet.put(key, new Element<>(key, Instant.now()));
7555
}
7656

7757
/**
78-
* Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet.
58+
* Checks if an element is in the LWWElementSet.
59+
* An element is considered present if it exists in the addSet and either does not exist in the removeSet,
60+
* or its add timestamp is later than any corresponding remove timestamp.
7961
*
80-
* @param e The element to be checked.
81-
* @return True if the element is present, false otherwise.
62+
* @param key The key of the element to be checked.
63+
* @return {@code true} if the element is present in the set (i.e., its add timestamp is later than its remove timestamp, or it is not in the remove set),
64+
* {@code false} otherwise (i.e., the element has been removed or its remove timestamp is later than its add timestamp).
8265
*/
83-
public boolean lookup(Element e) {
84-
Element inAddSet = addSet.get(e.key);
85-
Element inRemoveSet = removeSet.get(e.key);
66+
public boolean lookup(T key) {
67+
Element<T> inAddSet = addSet.get(key);
68+
Element<T> inRemoveSet = removeSet.get(key);
8669

87-
return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp));
70+
return inAddSet != null && (inRemoveSet == null || inAddSet.timestamp.isAfter(inRemoveSet.timestamp));
8871
}
8972

9073
/**
91-
* Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset.
74+
* Merges another LWWElementSet into this set.
75+
* This method takes the union of both the add-sets and remove-sets from the two sets, resolving conflicts by
76+
* keeping the element with the latest timestamp. If an element appears in both the add-set and remove-set of both sets,
77+
* the one with the later timestamp will be retained.
9278
*
93-
* @param other The LWWElementSet to compare.
94-
* @return True if the set is subset, false otherwise.
79+
* @param other The LWWElementSet to merge with the current set.
9580
*/
96-
public boolean compare(LWWElementSet other) {
97-
return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet());
81+
public void merge(LWWElementSet<T> other) {
82+
for (Map.Entry<T, Element<T>> entry : other.addSet.entrySet()) {
83+
addSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
84+
}
85+
for (Map.Entry<T, Element<T>> entry : other.removeSet.entrySet()) {
86+
removeSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
87+
}
9888
}
9989

10090
/**
101-
* Merges another LWWElementSet into this set by resolving conflicts based on timestamps.
91+
* Resolves conflicts between two elements by selecting the one with the later timestamp.
92+
* This method is used when merging two LWWElementSets to ensure that the most recent operation (based on timestamps) is kept.
10293
*
103-
* @param other The LWWElementSet to merge.
94+
* @param e1 The first element.
95+
* @param e2 The second element.
96+
* @return The element with the later timestamp.
10497
*/
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-
}
98+
private Element<T> resolveConflict(Element<T> e1, Element<T> e2) {
99+
return e1.timestamp.isAfter(e2.timestamp) ? e1 : e2;
117100
}
101+
}
102+
103+
/**
104+
* Represents an element in the LWWElementSet, consisting of a key and a timestamp.
105+
* This class is used to store the elements in both the add and remove sets with their respective timestamps.
106+
*
107+
* @param <T> The type of the key associated with the element.
108+
*/
109+
class Element<T> {
110+
T key;
111+
Instant timestamp;
118112

119113
/**
120-
* Compares timestamps of two elements based on their bias (ADDS or REMOVALS).
114+
* Constructs a new Element with the specified key and timestamp.
121115
*
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.
116+
* @param key The key of the element.
117+
* @param timestamp The timestamp associated with the element.
125118
*/
126-
public boolean compareTimestamps(Element e, Element other) {
127-
if (e.bias != 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 != Bias.ADDS;
135-
}
136-
return timestampComparison < 0;
119+
Element(T key, Instant timestamp) {
120+
this.key = key;
121+
this.timestamp = timestamp;
137122
}
138123
}
124+

0 commit comments

Comments
 (0)