scirs2_core/array/
record_array.rs

1//! Implementation of record arrays for structured data with named fields
2//!
3//! Record arrays are useful in scientific computing when working with:
4//! - Tabular data with different types per column
5//! - Data with named fields
6//! - Structured data that mixes different types
7//!
8//! The implementation is inspired by ``NumPy``'s `RecordArray`.
9
10use ndarray::{Array, Ix1};
11use std::collections::HashMap;
12use std::fmt;
13
14// Use the array error from masked_array.rs
15use super::masked_array::ArrayError;
16
17/// Enum to hold different types of field values
18#[derive(Clone, Debug)]
19pub enum FieldValue {
20    Bool(bool),
21    Int8(i8),
22    Int16(i16),
23    Int32(i32),
24    Int64(i64),
25    UInt8(u8),
26    UInt16(u16),
27    UInt32(u32),
28    UInt64(u64),
29    Float32(f32),
30    Float64(f64),
31    String(String),
32    // Could add more types as needed
33}
34
35// Implement conversions from common Rust types to FieldValue
36impl From<bool> for FieldValue {
37    fn from(value: bool) -> Self {
38        Self::Bool(value)
39    }
40}
41
42impl From<i8> for FieldValue {
43    fn from(value: i8) -> Self {
44        Self::Int8(value)
45    }
46}
47
48impl From<i16> for FieldValue {
49    fn from(value: i16) -> Self {
50        Self::Int16(value)
51    }
52}
53
54impl From<i32> for FieldValue {
55    fn from(value: i32) -> Self {
56        Self::Int32(value)
57    }
58}
59
60impl From<i64> for FieldValue {
61    fn from(value: i64) -> Self {
62        Self::Int64(value)
63    }
64}
65
66impl From<u8> for FieldValue {
67    fn from(value: u8) -> Self {
68        Self::UInt8(value)
69    }
70}
71
72impl From<u16> for FieldValue {
73    fn from(value: u16) -> Self {
74        Self::UInt16(value)
75    }
76}
77
78impl From<u32> for FieldValue {
79    fn from(value: u32) -> Self {
80        Self::UInt32(value)
81    }
82}
83
84impl From<u64> for FieldValue {
85    fn from(value: u64) -> Self {
86        Self::UInt64(value)
87    }
88}
89
90impl From<f32> for FieldValue {
91    fn from(value: f32) -> Self {
92        Self::Float32(value)
93    }
94}
95
96impl From<f64> for FieldValue {
97    fn from(value: f64) -> Self {
98        Self::Float64(value)
99    }
100}
101
102impl From<&str> for FieldValue {
103    fn from(value: &str) -> Self {
104        Self::String(value.to_string())
105    }
106}
107
108impl From<String> for FieldValue {
109    fn from(value: String) -> Self {
110        Self::String(value)
111    }
112}
113
114impl fmt::Display for FieldValue {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Self::Bool(v) => write!(f, "{v}"),
118            Self::Int8(v) => write!(f, "{v}"),
119            Self::Int16(v) => write!(f, "{v}"),
120            Self::Int32(v) => write!(f, "{v}"),
121            Self::Int64(v) => write!(f, "{v}"),
122            Self::UInt8(v) => write!(f, "{v}"),
123            Self::UInt16(v) => write!(f, "{v}"),
124            Self::UInt32(v) => write!(f, "{v}"),
125            Self::UInt64(v) => write!(f, "{v}"),
126            Self::Float32(v) => write!(f, "{v}"),
127            Self::Float64(v) => write!(f, "{v}"),
128            Self::String(v) => write!(f, "\"{v}\""),
129        }
130    }
131}
132
133/// Represents a single record (row) in a `RecordArray`
134#[derive(Clone, Debug, Default)]
135pub struct Record {
136    /// Map of field names to values
137    fields: HashMap<String, FieldValue>,
138
139    /// Field names in order
140    names: Vec<String>,
141}
142
143impl Record {
144    /// Create a new empty record
145    #[must_use]
146    pub fn new() -> Self {
147        Self::default()
148    }
149
150    /// Add a field to the record
151    pub fn add_field(&mut self, name: &str, value: FieldValue) {
152        if !self.fields.contains_key(name) {
153            self.names.push(name.to_string());
154        }
155        self.fields.insert(name.to_string(), value);
156    }
157
158    /// Get a field value by name
159    #[must_use]
160    pub fn get_field(&self, name: &str) -> Option<&FieldValue> {
161        self.fields.get(name)
162    }
163
164    /// Get a mutable reference to a field value by name
165    pub fn get_field_mut(&mut self, name: &str) -> Option<&mut FieldValue> {
166        self.fields.get_mut(name)
167    }
168
169    /// Get the number of fields
170    #[must_use]
171    pub fn num_fields(&self) -> usize {
172        self.fields.len()
173    }
174
175    /// Get the field names
176    #[must_use]
177    #[allow(clippy::missing_const_for_fn)]
178    pub fn names(&self) -> &[String] {
179        &self.names
180    }
181    /// Pretty print the record
182    #[must_use]
183    pub fn pprint(&self) -> String {
184        let mut result = String::new();
185
186        let max_name_len = self
187            .names
188            .iter()
189            .map(std::string::String::len)
190            .max()
191            .unwrap_or(0);
192
193        for name in &self.names {
194            if let Some(value) = self.fields.get(name) {
195                use std::fmt::Write;
196                let _ = writeln!(&mut result, "{name:<max_name_len$}: {value}");
197            }
198        }
199
200        result
201    }
202}
203
204impl fmt::Display for Record {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(
207            f,
208            "({})",
209            self.names
210                .iter()
211                .filter_map(|name| self.fields.get(name).map(|v| format!("{name}: {v}")))
212                .collect::<Vec<_>>()
213                .join(", ")
214        )
215    }
216}
217
218/// A structured array with named fields
219#[derive(Clone, Debug)]
220pub struct RecordArray {
221    /// The array of records
222    pub records: Vec<Record>,
223
224    /// The names of the fields (columns)
225    pub names: Vec<String>,
226
227    /// Optional titles (aliases) for fields
228    pub field_titles: HashMap<String, String>,
229
230    /// Maps field names to their index in `names`
231    field_indices: HashMap<String, usize>,
232
233    /// The shape of the array
234    shape: Vec<usize>,
235
236    /// Whether fields can be accessed by attribute
237    allow_field_attributes: bool,
238}
239
240impl RecordArray {
241    /// Create a new `RecordArray` from a vector of records
242    ///
243    /// # Errors
244    /// Returns `ArrayError::ValueError` if records are empty or have inconsistent field structures.
245    pub fn new(records: Vec<Record>) -> Result<Self, ArrayError> {
246        if records.is_empty() {
247            return Err(ArrayError::ValueError(
248                "Records cannot be empty".to_string(),
249            ));
250        }
251
252        // Get field names from the first record
253        let names = records[0].names().to_vec();
254
255        // Verify all records have the same fields
256        for (i, record) in records.iter().enumerate().skip(1) {
257            let record_fields = record.names();
258            if record_fields.len() != names.len() {
259                return Err(ArrayError::ValueError(format!(
260                    "Record {i} has {} fields, but expected {}",
261                    record_fields.len(),
262                    names.len()
263                )));
264            }
265
266            for name in &names {
267                if !record_fields.contains(name) {
268                    return Err(ArrayError::ValueError(format!(
269                        "Record {i} is missing field '{name}'"
270                    )));
271                }
272            }
273        }
274
275        // Create field index map
276        let mut field_indices = HashMap::new();
277        for name in names.iter() {
278            field_indices.insert(name.clone(), 0);
279        }
280
281        // Store the length
282        let len = records.len();
283
284        Ok(Self {
285            records,
286            names,
287            field_titles: HashMap::new(),
288            field_indices,
289            shape: vec![len],
290            allow_field_attributes: true,
291        })
292    }
293
294    /// Create a new `RecordArray` with custom field titles
295    ///
296    /// # Errors
297    /// Returns `ArrayError::ValueError` if records are invalid or titles reference non-existent fields.
298    pub fn with_titles(
299        records: Vec<Record>,
300        titles: HashMap<String, String>,
301    ) -> Result<Self, ArrayError> {
302        let mut record_array = Self::new(records)?;
303
304        // Validate titles
305        for name in titles.keys() {
306            if !record_array.field_indices.contains_key(name) {
307                return Err(ArrayError::ValueError(format!(
308                    "Cannot add title for non-existent field '{name}'"
309                )));
310            }
311        }
312
313        record_array.field_titles = titles;
314        Ok(record_array)
315    }
316
317    /// Enable or disable attribute-style field access
318    pub const fn set_allow_field_attributes(&mut self, allow: bool) {
319        self.allow_field_attributes = allow;
320    }
321
322    /// Get whether attribute-style field access is allowed
323    #[must_use]
324    pub const fn allow_field_attributes(&self) -> bool {
325        self.allow_field_attributes
326    }
327
328    /// Get the shape of the array
329    #[must_use]
330    #[allow(clippy::missing_const_for_fn)]
331    pub fn shape(&self) -> &[usize] {
332        &self.shape
333    }
334
335    /// Get all values for a specific field across all records
336    pub fn field_values(&self, name: &str) -> Result<Vec<FieldValue>, ArrayError> {
337        if !self.names.contains(&name.to_string()) {
338            return Err(ArrayError::ValueError(format!(
339                "Field '{}' not found",
340                name
341            )));
342        }
343
344        let mut values = Vec::with_capacity(self.records.len());
345        for record in &self.records {
346            if let Some(value) = record.get_field(name) {
347                values.push(value.clone());
348            } else {
349                return Err(ArrayError::ValueError(format!(
350                    "Field '{}' missing in some records",
351                    name
352                )));
353            }
354        }
355
356        Ok(values)
357    }
358
359    /// Get the number of records
360    #[must_use]
361    pub fn numrecords(&self) -> usize {
362        self.records.len()
363    }
364
365    /// Get a reference to a record by index
366    #[must_use]
367    pub fn get_record(&self, index: usize) -> Option<&Record> {
368        self.records.get(index)
369    }
370
371    /// Get a mutable reference to a record by index
372    pub fn get_record_mut(&mut self, index: usize) -> Option<&mut Record> {
373        self.records.get_mut(index)
374    }
375
376    /// Get a field values as a vector
377    ///
378    /// # Errors
379    /// Returns `ArrayError::ValueError` if the field name is not found.
380    ///
381    /// # Panics
382    /// Panics if a record doesn't have the field that should exist based on validation.
383    pub fn get_field(&self, name: &str) -> Result<Vec<FieldValue>, ArrayError> {
384        if !self.field_indices.contains_key(name) {
385            return Err(ArrayError::ValueError(format!("Field '{name}' not found")));
386        }
387
388        let values = self
389            .records
390            .iter()
391            .map(|record| {
392                record
393                    .get_field(name)
394                    .expect("Field should exist based on validation")
395                    .clone()
396            })
397            .collect();
398
399        Ok(values)
400    }
401
402    /// Get a field as an array of f64 values
403    ///
404    /// # Errors
405    /// Returns `ArrayError::ValueError` if the field name is not found.
406    #[allow(clippy::cast_precision_loss)]
407    pub fn field_as_f64_array(&self, name: &str) -> Result<Array<f64, Ix1>, ArrayError> {
408        let values = self.field_values(name)?;
409
410        let mut result = Array::zeros(self.records.len());
411
412        for (i, value) in values.iter().enumerate() {
413            let val = match value {
414                FieldValue::Bool(v) => {
415                    if *v {
416                        1.0
417                    } else {
418                        0.0
419                    }
420                }
421                FieldValue::Int8(v) => f64::from(*v),
422                FieldValue::Int16(v) => f64::from(*v),
423                FieldValue::Int32(v) => f64::from(*v),
424                FieldValue::Int64(v) => *v as f64,
425                FieldValue::UInt8(v) => f64::from(*v),
426                FieldValue::UInt16(v) => f64::from(*v),
427                FieldValue::UInt32(v) => f64::from(*v),
428                FieldValue::UInt64(v) => *v as f64,
429                FieldValue::Float32(v) => f64::from(*v),
430                FieldValue::Float64(v) => *v,
431                FieldValue::String(_) => {
432                    return Err(ArrayError::ValueError(format!(
433                        "Cannot convert field '{name}' of type String to f64"
434                    )))
435                }
436            };
437
438            result[i] = val;
439        }
440
441        Ok(result)
442    }
443
444    /// Get a field as an array of i64 values
445    ///
446    /// # Errors
447    /// Returns `ArrayError::ValueError` if the field name is not found or contains non-convertible types.
448    #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
449    pub fn field_as_i64_array(&self, name: &str) -> Result<Array<i64, Ix1>, ArrayError> {
450        let values = self.field_values(name)?;
451
452        let mut result = Array::zeros(self.records.len());
453
454        for (i, value) in values.iter().enumerate() {
455            let val = match value {
456                FieldValue::Bool(v) => i64::from(*v),
457                FieldValue::Int8(v) => i64::from(*v),
458                FieldValue::Int16(v) => i64::from(*v),
459                FieldValue::Int32(v) => i64::from(*v),
460                FieldValue::Int64(v) => *v,
461                FieldValue::UInt8(v) => i64::from(*v),
462                FieldValue::UInt16(v) => i64::from(*v),
463                FieldValue::UInt32(v) => i64::from(*v),
464                FieldValue::UInt64(v) => {
465                    if *v > i64::MAX as u64 {
466                        return Err(ArrayError::ValueError(format!(
467                            "Value {v} in field '{name}' is too large for i64"
468                        )));
469                    }
470                    *v as i64
471                }
472                FieldValue::Float32(v) => *v as i64,
473                FieldValue::Float64(v) => *v as i64,
474                FieldValue::String(_) => {
475                    return Err(ArrayError::ValueError(format!(
476                        "Cannot convert field '{name}' of type String to i64"
477                    )))
478                }
479            };
480
481            result[i] = val;
482        }
483
484        Ok(result)
485    }
486
487    /// Get a field as an array of String values
488    ///
489    /// # Errors
490    /// Returns `ArrayError::ValueError` if the field name is not found.
491    pub fn name_4(&self, name: &str) -> Result<Vec<String>, ArrayError> {
492        let values = self.field_values(name)?;
493
494        let mut result = Vec::with_capacity(self.records.len());
495
496        for value in values {
497            let val = match value {
498                FieldValue::Bool(v) => v.to_string(),
499                FieldValue::Int8(v) => v.to_string(),
500                FieldValue::Int16(v) => v.to_string(),
501                FieldValue::Int32(v) => v.to_string(),
502                FieldValue::Int64(v) => v.to_string(),
503                FieldValue::UInt8(v) => v.to_string(),
504                FieldValue::UInt16(v) => v.to_string(),
505                FieldValue::UInt32(v) => v.to_string(),
506                FieldValue::UInt64(v) => v.to_string(),
507                FieldValue::Float32(v) => v.to_string(),
508                FieldValue::Float64(v) => v.to_string(),
509                FieldValue::String(v) => v,
510            };
511
512            result.push(val);
513        }
514
515        Ok(result)
516    }
517
518    /// Get a field by its title (alias) rather than its name
519    ///
520    /// # Errors
521    /// Returns `ArrayError::ValueError` if the title is not found.
522    pub fn get_field_by_title(&self, title: &str) -> Result<Vec<FieldValue>, ArrayError> {
523        // Find the field name corresponding to the title
524        let name = self
525            .field_titles
526            .iter()
527            .find_map(|(name, t)| if t == title { Some(name) } else { None })
528            .ok_or_else(|| ArrayError::ValueError(format!("Title '{title}' not found")))?;
529
530        // Get the field values by name
531        self.field_values(name)
532    }
533
534    /// Set a field value for a record
535    ///
536    /// # Errors
537    /// Returns `ArrayError::ValueError` if the field name is not found or record index is out of bounds.
538    pub fn name_5(
539        &mut self,
540        name: &str,
541        record_idx: usize,
542        value: FieldValue,
543    ) -> Result<(), ArrayError> {
544        // First check if field exists
545        if !self.field_indices.contains_key(name) {
546            return Err(ArrayError::ValueError(format!("Field '{name}' not found")));
547        }
548
549        // Then get record and set the field
550        let record = self.get_record_mut(record_idx).ok_or_else(|| {
551            ArrayError::ValueError(format!("Record index {record_idx} out of bounds"))
552        })?;
553
554        record.add_field(name, value);
555        Ok(())
556    }
557
558    /// Adds a new field to all records
559    ///
560    /// # Errors
561    /// Returns `ArrayError::ValueError` if the field already exists or the number of values doesn't match the number of records.
562    pub fn name_6(&mut self, name: &str, values: Vec<FieldValue>) -> Result<(), ArrayError> {
563        // Check if field already exists
564        if self.field_indices.contains_key(name) {
565            return Err(ArrayError::ValueError(format!(
566                "Field '{name}' already exists"
567            )));
568        }
569
570        // Check if the number of values matches the number of records
571        if values.len() != self.records.len() {
572            return Err(ArrayError::ValueError(format!(
573                "Number of values ({}) doesn't match number of records ({})",
574                values.len(),
575                self.records.len()
576            )));
577        }
578
579        // Add field to each record
580        for (i, record) in self.records.iter_mut().enumerate() {
581            record.add_field(name, values[i].clone());
582        }
583
584        // Update field names and indices
585        let new_index = self.names.len();
586        self.names.push(name.to_string());
587        self.field_indices.insert(name.to_string(), new_index);
588
589        Ok(())
590    }
591
592    /// Removes a field from all records
593    ///
594    /// # Errors
595    /// Returns `ArrayError::ValueError` if the field name is not found.
596    pub fn name_7(&mut self, name: &str) -> Result<(), ArrayError> {
597        // Check if field exists
598        if !self.field_indices.contains_key(name) {
599            return Err(ArrayError::ValueError(format!("Field '{name}' not found")));
600        }
601
602        // Remove field from each record
603        for record in &mut self.records {
604            // Create a new vector of field names without the removed field
605            let new_names: Vec<String> = record
606                .names
607                .iter()
608                .filter(|fieldname| *fieldname != name)
609                .cloned()
610                .collect();
611
612            // Remove the field from the hashmap
613            record.fields.remove(name);
614
615            // Update field names
616            record.names = new_names;
617        }
618
619        // Get the index of the field to remove
620        let index_to_remove = self.field_indices[name];
621
622        // Remove field from names
623        self.names.remove(index_to_remove);
624
625        // Remove field from field_titles if present
626        self.field_titles.remove(name);
627
628        // Rebuild field_indices map
629        self.field_indices.clear();
630        for (i, fieldname) in self.names.iter().enumerate() {
631            self.field_indices.insert(fieldname.clone(), i);
632        }
633
634        Ok(())
635    }
636
637    /// Rename a field
638    ///
639    /// # Errors
640    /// Returns `ArrayError::ValueError` if old field is not found or new field already exists.
641    pub fn name_8(&mut self, old_name: &str, newname: &str) -> Result<(), ArrayError> {
642        // Check if old field exists
643        if !self.field_indices.contains_key(old_name) {
644            return Err(ArrayError::ValueError(format!(
645                "Field '{old_name}' not found"
646            )));
647        }
648
649        // Check if new field already exists
650        if self.field_indices.contains_key(newname) {
651            return Err(ArrayError::ValueError(format!(
652                "Field '{newname}' already exists"
653            )));
654        }
655
656        // Rename field in each record
657        for record in &mut self.records {
658            // Get the value for the field
659            if let Some(value) = record.fields.remove(old_name) {
660                // Add field with new name
661                record.add_field(newname, value);
662
663                // Update names
664                let old_index = record
665                    .names
666                    .iter()
667                    .position(|name| name == old_name)
668                    .expect("Failed to create RecordArray in test");
669                record.names[old_index] = newname.to_string();
670            }
671        }
672
673        // Update names in RecordArray
674        let old_index = self.field_indices[old_name];
675        self.names[old_index] = newname.to_string();
676
677        // Update field_indices
678        self.field_indices.remove(old_name);
679        self.field_indices.insert(newname.to_string(), old_index);
680
681        // Update field_titles if the old name had a title
682        if let Some(title) = self.field_titles.remove(old_name) {
683            self.field_titles.insert(newname.to_string(), title);
684        }
685
686        Ok(())
687    }
688
689    /// Create a view of the record array with a subset of records
690    ///
691    /// # Errors
692    /// Returns `ArrayError::ValueError` if any index is out of bounds.
693    pub fn view(&self, indices: &[usize]) -> Result<Self, ArrayError> {
694        let mut newrecords = Vec::with_capacity(indices.len());
695
696        // Collect records at specified indices
697        for &idx in indices {
698            if idx >= self.records.len() {
699                return Err(ArrayError::ValueError(format!(
700                    "Index {idx} out of bounds for record array of length {}",
701                    self.records.len()
702                )));
703            }
704
705            newrecords.push(self.records[idx].clone());
706        }
707
708        // Create a new RecordArray with the selected records
709        let result = Self {
710            records: newrecords,
711            names: self.names.clone(),
712            field_titles: self.field_titles.clone(),
713            field_indices: self.field_indices.clone(),
714            shape: vec![indices.len()],
715            allow_field_attributes: self.allow_field_attributes,
716        };
717
718        Ok(result)
719    }
720
721    /// Filter the record array by a condition on a field
722    ///
723    /// # Errors
724    /// Returns `ArrayError::ValueError` if the field name is not found.
725    pub fn filter<F>(&self, name: &str, condition: F) -> Result<Self, ArrayError>
726    where
727        F: Fn(&FieldValue) -> bool,
728    {
729        // Check if field exists
730        if !self.field_indices.contains_key(name) {
731            return Err(ArrayError::ValueError(format!("Field '{name}' not found")));
732        }
733
734        // Get all values for the field
735        let values = self.field_values(name)?;
736
737        // Find indices where the condition is true
738        let mut indices = Vec::new();
739        for (i, value) in values.iter().enumerate() {
740            if condition(value) {
741                indices.push(i);
742            }
743        }
744
745        // Create a view with these indices
746        self.view(&indices)
747    }
748
749    /// Merge two record arrays with compatible fields
750    ///
751    /// # Errors
752    /// Returns `ArrayError::ValueError` if the arrays have incompatible field structures.
753    pub fn merge(&self, other: &Self) -> Result<Self, ArrayError> {
754        // Check field compatibility
755        if self.names.len() != other.names.len() {
756            return Err(ArrayError::ValueError(format!(
757                "Cannot merge record arrays with different number of fields ({} vs {})",
758                self.names.len(),
759                other.names.len()
760            )));
761        }
762
763        for name in &self.names {
764            if !other.field_indices.contains_key(name) {
765                return Err(ArrayError::ValueError(format!(
766                    "Field '{name}' not found in the second record array"
767                )));
768            }
769        }
770
771        // Combine records
772        let mut newrecords = Vec::with_capacity(self.records.len() + other.records.len());
773        newrecords.extend_from_slice(&self.records);
774        newrecords.extend_from_slice(&other.records);
775
776        // Create merged RecordArray
777        let result = Self {
778            records: newrecords,
779            names: self.names.clone(),
780            field_titles: self.field_titles.clone(),
781            field_indices: self.field_indices.clone(),
782            shape: vec![self.records.len() + other.records.len()],
783            allow_field_attributes: self.allow_field_attributes,
784        };
785
786        Ok(result)
787    }
788}
789
790/// Helper function to compare field values
791#[allow(dead_code)]
792fn compare_field_values(a: &FieldValue, b: &FieldValue) -> Option<std::cmp::Ordering> {
793    match (a, b) {
794        // Compare same types directly
795        (FieldValue::Bool(a), FieldValue::Bool(b)) => Some(a.cmp(b)),
796        (FieldValue::Int8(a), FieldValue::Int8(b)) => Some(a.cmp(b)),
797        (FieldValue::Int16(a), FieldValue::Int16(b)) => Some(a.cmp(b)),
798        (FieldValue::Int32(a), FieldValue::Int32(b)) => Some(a.cmp(b)),
799        (FieldValue::Int64(a), FieldValue::Int64(b)) => Some(a.cmp(b)),
800        (FieldValue::UInt8(a), FieldValue::UInt8(b)) => Some(a.cmp(b)),
801        (FieldValue::UInt16(a), FieldValue::UInt16(b)) => Some(a.cmp(b)),
802        (FieldValue::UInt32(a), FieldValue::UInt32(b)) => Some(a.cmp(b)),
803        (FieldValue::UInt64(a), FieldValue::UInt64(b)) => Some(a.cmp(b)),
804        (FieldValue::Float32(a), FieldValue::Float32(b)) => a.partial_cmp(b),
805        (FieldValue::Float64(a), FieldValue::Float64(b)) => a.partial_cmp(b),
806        (FieldValue::String(a), FieldValue::String(b)) => Some(a.cmp(b)),
807
808        // Mixed numeric comparisons - convert to f64 when possible
809        (FieldValue::Int8(a), FieldValue::Float32(b)) => (*a as f32).partial_cmp(b),
810        (FieldValue::Int8(a), FieldValue::Float64(b)) => (*a as f64).partial_cmp(b),
811        (FieldValue::Float32(a), FieldValue::Int8(b)) => a.partial_cmp(&(*b as f32)),
812        (FieldValue::Float64(a), FieldValue::Int8(b)) => a.partial_cmp(&(*b as f64)),
813
814        // For other mixed types, compare by type name as a fallback
815        _ => {
816            let type_a = std::any::type_name::<FieldValue>();
817            let type_b = std::any::type_name::<FieldValue>();
818            Some(type_a.cmp(type_b))
819        }
820    }
821}
822
823impl fmt::Display for RecordArray {
824    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825        writeln!(f, "RecordArray(")?;
826
827        let maxrecords_to_show = 10;
828        let numrecords = self.records.len();
829        let show_all = numrecords <= maxrecords_to_show;
830
831        let records_to_show = if show_all {
832            &self.records[..]
833        } else {
834            let half = maxrecords_to_show / 2;
835            &self.records[..half]
836        };
837
838        for record in records_to_show {
839            writeln!(f, "  {record},")?;
840        }
841
842        if !show_all {
843            writeln!(f, "  ...")?;
844
845            let half = maxrecords_to_show / 2;
846            let remaining = &self.records[numrecords - half..];
847
848            for record in remaining {
849                writeln!(f, "  {record},")?;
850            }
851        }
852
853        write!(f, ")")
854    }
855}
856
857/// Create a `RecordArray` from arrays of the same length
858///
859/// # Errors
860/// Returns `ArrayError::ValueError` if field names don't match arrays count or arrays have different lengths.
861#[allow(dead_code)]
862pub fn from_arrays(names: &[&str], arrays: &[Vec<FieldValue>]) -> Result<RecordArray, ArrayError> {
863    if names.len() != arrays.len() {
864        return Err(ArrayError::ValueError(format!(
865            "Number of field _names ({}) must match number of arrays ({})",
866            names.len(),
867            arrays.len()
868        )));
869    }
870
871    if arrays.is_empty() {
872        return Err(ArrayError::ValueError("No arrays provided".to_string()));
873    }
874
875    let numrecords = arrays[0].len();
876
877    // Check all arrays have the same length
878    for (i, array) in arrays.iter().enumerate().skip(1) {
879        if array.len() != numrecords {
880            return Err(ArrayError::ValueError(format!(
881                "Array {i} has length {}, but expected {numrecords}",
882                array.len()
883            )));
884        }
885    }
886
887    // Create records
888    let mut records = Vec::with_capacity(numrecords);
889
890    for i in 0..numrecords {
891        let mut record = Record::new();
892
893        for (name, array) in names.iter().zip(arrays.iter()) {
894            record.add_field(name, array[i].clone());
895        }
896
897        records.push(record);
898    }
899
900    RecordArray::new(records)
901}
902
903/// Create a `RecordArray` from arrays with different numeric types
904///
905/// # Errors
906/// Returns `ArrayError::ValueError` if field names don't match 3 arrays or arrays have different lengths.
907#[allow(dead_code)]
908pub fn record_array_from_typed_arrays<A, B, C>(
909    names: &[&str],
910    arrays: (&[A], &[B], &[C]),
911) -> Result<RecordArray, ArrayError>
912where
913    A: Clone + Into<FieldValue>,
914    B: Clone + Into<FieldValue>,
915    C: Clone + Into<FieldValue>,
916{
917    if names.len() != 3 {
918        return Err(ArrayError::ValueError(format!(
919            "Number of field _names ({}) must match number of arrays (3)",
920            names.len()
921        )));
922    }
923
924    let a_len = arrays.0.len();
925    let b_len = arrays.1.len();
926    let c_len = arrays.2.len();
927
928    // Check all arrays have the same length
929    if a_len != b_len || a_len != c_len {
930        return Err(ArrayError::ValueError(format!(
931            "Arrays have different lengths: {a_len}, {b_len}, {c_len}"
932        )));
933    }
934
935    // Create records
936    let mut records = Vec::with_capacity(a_len);
937
938    for i in 0..a_len {
939        let mut record = Record::new();
940
941        record.add_field(names[0], arrays.0[i].clone().into());
942        record.add_field(names[1], arrays.1[i].clone().into());
943        record.add_field(names[2], arrays.2[i].clone().into());
944
945        records.push(record);
946    }
947
948    RecordArray::new(records)
949}
950
951/// Create a `RecordArray` from a sequence of tuples
952///
953/// # Errors
954/// Returns `ArrayError::ValueError` if field names don't match 3 tuple elements.
955#[allow(dead_code)]
956pub fn record_array_fromrecords<A, B, C>(
957    names: &[&str],
958    tuples: &[(A, B, C)],
959) -> Result<RecordArray, ArrayError>
960where
961    A: Clone + Into<FieldValue>,
962    B: Clone + Into<FieldValue>,
963    C: Clone + Into<FieldValue>,
964{
965    if names.len() != 3 {
966        return Err(ArrayError::ValueError(format!(
967            "Number of field _names ({}) must match number of tuple elements (3)",
968            names.len()
969        )));
970    }
971
972    // Create records
973    let mut records = Vec::with_capacity(tuples.len());
974
975    for tuple in tuples {
976        let mut record = Record::new();
977
978        record.add_field(names[0], tuple.0.clone().into());
979        record.add_field(names[1], tuple.1.clone().into());
980        record.add_field(names[2], tuple.2.clone().into());
981
982        records.push(record);
983    }
984
985    RecordArray::new(records)
986}