Skip to main content

argumentation_values/
types.rs

1//! Core types: `Value`, `ValueAssignment`, `Audience`.
2
3use smallvec::SmallVec;
4use std::collections::HashMap;
5use std::hash::Hash;
6
7/// A value that an argument can promote.
8///
9/// Currently a thin newtype around `String`. May become an extensible
10/// trait in the future if consumers need richer value semantics
11/// (numeric magnitudes, hierarchical taxonomies). v0 keeps it simple.
12#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Value(String);
14
15impl Value {
16    /// Construct a `Value` from any string-like input.
17    pub fn new(s: impl Into<String>) -> Self {
18        Self(s.into())
19    }
20
21    /// Borrow the underlying string.
22    pub fn as_str(&self) -> &str {
23        &self.0
24    }
25}
26
27impl std::fmt::Display for Value {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.0)
30    }
31}
32
33impl From<&str> for Value {
34    fn from(s: &str) -> Self {
35        Self(s.to_string())
36    }
37}
38
39impl From<String> for Value {
40    fn from(s: String) -> Self {
41        Self(s)
42    }
43}
44
45/// Maps each argument to the set of values it promotes.
46///
47/// An empty set (or absent entry) means "promotes no value" — under VAF
48/// semantics such arguments defeat unconditionally (no value preference
49/// can save a target whose attacker promotes no value, and vice versa).
50///
51/// Multi-value support per Kaci & van der Torre (2008): an argument may
52/// promote several values simultaneously. Single-value (Bench-Capon 2003)
53/// is the degenerate case where every set has exactly one element.
54#[derive(Debug, Clone)]
55pub struct ValueAssignment<A: Eq + Hash> {
56    /// `SmallVec<[Value; 1]>` keeps the common single-value case allocation-free.
57    promoted: HashMap<A, SmallVec<[Value; 1]>>,
58}
59
60impl<A: Eq + Hash> Default for ValueAssignment<A> {
61    fn default() -> Self {
62        Self {
63            promoted: HashMap::new(),
64        }
65    }
66}
67
68impl<A: Eq + Hash + Clone> ValueAssignment<A> {
69    /// Construct an empty assignment.
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Add a value to the set of values promoted by `arg`.
75    /// Returns `&mut self` for builder chaining.
76    pub fn promote(&mut self, arg: A, value: Value) -> &mut Self {
77        let entry = self.promoted.entry(arg).or_default();
78        if !entry.contains(&value) {
79            entry.push(value);
80        }
81        self
82    }
83
84    /// The set of values promoted by `arg`. Returns an empty slice if
85    /// `arg` is not present (which is semantically the "no values" case).
86    pub fn values(&self, arg: &A) -> &[Value] {
87        self.promoted
88            .get(arg)
89            .map(|v| v.as_slice())
90            .unwrap_or(&[])
91    }
92
93    /// Iterator over (argument, values) entries.
94    pub fn entries(&self) -> impl Iterator<Item = (&A, &[Value])> {
95        self.promoted.iter().map(|(k, v)| (k, v.as_slice()))
96    }
97
98    /// All distinct values mentioned anywhere in the assignment.
99    pub fn distinct_values(&self) -> std::collections::BTreeSet<&Value> {
100        self.promoted.values().flatten().collect()
101    }
102}
103
104/// An audience is a strict partial order over values, represented as
105/// ranked tiers. Each inner `Vec<Value>` is one tier; values within a
106/// tier are equally preferred. Earlier tiers are strictly more preferred
107/// than later tiers.
108///
109/// # Examples
110///
111/// ```rust
112/// use argumentation_values::{Audience, Value};
113/// let life = Value::new("life");
114/// let property = Value::new("property");
115///
116/// // Total order: life > property
117/// let strict = Audience::total([life.clone(), property.clone()]);
118/// assert!(strict.prefers(&life, &property));
119/// assert!(!strict.prefers(&property, &life));
120///
121/// // Incomparable values
122/// let flat = Audience::from_tiers(vec![vec![life.clone(), property.clone()]]);
123/// assert!(!flat.prefers(&life, &property));
124/// assert!(!flat.prefers(&property, &life));
125/// ```
126#[derive(Debug, Clone, Default)]
127pub struct Audience {
128    /// Each inner Vec is a tier; index 0 is most preferred.
129    tiers: Vec<Vec<Value>>,
130}
131
132impl Audience {
133    /// Construct an empty audience (no preferences — all attacks survive).
134    pub fn new() -> Self {
135        Self::default()
136    }
137
138    /// Construct a total ordering from an iterator of values, most
139    /// preferred first.
140    pub fn total<I: IntoIterator<Item = Value>>(ranked: I) -> Self {
141        Self {
142            tiers: ranked.into_iter().map(|v| vec![v]).collect(),
143        }
144    }
145
146    /// Construct from explicit ranked tiers. Each inner vec is one tier
147    /// of equally preferred values.
148    pub fn from_tiers(tiers: Vec<Vec<Value>>) -> Self {
149        Self { tiers }
150    }
151
152    /// Returns true iff `a` is *strictly* preferred to `b` under this audience.
153    /// Returns false if either value is unranked (incomparable).
154    pub fn prefers(&self, a: &Value, b: &Value) -> bool {
155        match (self.rank(a), self.rank(b)) {
156            (Some(ra), Some(rb)) => ra < rb,
157            _ => false,
158        }
159    }
160
161    /// 0-indexed tier of `v` (0 = most preferred), or `None` if `v` is
162    /// unranked (not mentioned in any tier).
163    ///
164    /// Public so consumers (e.g., `ValueAwareScorer`) can compute boost
165    /// magnitudes without re-implementing the lookup.
166    pub fn rank(&self, v: &Value) -> Option<usize> {
167        self.tiers
168            .iter()
169            .position(|tier| tier.iter().any(|x| x == v))
170    }
171
172    /// Iterate the distinct values mentioned in this audience.
173    pub fn values(&self) -> impl Iterator<Item = &Value> {
174        self.tiers.iter().flatten()
175    }
176
177    /// Number of distinct values in this audience.
178    pub fn value_count(&self) -> usize {
179        self.tiers.iter().map(|t| t.len()).sum()
180    }
181
182    /// Number of tiers (rank levels).
183    pub fn tier_count(&self) -> usize {
184        self.tiers.len()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn value_assignment_dedupes_promotions() {
194        let mut va: ValueAssignment<&str> = ValueAssignment::new();
195        va.promote("a", Value::new("life"));
196        va.promote("a", Value::new("life"));
197        assert_eq!(va.values(&"a").len(), 1);
198    }
199
200    #[test]
201    fn value_assignment_accepts_multi_value() {
202        let mut va: ValueAssignment<&str> = ValueAssignment::new();
203        va.promote("a", Value::new("life"));
204        va.promote("a", Value::new("autonomy"));
205        assert_eq!(va.values(&"a").len(), 2);
206    }
207
208    #[test]
209    fn audience_total_orders_strictly() {
210        let a = Audience::total([Value::new("life"), Value::new("property")]);
211        assert!(a.prefers(&Value::new("life"), &Value::new("property")));
212        assert!(!a.prefers(&Value::new("property"), &Value::new("life")));
213    }
214
215    #[test]
216    fn audience_unranked_values_are_incomparable() {
217        let a = Audience::total([Value::new("life")]);
218        assert!(!a.prefers(&Value::new("property"), &Value::new("life")));
219        assert!(!a.prefers(&Value::new("life"), &Value::new("property")));
220    }
221
222    #[test]
223    fn audience_intra_tier_values_are_incomparable() {
224        let a = Audience::from_tiers(vec![vec![Value::new("life"), Value::new("liberty")]]);
225        assert!(!a.prefers(&Value::new("life"), &Value::new("liberty")));
226        assert!(!a.prefers(&Value::new("liberty"), &Value::new("life")));
227    }
228
229    #[test]
230    fn audience_distinct_values_count() {
231        let a = Audience::from_tiers(vec![
232            vec![Value::new("a"), Value::new("b")],
233            vec![Value::new("c")],
234        ]);
235        assert_eq!(a.value_count(), 3);
236        assert_eq!(a.tier_count(), 2);
237    }
238
239    #[test]
240    fn audience_rank_returns_tier_index() {
241        let a = Audience::from_tiers(vec![
242            vec![Value::new("life"), Value::new("liberty")],
243            vec![Value::new("property")],
244        ]);
245        assert_eq!(a.rank(&Value::new("life")), Some(0));
246        assert_eq!(a.rank(&Value::new("liberty")), Some(0));
247        assert_eq!(a.rank(&Value::new("property")), Some(1));
248        assert_eq!(a.rank(&Value::new("comfort")), None);
249    }
250}