Skip to main content

oxihuman_core/
access_map.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Key-value access tracking map that records read/write counts per key.
6
7use std::collections::HashMap;
8
9/// Tracks per-key access counts (reads and writes).
10#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct AccessMap {
13    reads: HashMap<String, u64>,
14    writes: HashMap<String, u64>,
15}
16
17#[allow(dead_code)]
18impl AccessMap {
19    pub fn new() -> Self {
20        Self {
21            reads: HashMap::new(),
22            writes: HashMap::new(),
23        }
24    }
25
26    pub fn record_read(&mut self, key: &str) {
27        *self.reads.entry(key.to_string()).or_insert(0) += 1;
28    }
29
30    pub fn record_write(&mut self, key: &str) {
31        *self.writes.entry(key.to_string()).or_insert(0) += 1;
32    }
33
34    pub fn read_count(&self, key: &str) -> u64 {
35        self.reads.get(key).copied().unwrap_or(0)
36    }
37
38    pub fn write_count(&self, key: &str) -> u64 {
39        self.writes.get(key).copied().unwrap_or(0)
40    }
41
42    pub fn total_accesses(&self, key: &str) -> u64 {
43        self.read_count(key) + self.write_count(key)
44    }
45
46    pub fn tracked_key_count(&self) -> usize {
47        let mut keys: std::collections::HashSet<&String> = self.reads.keys().collect();
48        keys.extend(self.writes.keys());
49        keys.len()
50    }
51
52    pub fn most_read(&self) -> Option<(String, u64)> {
53        self.reads
54            .iter()
55            .max_by_key(|(_, &v)| v)
56            .map(|(k, &v)| (k.clone(), v))
57    }
58
59    pub fn most_written(&self) -> Option<(String, u64)> {
60        self.writes
61            .iter()
62            .max_by_key(|(_, &v)| v)
63            .map(|(k, &v)| (k.clone(), v))
64    }
65
66    pub fn clear(&mut self) {
67        self.reads.clear();
68        self.writes.clear();
69    }
70
71    pub fn has_key(&self, key: &str) -> bool {
72        self.reads.contains_key(key) || self.writes.contains_key(key)
73    }
74
75    pub fn all_keys(&self) -> Vec<String> {
76        let mut keys: std::collections::HashSet<String> = self.reads.keys().cloned().collect();
77        keys.extend(self.writes.keys().cloned());
78        let mut v: Vec<String> = keys.into_iter().collect();
79        v.sort();
80        v
81    }
82}
83
84impl Default for AccessMap {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn new_map_is_empty() {
96        let m = AccessMap::new();
97        assert_eq!(m.tracked_key_count(), 0);
98    }
99
100    #[test]
101    fn record_read_increments() {
102        let mut m = AccessMap::new();
103        m.record_read("a");
104        m.record_read("a");
105        assert_eq!(m.read_count("a"), 2);
106    }
107
108    #[test]
109    fn record_write_increments() {
110        let mut m = AccessMap::new();
111        m.record_write("b");
112        assert_eq!(m.write_count("b"), 1);
113    }
114
115    #[test]
116    fn total_accesses_sums_reads_writes() {
117        let mut m = AccessMap::new();
118        m.record_read("x");
119        m.record_write("x");
120        m.record_write("x");
121        assert_eq!(m.total_accesses("x"), 3);
122    }
123
124    #[test]
125    fn missing_key_returns_zero() {
126        let m = AccessMap::new();
127        assert_eq!(m.read_count("missing"), 0);
128        assert_eq!(m.write_count("missing"), 0);
129    }
130
131    #[test]
132    fn most_read_returns_highest() {
133        let mut m = AccessMap::new();
134        m.record_read("a");
135        m.record_read("b");
136        m.record_read("b");
137        let (k, c) = m.most_read().expect("should succeed");
138        assert_eq!(k, "b");
139        assert_eq!(c, 2);
140    }
141
142    #[test]
143    fn clear_resets_all() {
144        let mut m = AccessMap::new();
145        m.record_read("a");
146        m.record_write("b");
147        m.clear();
148        assert_eq!(m.tracked_key_count(), 0);
149    }
150
151    #[test]
152    fn has_key_checks_both() {
153        let mut m = AccessMap::new();
154        m.record_write("w");
155        assert!(m.has_key("w"));
156        assert!(!m.has_key("missing"));
157    }
158
159    #[test]
160    fn all_keys_sorted() {
161        let mut m = AccessMap::new();
162        m.record_read("c");
163        m.record_write("a");
164        m.record_read("b");
165        assert_eq!(
166            m.all_keys(),
167            vec!["a".to_string(), "b".to_string(), "c".to_string()]
168        );
169    }
170
171    #[test]
172    fn default_is_empty() {
173        let m = AccessMap::default();
174        assert_eq!(m.tracked_key_count(), 0);
175    }
176}