use std::collections::{HashMap, HashSet};
use std::hash::Hash;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Because {
Near,
Subscribed,
Either,
}
impl Because {
pub fn is_near(self) -> bool {
matches!(self, Because::Near | Because::Either)
}
pub fn is_subscribed(self) -> bool {
matches!(self, Because::Subscribed | Because::Either)
}
}
#[derive(Debug, Clone)]
pub struct Subscriptions<K: Eq + Hash + Clone> {
out: HashMap<K, HashSet<K>>,
back: HashMap<K, HashSet<K>>,
limit: usize,
}
impl<K: Eq + Hash + Clone> Default for Subscriptions<K> {
fn default() -> Self {
Self::new(usize::MAX)
}
}
impl<K: Eq + Hash + Clone> Subscriptions<K> {
pub fn new(limit: usize) -> Self {
Self {
out: HashMap::new(),
back: HashMap::new(),
limit,
}
}
pub fn subscribe(&mut self, who: K, to: K) -> bool {
if who == to {
return false;
}
let held = self.out.get(&who).map(|s| s.len()).unwrap_or(0);
if held >= self.limit && !self.out.get(&who).is_some_and(|s| s.contains(&to)) {
return false;
}
self.out.entry(who.clone()).or_default().insert(to.clone());
self.back.entry(to).or_default().insert(who);
true
}
pub fn pair(&mut self, a: K, b: K) -> bool {
if a == b {
return false;
}
let room = |set: &Self, who: &K, to: &K| {
set.out.get(who).is_some_and(|s| s.contains(to)) || set.out.get(who).map(|s| s.len()).unwrap_or(0) < set.limit
};
if !room(self, &a, &b) || !room(self, &b, &a) {
return false;
}
self.subscribe(a.clone(), b.clone());
self.subscribe(b, a);
true
}
pub fn group(&mut self, a: K, b: K) -> bool {
if a == b {
return false;
}
let mut members: Vec<K> = self.group_of(&a);
for member in self.group_of(&b) {
if !members.contains(&member) {
members.push(member);
}
}
if members.len().saturating_sub(1) > self.limit {
return false;
}
for who in &members {
for to in &members {
if who != to {
self.out.entry(who.clone()).or_default().insert(to.clone());
self.back.entry(to.clone()).or_default().insert(who.clone());
}
}
}
true
}
pub fn group_of(&self, key: &K) -> Vec<K> {
let mut members = vec![key.clone()];
if let Some(set) = self.out.get(key) {
for other in set {
if self.back.get(key).is_some_and(|b| b.contains(other)) {
members.push(other.clone());
}
}
}
members
}
pub fn unsubscribe(&mut self, who: &K, from: &K) {
if let Some(set) = self.out.get_mut(who) {
set.remove(from);
if set.is_empty() {
self.out.remove(who);
}
}
if let Some(set) = self.back.get_mut(from) {
set.remove(who);
if set.is_empty() {
self.back.remove(from);
}
}
}
pub fn leave_group(&mut self, key: &K) -> Vec<K> {
let members: Vec<K> = self.group_of(key).into_iter().filter(|m| m != key).collect();
for other in &members {
self.unsubscribe(key, other);
self.unsubscribe(other, key);
}
if members.len() == 1 {
let alone = members[0].clone();
for other in self.group_of(&alone).into_iter().filter(|m| *m != alone) {
self.unsubscribe(&alone, &other);
self.unsubscribe(&other, &alone);
}
}
members
}
pub fn remove(&mut self, key: &K) -> Vec<K> {
let watchers: Vec<K> = self.back.remove(key).map(|s| s.into_iter().collect()).unwrap_or_default();
for watcher in &watchers {
if let Some(set) = self.out.get_mut(watcher) {
set.remove(key);
if set.is_empty() {
self.out.remove(watcher);
}
}
}
if let Some(mine) = self.out.remove(key) {
for to in mine {
if let Some(set) = self.back.get_mut(&to) {
set.remove(key);
if set.is_empty() {
self.back.remove(&to);
}
}
}
}
watchers
}
pub fn of<'a>(&'a self, key: &K) -> impl Iterator<Item = &'a K> + 'a {
self.out.get(key).into_iter().flatten()
}
pub fn watchers<'a>(&'a self, key: &K) -> impl Iterator<Item = &'a K> + 'a {
self.back.get(key).into_iter().flatten()
}
pub fn count_of(&self, key: &K) -> usize {
self.out.get(key).map(|s| s.len()).unwrap_or(0)
}
pub fn is_subscribed(&self, who: &K, to: &K) -> bool {
self.out.get(who).is_some_and(|s| s.contains(to))
}
pub fn subscribers(&self) -> impl Iterator<Item = &K> {
self.out.keys()
}
pub fn clear(&mut self) {
self.out.clear();
self.back.clear();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Audience<K> {
pub entries: Vec<(K, Because)>,
pub near: usize,
pub added: usize,
}
impl<K: Eq + Hash + Clone + Ord> Audience<K> {
pub fn of(near: &[K], subscriptions: &Subscriptions<K>, viewer: &K) -> Self {
let close: HashSet<&K> = near.iter().collect();
let mut entries: Vec<(K, Because)> = Vec::with_capacity(near.len());
let mut added = 0;
for key in near {
let also = key != viewer && subscriptions.is_subscribed(viewer, key);
entries.push((key.clone(), if also { Because::Either } else { Because::Near }));
}
for key in subscriptions.of(viewer) {
if !close.contains(key) {
entries.push((key.clone(), Because::Subscribed));
added += 1;
}
}
entries.sort_unstable();
Self {
near: near.len(),
added,
entries,
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.entries.iter().map(|(key, _)| key)
}
pub fn visible(&self) -> impl Iterator<Item = &K> {
self
.entries
.iter()
.filter(|(_, why)| why.is_near())
.map(|(key, _)| key)
}
pub fn why(&self, key: &K) -> Option<Because> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, why)| *why)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_subscription_is_directed_unless_you_ask_for_both() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
assert!(subs.subscribe(1, 2));
assert!(subs.is_subscribed(&1, &2));
assert!(!subs.is_subscribed(&2, &1));
assert!(subs.pair(3, 4));
assert!(subs.is_subscribed(&3, &4) && subs.is_subscribed(&4, &3));
}
#[test]
fn nobody_subscribes_to_themselves() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
assert!(!subs.subscribe(1, 1));
assert!(!subs.pair(1, 1));
assert!(!subs.group(1, 1));
assert_eq!(subs.count_of(&1), 0);
}
#[test]
fn grouping_merges_both_groups_rather_than_adding_one_person() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.pair(1, 2);
subs.pair(3, 4);
assert!(subs.group(2, 3));
for key in 1..=4u32 {
assert_eq!(subs.group_of(&key).len(), 4, "everyone sees the whole group");
assert_eq!(subs.count_of(&key), 3, "and is subscribed to everyone but themselves");
}
}
#[test]
fn a_group_refuses_to_grow_past_the_limit_and_changes_nothing_when_it_does() {
let mut subs: Subscriptions<u32> = Subscriptions::new(3);
subs.pair(1, 2);
subs.pair(3, 4);
assert!(subs.group(1, 3), "four members is three subscriptions each");
subs.pair(5, 6);
assert!(!subs.group(1, 5), "six would be five each, past the limit");
assert_eq!(subs.group_of(&1).len(), 4, "and the existing group is untouched");
assert_eq!(subs.group_of(&5).len(), 2);
}
#[test]
fn a_one_sided_subscription_is_not_a_group() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.subscribe(1, 2);
assert_eq!(subs.group_of(&1), vec![1]);
assert_eq!(subs.group_of(&2), vec![2]);
}
#[test]
fn removing_a_key_names_everyone_who_has_to_be_told() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.subscribe(1, 9);
subs.subscribe(2, 9);
subs.subscribe(9, 3);
let mut told = subs.remove(&9);
told.sort_unstable();
assert_eq!(told, vec![1, 2]);
assert_eq!(subs.count_of(&1), 0, "and their subscription is gone with it");
assert_eq!(subs.watchers(&3).count(), 0, "both directions, or the reverse index leaks");
}
#[test]
fn a_removed_key_leaves_nothing_behind() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.pair(1, 2);
subs.remove(&1);
assert_eq!(subs.count_of(&2), 0);
assert_eq!(subs.subscribers().count(), 0, "no empty sets left holding memory");
}
#[test]
fn unsubscribing_one_direction_leaves_the_other_and_is_no_longer_a_group() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.pair(1, 2);
subs.unsubscribe(&1, &2);
assert!(!subs.is_subscribed(&1, &2));
assert!(subs.is_subscribed(&2, &1), "the other direction is untouched");
assert_eq!(subs.group_of(&1), vec![1], "and neither is in a group any more");
assert_eq!(subs.group_of(&2), vec![2]);
}
#[test]
fn unsubscribing_something_that_was_never_there_changes_nothing() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.subscribe(1, 2);
subs.unsubscribe(&1, &9);
subs.unsubscribe(&9, &1);
assert!(subs.is_subscribed(&1, &2));
assert_eq!(subs.subscribers().count(), 1, "and leaves no empty sets behind");
}
#[test]
fn leaving_a_party_is_not_leaving_the_world() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.group(1, 2);
subs.group(1, 3);
subs.subscribe(9, 1);
let told = subs.leave_group(&1);
assert_eq!(told.len(), 2, "both remaining members were told");
assert_eq!(subs.group_of(&1), vec![1], "out of the party");
assert!(subs.is_subscribed(&9, &1), "and still being watched by a spectator");
let mut left = subs.group_of(&2);
left.sort_unstable();
assert_eq!(left, vec![2, 3], "the rest of the party is intact");
}
#[test]
fn the_second_to_last_leaving_dissolves_what_is_left() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.group(1, 2);
subs.group(1, 3);
subs.leave_group(&3);
assert_eq!(subs.group_of(&1).len(), 2, "two is still a party");
subs.leave_group(&2);
assert_eq!(subs.group_of(&1), vec![1], "one is not");
assert_eq!(subs.subscribers().count(), 0, "and nothing is left holding memory");
}
#[test]
fn an_audience_labels_why_each_entry_is_there() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.pair(1, 2);
subs.pair(1, 9);
let audience = Audience::of(&[1, 2, 3], &subs, &1);
assert_eq!(audience.why(&1), Some(Because::Near), "the viewer is not their own subscription");
assert_eq!(audience.why(&2), Some(Because::Either));
assert_eq!(audience.why(&3), Some(Because::Near));
assert_eq!(audience.why(&9), Some(Because::Subscribed));
assert_eq!(audience.added, 1, "which cost exactly one entry");
assert_eq!(audience.visible().count(), 3, "and 9 is described without being drawable");
}
#[test]
fn a_subscriber_standing_beside_you_costs_nothing_extra() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.pair(1, 2);
let audience = Audience::of(&[1, 2, 3], &subs, &1);
assert_eq!(audience.added, 0);
assert_eq!(audience.len(), 3, "and nobody is listed twice");
}
#[test]
fn an_audience_is_ordered_so_a_diff_between_ticks_means_something() {
let mut subs: Subscriptions<u32> = Subscriptions::default();
subs.pair(1, 40);
let a = Audience::of(&[7, 1, 22], &subs, &1);
let b = Audience::of(&[22, 7, 1], &subs, &1);
assert_eq!(a, b, "the order the query returned is not the order sent");
assert_eq!(a.keys().copied().collect::<Vec<_>>(), vec![1, 7, 22, 40]);
}
#[test]
fn what_the_second_channel_costs() {
let mut subs: Subscriptions<u32> = Subscriptions::new(4);
subs.group(1, 2);
subs.group(1, 3);
subs.group(1, 4);
subs.group(1, 5);
assert_eq!(subs.count_of(&1), 4);
for together in [0usize, 2, 4] {
let mut near: Vec<u32> = (100..140).collect();
near.push(1);
near.extend((2..=5).take(together));
let audience = Audience::of(&near, &subs, &1);
assert_eq!(audience.added, 4 - together, "only the ones distance missed");
}
}
}