Skip to main content

oxihuman_core/
data_table.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// A simple column-oriented data table with string column names and f64 values.
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct DataTable {
9    columns: Vec<String>,
10    rows: Vec<Vec<f64>>,
11}
12
13#[allow(dead_code)]
14impl DataTable {
15    pub fn new(columns: &[&str]) -> Self {
16        Self {
17            columns: columns.iter().map(|s| s.to_string()).collect(),
18            rows: Vec::new(),
19        }
20    }
21
22    pub fn add_row(&mut self, values: &[f64]) -> bool {
23        if values.len() != self.columns.len() {
24            return false;
25        }
26        self.rows.push(values.to_vec());
27        true
28    }
29
30    pub fn get(&self, row: usize, col: usize) -> Option<f64> {
31        self.rows.get(row).and_then(|r| r.get(col).copied())
32    }
33
34    pub fn col_index(&self, name: &str) -> Option<usize> {
35        self.columns.iter().position(|c| c == name)
36    }
37
38    pub fn get_by_name(&self, row: usize, col_name: &str) -> Option<f64> {
39        let col = self.col_index(col_name)?;
40        self.get(row, col)
41    }
42
43    pub fn num_rows(&self) -> usize {
44        self.rows.len()
45    }
46
47    pub fn num_cols(&self) -> usize {
48        self.columns.len()
49    }
50
51    pub fn column_sum(&self, col: usize) -> f64 {
52        self.rows.iter().filter_map(|r| r.get(col)).sum()
53    }
54
55    pub fn column_avg(&self, col: usize) -> f64 {
56        if self.rows.is_empty() {
57            return 0.0;
58        }
59        self.column_sum(col) / self.rows.len() as f64
60    }
61
62    pub fn column_names(&self) -> &[String] {
63        &self.columns
64    }
65
66    pub fn is_empty(&self) -> bool {
67        self.rows.is_empty()
68    }
69
70    pub fn clear(&mut self) {
71        self.rows.clear();
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_new() {
81        let dt = DataTable::new(&["x", "y"]);
82        assert_eq!(dt.num_cols(), 2);
83        assert!(dt.is_empty());
84    }
85
86    #[test]
87    fn test_add_row() {
88        let mut dt = DataTable::new(&["a", "b"]);
89        assert!(dt.add_row(&[1.0, 2.0]));
90        assert_eq!(dt.num_rows(), 1);
91    }
92
93    #[test]
94    fn test_add_row_wrong_size() {
95        let mut dt = DataTable::new(&["a", "b"]);
96        assert!(!dt.add_row(&[1.0]));
97    }
98
99    #[test]
100    fn test_get() {
101        let mut dt = DataTable::new(&["x"]);
102        dt.add_row(&[42.0]);
103        assert_eq!(dt.get(0, 0), Some(42.0));
104        assert_eq!(dt.get(1, 0), None);
105    }
106
107    #[test]
108    fn test_get_by_name() {
109        let mut dt = DataTable::new(&["x", "y"]);
110        dt.add_row(&[1.0, 2.0]);
111        assert_eq!(dt.get_by_name(0, "y"), Some(2.0));
112        assert_eq!(dt.get_by_name(0, "z"), None);
113    }
114
115    #[test]
116    fn test_column_sum() {
117        let mut dt = DataTable::new(&["val"]);
118        dt.add_row(&[1.0]);
119        dt.add_row(&[2.0]);
120        dt.add_row(&[3.0]);
121        assert!((dt.column_sum(0) - 6.0).abs() < 1e-12);
122    }
123
124    #[test]
125    fn test_column_avg() {
126        let mut dt = DataTable::new(&["val"]);
127        dt.add_row(&[2.0]);
128        dt.add_row(&[4.0]);
129        assert!((dt.column_avg(0) - 3.0).abs() < 1e-12);
130    }
131
132    #[test]
133    fn test_column_avg_empty() {
134        let dt = DataTable::new(&["val"]);
135        assert!((dt.column_avg(0)).abs() < 1e-12);
136    }
137
138    #[test]
139    fn test_clear() {
140        let mut dt = DataTable::new(&["a"]);
141        dt.add_row(&[1.0]);
142        dt.clear();
143        assert!(dt.is_empty());
144    }
145
146    #[test]
147    fn test_col_index() {
148        let dt = DataTable::new(&["alpha", "beta", "gamma"]);
149        assert_eq!(dt.col_index("beta"), Some(1));
150        assert_eq!(dt.col_index("delta"), None);
151    }
152}