From 5c0505e221eb96028d1a92566832cb59d580507d Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Mon, 27 Feb 2023 11:27:15 +0100 Subject: [PATCH] crdt: Implement `GSet` type, based on `GMap` --- radicle-crdt/src/gmap.rs | 8 +++- radicle-crdt/src/gset.rs | 97 ++++++++++++++++++++++++++++++++++++++++ radicle-crdt/src/lib.rs | 2 + 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 radicle-crdt/src/gset.rs diff --git a/radicle-crdt/src/gmap.rs b/radicle-crdt/src/gmap.rs index 42d750a2..9377ec00 100644 --- a/radicle-crdt/src/gmap.rs +++ b/radicle-crdt/src/gmap.rs @@ -1,4 +1,4 @@ -use std::collections::btree_map::{Entry, IntoIter}; +use std::collections::btree_map::{Entry, IntoIter, IntoKeys}; use std::collections::BTreeMap; use std::ops::Deref; @@ -35,6 +35,12 @@ impl GMap { } } +impl GMap { + pub fn into_keys(self) -> IntoKeys { + self.inner.into_keys() + } +} + impl FromIterator<(K, V)> for GMap { fn from_iter>(iter: I) -> Self { let mut map = GMap::default(); diff --git a/radicle-crdt/src/gset.rs b/radicle-crdt/src/gset.rs new file mode 100644 index 00000000..4a4c0975 --- /dev/null +++ b/radicle-crdt/src/gset.rs @@ -0,0 +1,97 @@ +use std::collections::btree_map::{IntoKeys, Keys}; +use std::ops::Deref; + +use crate::GMap; +use crate::Semilattice; + +/// Grow-only set. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GSet { + inner: GMap, +} + +impl GSet { + pub fn singleton(key: K) -> Self { + Self { + inner: GMap::from_iter([(key, ())]), + } + } + + pub fn insert(&mut self, key: K) { + self.inner.insert(key, ()); + } + + pub fn iter(&self) -> Keys<'_, K, ()> { + self.inner.keys() + } +} + +impl FromIterator for GSet { + fn from_iter>(iter: I) -> Self { + let mut map = GSet::default(); + for k in iter.into_iter() { + map.insert(k); + } + map + } +} + +impl Extend for GSet { + fn extend>(&mut self, iter: I) { + for k in iter.into_iter() { + self.insert(k); + } + } +} + +impl IntoIterator for GSet { + type Item = K; + type IntoIter = IntoKeys; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_keys() + } +} + +impl Default for GSet { + fn default() -> Self { + Self { + inner: GMap::default(), + } + } +} + +impl Semilattice for GSet { + fn merge(&mut self, other: Self) { + for k in other.into_iter() { + self.insert(k); + } + } +} + +impl Deref for GSet { + type Target = GMap; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[cfg(test)] +mod tests { + use qcheck_macros::quickcheck; + + use super::*; + + #[quickcheck] + fn prop_semilattice(a: Vec, b: Vec, c: Vec, mix: Vec) { + let mut a = GSet::from_iter(a); + let mut b = GSet::from_iter(b); + let c = GSet::from_iter(c); + + a.extend(mix.clone()); + b.extend(mix); + + crate::test::assert_laws(&a, &b, &c); + } +} diff --git a/radicle-crdt/src/lib.rs b/radicle-crdt/src/lib.rs index 930984fa..1595dbd0 100644 --- a/radicle-crdt/src/lib.rs +++ b/radicle-crdt/src/lib.rs @@ -4,6 +4,7 @@ #![allow(clippy::type_complexity)] pub mod clock; pub mod gmap; +pub mod gset; pub mod lwwmap; pub mod lwwreg; pub mod lwwset; @@ -17,6 +18,7 @@ pub mod test; pub use clock::Lamport; pub use gmap::GMap; +pub use gset::GSet; pub use lwwmap::LWWMap; pub use lwwreg::LWWReg; pub use lwwset::LWWSet;