Skip to content

Commit b1efd4e

Browse files
authored
Add G-Counter (Grow-only Counter) (#4965)
1 parent c527dff commit b1efd4e

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.thealgorithms.datastructures.crdt;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
/**
7+
* G-Counter (Grow-only Counter) is a state-based CRDT (Conflict-free Replicated Data Type)
8+
* designed for tracking counts in a distributed and concurrent environment.
9+
* Each process maintains its own counter, allowing only increments. The total count
10+
* is obtained by summing individual process counts.
11+
* This implementation supports incrementing, querying the total count,
12+
* comparing with other G-Counters, and merging with another G-Counter
13+
* to compute the element-wise maximum.
14+
* (https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)
15+
*
16+
* @author itakurah (https://github.com/itakurah)
17+
*/
18+
19+
class GCounter {
20+
private final Map<Integer, Integer> P;
21+
private final int myId;
22+
private final int n;
23+
24+
/**
25+
* Constructs a G-Counter for a cluster of n nodes.
26+
*
27+
* @param n The number of nodes in the cluster.
28+
*/
29+
public GCounter(int myId, int n) {
30+
this.myId = myId;
31+
this.n = n;
32+
this.P = new HashMap<>();
33+
34+
for (int i = 0; i < n; i++) {
35+
P.put(i, 0);
36+
}
37+
}
38+
39+
/**
40+
* Increments the counter for the current node.
41+
*/
42+
public void increment() {
43+
P.put(myId, P.get(myId) + 1);
44+
}
45+
46+
/**
47+
* Gets the total value of the counter by summing up values from all nodes.
48+
*
49+
* @return The total value of the counter.
50+
*/
51+
public int value() {
52+
int sum = 0;
53+
for (int v : P.values()) {
54+
sum += v;
55+
}
56+
return sum;
57+
}
58+
59+
/**
60+
* Compares the state of this G-Counter with another G-Counter.
61+
*
62+
* @param other The other G-Counter to compare with.
63+
* @return True if the state of this G-Counter is less than or equal to the state of the other G-Counter.
64+
*/
65+
public boolean compare(GCounter other) {
66+
for (int i = 0; i < n; i++) {
67+
if (this.P.get(i) > other.P.get(i)) {
68+
return false;
69+
}
70+
}
71+
return true;
72+
}
73+
74+
/**
75+
* Merges the state of this G-Counter with another G-Counter.
76+
*
77+
* @param other The other G-Counter to merge with.
78+
*/
79+
public void merge(GCounter other) {
80+
for (int i = 0; i < n; i++) {
81+
this.P.put(i, Math.max(this.P.get(i), other.P.get(i)));
82+
}
83+
}
84+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.thealgorithms.datastructures.crdt;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
public class GCounterTest {
8+
@Test
9+
void increment() {
10+
GCounter counter = new GCounter(0, 3);
11+
counter.increment();
12+
counter.increment();
13+
counter.increment();
14+
assertEquals(3, counter.value());
15+
}
16+
17+
@Test
18+
void merge() {
19+
GCounter counter1 = new GCounter(0, 3);
20+
counter1.increment();
21+
GCounter counter2 = new GCounter(1, 3);
22+
counter2.increment();
23+
counter2.increment();
24+
GCounter counter3 = new GCounter(2, 3);
25+
counter3.increment();
26+
counter3.increment();
27+
counter3.increment();
28+
counter1.merge(counter2);
29+
counter1.merge(counter3);
30+
counter2.merge(counter1);
31+
counter3.merge(counter2);
32+
assertEquals(6, counter1.value());
33+
assertEquals(6, counter2.value());
34+
assertEquals(6, counter3.value());
35+
}
36+
37+
@Test
38+
void compare() {
39+
GCounter counter1 = new GCounter(0, 5);
40+
GCounter counter2 = new GCounter(3, 5);
41+
counter1.increment();
42+
counter1.increment();
43+
counter2.merge(counter1);
44+
counter2.increment();
45+
counter2.increment();
46+
assertTrue(counter1.compare(counter2));
47+
counter1.increment();
48+
counter2.increment();
49+
counter2.merge(counter1);
50+
assertTrue(counter1.compare(counter2));
51+
counter1.increment();
52+
assertFalse(counter1.compare(counter2));
53+
}
54+
}

0 commit comments

Comments
 (0)