argumentation_bipolar/
queries.rs1use crate::coalition::detect_coalitions;
11use crate::derived::closed_attacks;
12use crate::framework::BipolarFramework;
13use std::collections::{HashSet, VecDeque};
14use std::hash::Hash;
15
16#[must_use]
19pub fn transitive_supporters<A>(framework: &BipolarFramework<A>, a: &A) -> HashSet<A>
20where
21 A: Clone + Eq + Hash,
22{
23 let mut visited: HashSet<A> = HashSet::new();
24 let mut frontier: VecDeque<A> = VecDeque::new();
25 frontier.push_back(a.clone());
26
27 while let Some(current) = frontier.pop_front() {
28 for (supporter, supported) in framework.supports() {
29 if *supported == current && visited.insert(supporter.clone()) {
30 frontier.push_back(supporter.clone());
31 }
32 }
33 }
34 visited.remove(a);
35 visited
36}
37
38#[must_use]
41pub fn transitive_attackers<A>(framework: &BipolarFramework<A>, a: &A) -> HashSet<A>
42where
43 A: Clone + Eq + Hash,
44{
45 closed_attacks(framework)
46 .into_iter()
47 .filter_map(|(att, tgt)| if tgt == *a { Some(att) } else { None })
48 .collect()
49}
50
51#[must_use]
54pub fn coalitioned_with<A>(framework: &BipolarFramework<A>, a: &A) -> HashSet<A>
55where
56 A: Clone + Eq + Hash,
57{
58 let coalitions = detect_coalitions(framework);
59 for coalition in coalitions {
60 if coalition.members.contains(a) {
61 return coalition.members.into_iter().filter(|m| m != a).collect();
62 }
63 }
64 HashSet::new()
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn transitive_supporters_walks_support_chain() {
73 let mut bf = BipolarFramework::new();
75 bf.add_support("a", "b").unwrap();
76 bf.add_support("b", "c").unwrap();
77 let sups = transitive_supporters(&bf, &"c");
78 assert_eq!(sups.len(), 2);
79 assert!(sups.contains(&"a"));
80 assert!(sups.contains(&"b"));
81 }
82
83 #[test]
84 fn transitive_attackers_includes_derived_edges() {
85 let mut bf = BipolarFramework::new();
87 bf.add_support("a", "x").unwrap();
88 bf.add_attack("x", "b");
89 let atts = transitive_attackers(&bf, &"b");
90 assert!(atts.contains(&"a"), "derived attacker should be present");
91 assert!(atts.contains(&"x"));
92 }
93
94 #[test]
95 fn coalitioned_with_returns_siblings() {
96 let mut bf = BipolarFramework::new();
97 bf.add_support("alice", "bob").unwrap();
98 bf.add_support("bob", "alice").unwrap();
99 let allies = coalitioned_with(&bf, &"alice");
100 assert_eq!(allies.len(), 1);
101 assert!(allies.contains(&"bob"));
102 }
103
104 #[test]
105 fn coalitioned_with_returns_empty_for_singleton() {
106 let mut bf = BipolarFramework::new();
107 bf.add_argument("alice");
108 let allies = coalitioned_with(&bf, &"alice");
109 assert!(allies.is_empty());
110 }
111}