use smallvec::SmallVec;
use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Value(String);
impl Value {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Debug, Clone)]
pub struct ValueAssignment<A: Eq + Hash> {
promoted: HashMap<A, SmallVec<[Value; 1]>>,
}
impl<A: Eq + Hash> Default for ValueAssignment<A> {
fn default() -> Self {
Self {
promoted: HashMap::new(),
}
}
}
impl<A: Eq + Hash + Clone> ValueAssignment<A> {
pub fn new() -> Self {
Self::default()
}
pub fn promote(&mut self, arg: A, value: Value) -> &mut Self {
let entry = self.promoted.entry(arg).or_default();
if !entry.contains(&value) {
entry.push(value);
}
self
}
pub fn values(&self, arg: &A) -> &[Value] {
self.promoted
.get(arg)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn entries(&self) -> impl Iterator<Item = (&A, &[Value])> {
self.promoted.iter().map(|(k, v)| (k, v.as_slice()))
}
pub fn distinct_values(&self) -> std::collections::BTreeSet<&Value> {
self.promoted.values().flatten().collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct Audience {
tiers: Vec<Vec<Value>>,
}
impl Audience {
pub fn new() -> Self {
Self::default()
}
pub fn total<I: IntoIterator<Item = Value>>(ranked: I) -> Self {
Self {
tiers: ranked.into_iter().map(|v| vec![v]).collect(),
}
}
pub fn from_tiers(tiers: Vec<Vec<Value>>) -> Self {
Self { tiers }
}
pub fn prefers(&self, a: &Value, b: &Value) -> bool {
match (self.rank(a), self.rank(b)) {
(Some(ra), Some(rb)) => ra < rb,
_ => false,
}
}
pub fn rank(&self, v: &Value) -> Option<usize> {
self.tiers
.iter()
.position(|tier| tier.iter().any(|x| x == v))
}
pub fn values(&self) -> impl Iterator<Item = &Value> {
self.tiers.iter().flatten()
}
pub fn value_count(&self) -> usize {
self.tiers.iter().map(|t| t.len()).sum()
}
pub fn tier_count(&self) -> usize {
self.tiers.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_assignment_dedupes_promotions() {
let mut va: ValueAssignment<&str> = ValueAssignment::new();
va.promote("a", Value::new("life"));
va.promote("a", Value::new("life"));
assert_eq!(va.values(&"a").len(), 1);
}
#[test]
fn value_assignment_accepts_multi_value() {
let mut va: ValueAssignment<&str> = ValueAssignment::new();
va.promote("a", Value::new("life"));
va.promote("a", Value::new("autonomy"));
assert_eq!(va.values(&"a").len(), 2);
}
#[test]
fn audience_total_orders_strictly() {
let a = Audience::total([Value::new("life"), Value::new("property")]);
assert!(a.prefers(&Value::new("life"), &Value::new("property")));
assert!(!a.prefers(&Value::new("property"), &Value::new("life")));
}
#[test]
fn audience_unranked_values_are_incomparable() {
let a = Audience::total([Value::new("life")]);
assert!(!a.prefers(&Value::new("property"), &Value::new("life")));
assert!(!a.prefers(&Value::new("life"), &Value::new("property")));
}
#[test]
fn audience_intra_tier_values_are_incomparable() {
let a = Audience::from_tiers(vec![vec![Value::new("life"), Value::new("liberty")]]);
assert!(!a.prefers(&Value::new("life"), &Value::new("liberty")));
assert!(!a.prefers(&Value::new("liberty"), &Value::new("life")));
}
#[test]
fn audience_distinct_values_count() {
let a = Audience::from_tiers(vec![
vec![Value::new("a"), Value::new("b")],
vec![Value::new("c")],
]);
assert_eq!(a.value_count(), 3);
assert_eq!(a.tier_count(), 2);
}
#[test]
fn audience_rank_returns_tier_index() {
let a = Audience::from_tiers(vec![
vec![Value::new("life"), Value::new("liberty")],
vec![Value::new("property")],
]);
assert_eq!(a.rank(&Value::new("life")), Some(0));
assert_eq!(a.rank(&Value::new("liberty")), Some(0));
assert_eq!(a.rank(&Value::new("property")), Some(1));
assert_eq!(a.rank(&Value::new("comfort")), None);
}
}