argumentation_bipolar/
framework.rs1use crate::error::Error;
10use std::collections::{HashMap, HashSet};
11use std::hash::Hash;
12
13#[derive(Debug, Clone)]
18pub struct BipolarFramework<A: Clone + Eq + Hash> {
19 arguments: HashSet<A>,
20 attacks: HashSet<(A, A)>,
21 supports: HashSet<(A, A)>,
22}
23
24impl<A: Clone + Eq + Hash> BipolarFramework<A> {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 arguments: HashSet::new(),
30 attacks: HashSet::new(),
31 supports: HashSet::new(),
32 }
33 }
34
35 pub fn add_argument(&mut self, a: A) {
37 self.arguments.insert(a);
38 }
39
40 pub fn add_attack(&mut self, attacker: A, target: A) {
44 self.arguments.insert(attacker.clone());
45 self.arguments.insert(target.clone());
46 self.attacks.insert((attacker, target));
47 }
48
49 pub fn add_support(&mut self, supporter: A, supported: A) -> Result<(), Error>
54 where
55 A: std::fmt::Debug,
56 {
57 if supporter == supported {
58 return Err(Error::IllegalSelfSupport(format!("{:?}", supporter)));
59 }
60 self.arguments.insert(supporter.clone());
61 self.arguments.insert(supported.clone());
62 self.supports.insert((supporter, supported));
63 Ok(())
64 }
65
66 pub fn remove_support(&mut self, supporter: &A, supported: &A) -> bool {
70 self.supports
71 .remove(&(supporter.clone(), supported.clone()))
72 }
73
74 pub fn remove_attack(&mut self, attacker: &A, target: &A) -> bool {
76 self.attacks.remove(&(attacker.clone(), target.clone()))
77 }
78
79 pub fn arguments(&self) -> impl Iterator<Item = &A> {
81 self.arguments.iter()
82 }
83
84 pub fn attacks(&self) -> impl Iterator<Item = (&A, &A)> {
86 self.attacks.iter().map(|(a, b)| (a, b))
87 }
88
89 pub fn supports(&self) -> impl Iterator<Item = (&A, &A)> {
91 self.supports.iter().map(|(a, b)| (a, b))
92 }
93
94 #[must_use]
96 pub fn len(&self) -> usize {
97 self.arguments.len()
98 }
99
100 #[must_use]
102 pub fn is_empty(&self) -> bool {
103 self.arguments.is_empty()
104 }
105
106 pub fn direct_attackers(&self, a: &A) -> Vec<&A> {
110 self.attacks
111 .iter()
112 .filter(|(_, target)| target == a)
113 .map(|(attacker, _)| attacker)
114 .collect()
115 }
116
117 pub fn direct_supporters(&self, a: &A) -> Vec<&A> {
120 self.supports
121 .iter()
122 .filter(|(_, target)| target == a)
123 .map(|(supporter, _)| supporter)
124 .collect()
125 }
126
127 pub fn supporter_map(&self) -> HashMap<&A, HashSet<&A>> {
132 let mut map: HashMap<&A, HashSet<&A>> =
133 self.arguments.iter().map(|a| (a, HashSet::new())).collect();
134 for (supporter, supported) in &self.supports {
135 map.entry(supported).or_default().insert(supporter);
136 }
137 map
138 }
139}
140
141impl<A: Clone + Eq + Hash> Default for BipolarFramework<A> {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147const _: fn() = || {
150 fn assert_send<T: Send>() {}
151 fn assert_sync<T: Sync>() {}
152 assert_send::<BipolarFramework<String>>();
153 assert_sync::<BipolarFramework<String>>();
154};
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn empty_framework_has_no_arguments() {
162 let bf: BipolarFramework<&str> = BipolarFramework::new();
163 assert!(bf.is_empty());
164 assert_eq!(bf.len(), 0);
165 }
166
167 #[test]
168 fn add_argument_is_idempotent() {
169 let mut bf = BipolarFramework::new();
170 bf.add_argument("a");
171 bf.add_argument("a");
172 assert_eq!(bf.len(), 1);
173 }
174
175 #[test]
176 fn add_attack_registers_both_endpoints() {
177 let mut bf = BipolarFramework::new();
178 bf.add_attack("a", "b");
179 assert_eq!(bf.len(), 2);
180 assert_eq!(bf.direct_attackers(&"b"), vec![&"a"]);
181 assert!(bf.direct_attackers(&"a").is_empty());
182 }
183
184 #[test]
185 fn add_support_registers_both_endpoints() {
186 let mut bf = BipolarFramework::new();
187 bf.add_support("a", "b").unwrap();
188 assert_eq!(bf.len(), 2);
189 assert_eq!(bf.direct_supporters(&"b"), vec![&"a"]);
190 }
191
192 #[test]
193 fn self_support_is_rejected() {
194 let mut bf: BipolarFramework<&str> = BipolarFramework::new();
195 let err = bf.add_support("a", "a").unwrap_err();
196 assert!(matches!(err, Error::IllegalSelfSupport(_)));
197 }
198
199 #[test]
200 fn remove_support_returns_whether_edge_was_present() {
201 let mut bf = BipolarFramework::new();
202 bf.add_support("a", "b").unwrap();
203 assert!(bf.remove_support(&"a", &"b"));
204 assert!(!bf.remove_support(&"a", &"b"));
205 assert_eq!(bf.len(), 2);
207 }
208
209 #[test]
210 fn supporter_map_includes_all_arguments_even_unsupported_ones() {
211 let mut bf = BipolarFramework::new();
212 bf.add_argument("a");
213 bf.add_support("b", "c").unwrap();
214 let map = bf.supporter_map();
215 assert_eq!(map.len(), 3);
216 assert!(map[&"a"].is_empty());
217 assert!(map[&"b"].is_empty());
218 assert_eq!(map[&"c"].len(), 1);
219 assert!(map[&"c"].contains(&"b"));
220 }
221}