Skip to main content

molrs_core/block/
block_view.rs

1//! Zero-copy borrowed view of a [`Block`].
2//!
3//! `BlockView<'a>` borrows columns from a [`Block`] as [`ColumnView`]s without
4//! copying any array data, providing read-only access with the same API surface
5//! as `Block`.
6
7use std::collections::HashMap;
8
9use ndarray::ArrayViewD;
10
11use super::Block;
12use super::column::Column;
13use super::column_view::ColumnView;
14use super::dtype::DType;
15use crate::types::{F, I, U};
16
17/// A borrowed, read-only view of a [`Block`].
18///
19/// Keys are `&str` references into the original `Block`'s key strings.
20/// Values are [`ColumnView`]s that borrow the underlying array data.
21pub struct BlockView<'a> {
22    map: HashMap<&'a str, ColumnView<'a>>,
23    nrows: Option<usize>,
24}
25
26impl<'a> BlockView<'a> {
27    /// Creates an empty `BlockView`.
28    pub fn new() -> Self {
29        Self {
30            map: HashMap::new(),
31            nrows: None,
32        }
33    }
34
35    /// Inserts a column view under the given key.
36    ///
37    /// If the `BlockView` was empty, `nrows` is set from the column's axis-0
38    /// length. Subsequent insertions are not validated for consistency (caller
39    /// is responsible).
40    pub fn insert(&mut self, key: &'a str, col: ColumnView<'a>) {
41        if self.nrows.is_none() {
42            self.nrows = col.nrows();
43        }
44        self.map.insert(key, col);
45    }
46
47    /// Number of columns in the view.
48    #[inline]
49    pub fn len(&self) -> usize {
50        self.map.len()
51    }
52
53    /// Returns `true` if the view contains no columns.
54    #[inline]
55    pub fn is_empty(&self) -> bool {
56        self.map.is_empty()
57    }
58
59    /// Returns the common axis-0 length, or `None` if empty.
60    #[inline]
61    pub fn nrows(&self) -> Option<usize> {
62        self.nrows
63    }
64
65    /// Returns `true` if the view contains the specified key.
66    #[inline]
67    pub fn contains_key(&self, key: &str) -> bool {
68        self.map.contains_key(key)
69    }
70
71    /// Gets an immutable reference to the column view for `key` if present.
72    #[inline]
73    pub fn get(&self, key: &str) -> Option<&ColumnView<'a>> {
74        self.map.get(key)
75    }
76
77    /// Gets a float array view for `key` if present and of correct type.
78    pub fn get_float(&self, key: &str) -> Option<ArrayViewD<'a, F>> {
79        self.get(key).and_then(|c| c.as_float())
80    }
81
82    /// Gets an int array view for `key` if present and of correct type.
83    pub fn get_int(&self, key: &str) -> Option<ArrayViewD<'a, I>> {
84        self.get(key).and_then(|c| c.as_int())
85    }
86
87    /// Gets a bool array view for `key` if present and of correct type.
88    pub fn get_bool(&self, key: &str) -> Option<ArrayViewD<'a, bool>> {
89        self.get(key).and_then(|c| c.as_bool())
90    }
91
92    /// Gets a uint array view for `key` if present and of correct type.
93    pub fn get_uint(&self, key: &str) -> Option<ArrayViewD<'a, U>> {
94        self.get(key).and_then(|c| c.as_uint())
95    }
96
97    /// Gets a u8 array view for `key` if present and of correct type.
98    pub fn get_u8(&self, key: &str) -> Option<ArrayViewD<'a, u8>> {
99        self.get(key).and_then(|c| c.as_u8())
100    }
101
102    /// Gets a string array view for `key` if present and of correct type.
103    pub fn get_string(&self, key: &str) -> Option<ArrayViewD<'a, String>> {
104        self.get(key).and_then(|c| c.as_string())
105    }
106
107    /// Returns an iterator over `(&str, &ColumnView)`.
108    pub fn iter(&self) -> impl Iterator<Item = (&&'a str, &ColumnView<'a>)> {
109        self.map.iter()
110    }
111
112    /// Returns an iterator over column keys.
113    pub fn keys(&self) -> impl Iterator<Item = &&'a str> {
114        self.map.keys()
115    }
116
117    /// Returns an iterator over column view references.
118    pub fn values(&self) -> impl Iterator<Item = &ColumnView<'a>> {
119        self.map.values()
120    }
121
122    /// Returns the data type of the column with the given key, if it exists.
123    pub fn dtype(&self, key: &str) -> Option<DType> {
124        self.get(key).map(|c| c.dtype())
125    }
126
127    /// Creates an owned [`Block`] by cloning all viewed data.
128    pub fn to_owned(&self) -> Block {
129        let mut block = Block::new();
130        for (&key, col_view) in &self.map {
131            let col: Column = col_view.to_owned();
132            let _ = block.insert_column(key, col);
133        }
134        block
135    }
136}
137
138impl<'a> Default for BlockView<'a> {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl<'a> From<&'a Block> for BlockView<'a> {
145    fn from(block: &'a Block) -> Self {
146        let mut view = BlockView {
147            map: HashMap::with_capacity(block.len()),
148            nrows: block.nrows(),
149        };
150        for (key, col) in block.iter() {
151            view.map.insert(key, ColumnView::from(col));
152        }
153        view
154    }
155}
156
157impl std::fmt::Debug for BlockView<'_> {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        let mut map = f.debug_map();
160        for (k, v) in &self.map {
161            map.entry(k, &format!("{}(shape={:?})", v.dtype(), v.shape()));
162        }
163        map.finish()
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use ndarray::Array1;
171
172    #[test]
173    fn test_from_block() {
174        let mut block = Block::new();
175        block
176            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0, 3.0]).into_dyn())
177            .unwrap();
178        block
179            .insert("id", Array1::from_vec(vec![10 as I, 20, 30]).into_dyn())
180            .unwrap();
181
182        let view = BlockView::from(&block);
183        assert_eq!(view.len(), 2);
184        assert_eq!(view.nrows(), Some(3));
185        assert!(view.contains_key("x"));
186        assert!(view.contains_key("id"));
187        assert!(!view.is_empty());
188    }
189
190    #[test]
191    fn test_typed_getters() {
192        let mut block = Block::new();
193        block
194            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0, 3.0]).into_dyn())
195            .unwrap();
196        block
197            .insert("id", Array1::from_vec(vec![10 as I, 20, 30]).into_dyn())
198            .unwrap();
199
200        let view = BlockView::from(&block);
201
202        // Correct type
203        assert!(view.get_float("x").is_some());
204        assert!(view.get_int("id").is_some());
205
206        // Wrong type
207        assert!(view.get_int("x").is_none());
208        assert!(view.get_float("id").is_none());
209
210        // Missing key
211        assert!(view.get_float("missing").is_none());
212    }
213
214    #[test]
215    fn test_to_owned_roundtrip() {
216        let mut block = Block::new();
217        block
218            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0, 3.0]).into_dyn())
219            .unwrap();
220        block
221            .insert("id", Array1::from_vec(vec![10 as I, 20, 30]).into_dyn())
222            .unwrap();
223
224        let view = BlockView::from(&block);
225        let owned = view.to_owned();
226
227        assert_eq!(owned.nrows(), Some(3));
228        assert_eq!(owned.len(), 2);
229        assert_eq!(
230            owned
231                .get_float("x")
232                .unwrap()
233                .as_slice_memory_order()
234                .unwrap(),
235            &[1.0, 2.0, 3.0]
236        );
237        assert_eq!(
238            owned
239                .get_int("id")
240                .unwrap()
241                .as_slice_memory_order()
242                .unwrap(),
243            &[10, 20, 30]
244        );
245    }
246
247    #[test]
248    fn test_zero_copy() {
249        let mut block = Block::new();
250        block
251            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0, 3.0]).into_dyn())
252            .unwrap();
253
254        let view = BlockView::from(&block);
255        let orig_ptr = block.get_float("x").unwrap().as_ptr();
256        let view_ptr = view.get_float("x").unwrap().as_ptr();
257        assert_eq!(orig_ptr, view_ptr);
258    }
259
260    #[test]
261    fn test_empty_view() {
262        let view = BlockView::new();
263        assert!(view.is_empty());
264        assert_eq!(view.len(), 0);
265        assert_eq!(view.nrows(), None);
266    }
267
268    #[test]
269    fn test_iter_keys() {
270        let mut block = Block::new();
271        block
272            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0]).into_dyn())
273            .unwrap();
274        block
275            .insert("id", Array1::from_vec(vec![10 as I, 20]).into_dyn())
276            .unwrap();
277
278        let view = BlockView::from(&block);
279        let keys: Vec<&&str> = view.keys().collect();
280        assert_eq!(keys.len(), 2);
281    }
282
283    #[test]
284    fn test_dtype_query() {
285        let mut block = Block::new();
286        block
287            .insert("x", Array1::from_vec(vec![1.0 as F]).into_dyn())
288            .unwrap();
289
290        let view = BlockView::from(&block);
291        assert_eq!(view.dtype("x"), Some(DType::Float));
292        assert_eq!(view.dtype("missing"), None);
293    }
294}