From b1efd4e34bb18a618ab205c9c2a62802f151c70d Mon Sep 17 00:00:00 2001 From: Niklas Hoefflin <122729995+itakurah@users.noreply.github.com> Date: Fri, 24 Nov 2023 18:13:44 +0100 Subject: [PATCH] Add G-Counter (Grow-only Counter) (#4965) --- .../datastructures/crdt/GCounter.java | 84 +++++++++++++++++++ .../datastructures/crdt/GCounterTest.java | 54 ++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java create mode 100644 src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java b/src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java new file mode 100644 index 00000000..63364f85 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java @@ -0,0 +1,84 @@ +package com.thealgorithms.datastructures.crdt; + +import java.util.HashMap; +import java.util.Map; + +/** + * G-Counter (Grow-only Counter) is a state-based CRDT (Conflict-free Replicated Data Type) + * designed for tracking counts in a distributed and concurrent environment. + * Each process maintains its own counter, allowing only increments. The total count + * is obtained by summing individual process counts. + * This implementation supports incrementing, querying the total count, + * comparing with other G-Counters, and merging with another G-Counter + * to compute the element-wise maximum. + * (https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) + * + * @author itakurah (https://github.com/itakurah) + */ + +class GCounter { + private final Map P; + private final int myId; + private final int n; + + /** + * Constructs a G-Counter for a cluster of n nodes. + * + * @param n The number of nodes in the cluster. + */ + public GCounter(int myId, int n) { + this.myId = myId; + this.n = n; + this.P = new HashMap<>(); + + for (int i = 0; i < n; i++) { + P.put(i, 0); + } + } + + /** + * Increments the counter for the current node. + */ + public void increment() { + P.put(myId, P.get(myId) + 1); + } + + /** + * Gets the total value of the counter by summing up values from all nodes. + * + * @return The total value of the counter. + */ + public int value() { + int sum = 0; + for (int v : P.values()) { + sum += v; + } + return sum; + } + + /** + * Compares the state of this G-Counter with another G-Counter. + * + * @param other The other G-Counter to compare with. + * @return True if the state of this G-Counter is less than or equal to the state of the other G-Counter. + */ + public boolean compare(GCounter other) { + for (int i = 0; i < n; i++) { + if (this.P.get(i) > other.P.get(i)) { + return false; + } + } + return true; + } + + /** + * Merges the state of this G-Counter with another G-Counter. + * + * @param other The other G-Counter to merge with. + */ + public void merge(GCounter other) { + for (int i = 0; i < n; i++) { + this.P.put(i, Math.max(this.P.get(i), other.P.get(i))); + } + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java b/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java new file mode 100644 index 00000000..f931e602 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.datastructures.crdt; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class GCounterTest { + @Test + void increment() { + GCounter counter = new GCounter(0, 3); + counter.increment(); + counter.increment(); + counter.increment(); + assertEquals(3, counter.value()); + } + + @Test + void merge() { + GCounter counter1 = new GCounter(0, 3); + counter1.increment(); + GCounter counter2 = new GCounter(1, 3); + counter2.increment(); + counter2.increment(); + GCounter counter3 = new GCounter(2, 3); + counter3.increment(); + counter3.increment(); + counter3.increment(); + counter1.merge(counter2); + counter1.merge(counter3); + counter2.merge(counter1); + counter3.merge(counter2); + assertEquals(6, counter1.value()); + assertEquals(6, counter2.value()); + assertEquals(6, counter3.value()); + } + + @Test + void compare() { + GCounter counter1 = new GCounter(0, 5); + GCounter counter2 = new GCounter(3, 5); + counter1.increment(); + counter1.increment(); + counter2.merge(counter1); + counter2.increment(); + counter2.increment(); + assertTrue(counter1.compare(counter2)); + counter1.increment(); + counter2.increment(); + counter2.merge(counter1); + assertTrue(counter1.compare(counter2)); + counter1.increment(); + assertFalse(counter1.compare(counter2)); + } +}