premise 0.1.5

the core pattern library of Premise: sequences, tables, records, money, days and text, every function carrying the reason it exists
Documentation
use patterns_macros::because;
use std::collections::{BTreeMap, BTreeSet};

#[derive(patterns_macros::Record)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Award {
    pub count: u64,
    pub first: u64,
    pub last: u64,
}
because!(Award, "how many have earned one badge and when the first and the last did, the row a badge's leaderboard reads");

#[derive(patterns_macros::Record)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Earned<K, A> {
    pub who: K,
    pub badge: A,
    pub at: u64,
    pub rank: u64,
}
because!(Earned, "one account earning one badge at a moment, ranked by how many had earned it before, the row an account page lists");

pub type Given<K, A> = BTreeSet<(K, A)>;
because!(Given, "who has already been given which award, kept as a set of pairs so the question a grant asks, whether this one has had this already, is the set own membership test");

pub type Awards<A> = BTreeMap<A, Award>;
because!(Awards, "how many of each award have been given and when the first and last went out, which is what a rank is counted from");

pub type Granted<K, A> = (Given<K, A>, Awards<A>, Vec<Earned<K, A>>);
because!(Granted, "what a round of granting leaves behind: the pairs now given, the counts now raised, and the rows the round produced, returned together because a caller that took one and not the others would be holding a record of a grant that never happened");

pub fn awarded<K: Ord + Clone, A: Ord + Copy>(given: Given<K, A>, awards: Awards<A>, candidates: &[(K, A)], at: u64) -> Granted<K, A> {
    let mut given = given;
    let mut awards = awards;
    let mut rows = Vec::new();
    for (who, badge) in candidates {
        if !given.insert((who.clone(), *badge)) {
            continue;
        }
        let award = awards.entry(*badge).or_default();
        award.count += 1;
        if award.first == 0 {
            award.first = at;
        }
        award.last = award.last.max(at);
        rows.push(Earned {
            who: who.clone(),
            badge: *badge,
            at,
            rank: award.count,
        });
    }
    (given, awards, rows)
}
because!(awarded, "candidates granted once each and never again, each taking the next rank of its badge, in the order offered, so a badge earned twice in one batch counts once and the rank an account keeps is the one it earned at the time");