use std::collections::{BTreeSet, HashMap};
use crate::circuit::Circuit;
#[derive(Debug, Clone)]
pub struct NodeMap {
map: HashMap<String, usize>,
reverse: Vec<String>,
ground: String,
}
impl NodeMap {
pub fn from_circuit(circuit: &Circuit) -> Self {
let mut unique: BTreeSet<String> = BTreeSet::new();
for component in &circuit.components {
for node in component.all_nodes() {
unique.insert(node.clone());
}
}
unique.remove(&circuit.ground_node);
let mut map = HashMap::new();
let mut reverse = Vec::new();
for node in unique {
let idx = map.len();
map.insert(node.clone(), idx);
reverse.push(node);
}
Self {
map,
reverse,
ground: circuit.ground_node.clone(),
}
}
pub fn from_nodes(nodes: &[String], ground: &str) -> Self {
let mut map = HashMap::new();
let mut reverse = Vec::new();
for node in nodes {
if node == ground {
continue;
}
if map.contains_key(node.as_str()) {
continue;
}
let idx = map.len();
map.insert(node.clone(), idx);
reverse.push(node.clone());
}
Self {
map,
reverse,
ground: ground.to_string(),
}
}
pub fn index(&self, node: &str) -> Option<usize> {
if node == self.ground {
None
} else {
self.map.get(node).copied()
}
}
pub fn num_nodes(&self) -> usize {
self.map.len()
}
pub fn node_name(&self, index: usize) -> Option<&str> {
self.reverse.get(index).map(|s| s.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn three_nodes_with_ground() {
let nodes: Vec<String> = vec!["0".into(), "n1".into(), "n2".into()];
let nm = NodeMap::from_nodes(&nodes, "0");
assert_eq!(nm.num_nodes(), 2);
assert_eq!(nm.index("0"), None, "ground must map to None");
assert!(nm.index("n1").is_some());
assert!(nm.index("n2").is_some());
assert_ne!(nm.index("n1"), nm.index("n2"));
}
#[test]
fn empty_node_list_only_ground() {
let nodes: Vec<String> = vec!["0".into()];
let nm = NodeMap::from_nodes(&nodes, "0");
assert_eq!(nm.num_nodes(), 0);
assert_eq!(nm.index("0"), None);
}
#[test]
fn reverse_lookup_matches_forward() {
let nodes: Vec<String> = vec!["0".into(), "a".into(), "b".into(), "c".into()];
let nm = NodeMap::from_nodes(&nodes, "0");
for name in &["a", "b", "c"] {
let idx = nm.index(name).expect("node should have an index");
let reverse_name = nm.node_name(idx).expect("index should have a name");
assert_eq!(reverse_name, *name);
}
}
#[test]
fn duplicate_nodes_are_deduplicated() {
let nodes: Vec<String> = vec!["0".into(), "n1".into(), "n1".into(), "n2".into()];
let nm = NodeMap::from_nodes(&nodes, "0");
assert_eq!(nm.num_nodes(), 2);
}
#[test]
fn unknown_node_returns_none() {
let nodes: Vec<String> = vec!["0".into(), "n1".into()];
let nm = NodeMap::from_nodes(&nodes, "0");
assert_eq!(nm.index("nonexistent"), None);
}
}