ndhistogram/axis/
category.rs

1use std::hash::Hash;
2use std::{collections::HashMap, fmt::Display};
3
4use super::Axis;
5use super::SingleValueBinInterval;
6
7// Type-bound alias
8pub trait Value: Eq + Hash + Clone {}
9impl<T: Eq + Hash + Clone> Value for T {}
10
11/// An axis to represent a set of discrete values or categories with an overflow bin.
12///
13/// This axis also includes an overflow bin, to include "other" values not given
14/// when the axis was constructed.
15/// See [CategoryNoFlow](crate::axis::CategoryNoFlow) for a variant that includes no overflow bin.
16///
17/// # Example
18///
19/// ```rust
20/// use ndhistogram::axis::{Axis, Category, SingleValueBinInterval};
21/// let colors = Category::new(vec!["red", "blue", "pink", "yellow", "black"]);
22/// assert_eq!(colors.index(&"red"), Some(0));
23/// assert_eq!(colors.index(&"green"), Some(5));
24/// assert_eq!(colors.bin(1), Some(SingleValueBinInterval::new("blue")));
25/// assert_eq!(colors.bin(5), Some(SingleValueBinInterval::overflow()));
26/// ```
27#[derive(Default, Clone, PartialEq, Eq, Debug)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct Category<T>
30where
31    T: Eq + Hash,
32{
33    map_t_to_index: HashMap<T, usize>,
34    map_index_to_t: HashMap<usize, T>,
35}
36
37impl<T: Value> Category<T> {
38    fn insert(&mut self, value: T) {
39        // TODO: implement solution that does not require storing two copies of T
40        let index = self.map_index_to_t.len();
41        self.map_index_to_t.insert(index, value.clone());
42        self.map_t_to_index.insert(value, index);
43    }
44
45    fn get_index(&self, value: &T) -> Option<usize> {
46        self.map_t_to_index.get(value).copied()
47    }
48
49    fn get_value(&self, index: usize) -> Option<&T> {
50        self.map_index_to_t.get(&index)
51    }
52
53    fn len(&self) -> usize {
54        self.map_index_to_t.len()
55    }
56
57    fn constructor<I: IntoIterator<Item = T>>(values: I) -> Self {
58        let mut cat = Self {
59            map_t_to_index: HashMap::new(),
60            map_index_to_t: HashMap::new(),
61        };
62        // TODO: is it faster to directly construct the hashmap rather than repeatedly insert?
63        values.into_iter().for_each(|it| cat.insert(it));
64        cat
65    }
66
67    /// Factory method to create a category axis without an overflow bin.
68    ///
69    /// Takes an iterator over a set of values that represent each category.
70    /// All other values will be mapped to the overflow bin.
71    pub fn new<I: IntoIterator<Item = T>>(values: I) -> Self {
72        Self::constructor(values)
73    }
74}
75
76impl<T: Value> Axis for Category<T> {
77    type Coordinate = T;
78
79    type BinInterval = SingleValueBinInterval<T>;
80
81    #[inline]
82    fn index(&self, coordinate: &Self::Coordinate) -> Option<usize> {
83        self.get_index(coordinate).or_else(|| Some(self.len()))
84    }
85
86    fn num_bins(&self) -> usize {
87        self.len() + 1
88    }
89
90    fn bin(&self, index: usize) -> Option<Self::BinInterval> {
91        let value = self.get_value(index);
92        match value {
93            Some(value) => Some(Self::BinInterval::new(value.clone())),
94            None => {
95                if index == self.len() {
96                    Some(Self::BinInterval::overflow())
97                } else {
98                    None
99                }
100            }
101        }
102    }
103}
104
105impl<T: Display + Value> Display for Category<T> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        let comma_separated_list = self
108            .bins()
109            .take(10)
110            .map(|it| it.to_string())
111            .collect::<Vec<_>>()
112            .join(", ");
113        write!(f, "{{{}}}", comma_separated_list)
114    }
115}
116
117impl<'a, T: Value> IntoIterator for &'a Category<T> {
118    type Item = (usize, <Category<T> as Axis>::BinInterval);
119    type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
120
121    fn into_iter(self) -> Self::IntoIter {
122        self.iter()
123    }
124}