Skip to main content

xls_rs/
string_utils.rs

1//! String utilities for performance optimization
2//!
3//! This module provides helper functions and traits for efficient string operations
4//! including capacity pre-allocation and joining operations.
5
6/// Pre-allocate a String with estimated capacity
7///
8/// This helper avoids reallocations by pre-allocating based on estimated size.
9/// Use this when building strings incrementally.
10///
11/// # Arguments
12/// * `estimated_size` - Estimated number of characters needed
13///
14/// # Returns
15/// A String with pre-allocated capacity
16#[inline]
17pub fn string_with_capacity(estimated_size: usize) -> String {
18    String::with_capacity(estimated_size)
19}
20
21/// Join strings with a separator, pre-allocating based on estimated total size
22///
23/// More efficient than standard join when you know the approximate size upfront.
24///
25/// # Arguments
26/// * `parts` - Slice of string references to join
27/// * `separator` - Separator string
28/// * `estimated_part_size` - Average estimated size of each part
29///
30/// # Returns
31/// A new String with all parts joined by separator
32pub fn join_with_capacity(
33    parts: &[&str],
34    separator: &str,
35    estimated_part_size: usize,
36) -> String {
37    let total_capacity = parts.len() * estimated_part_size + (parts.len() * separator.len());
38    let mut result = String::with_capacity(total_capacity);
39
40    for (i, part) in parts.iter().enumerate() {
41        if i > 0 {
42            result.push_str(separator);
43        }
44        result.push_str(part);
45    }
46
47    result
48}
49
50/// Estimate CSV row string length based on number of columns
51///
52/// # Arguments
53/// * `num_cols` - Number of columns in the row
54///
55/// # Returns
56/// Estimated string capacity for a CSV row
57#[inline]
58pub const fn estimate_csv_row_capacity(num_cols: usize) -> usize {
59    // Assume average cell length of 10 chars + comma separator
60    num_cols * 11
61}
62
63/// Estimate JSON array string length
64///
65/// # Arguments
66/// * `num_rows` - Number of rows
67/// * `num_cols` - Number of columns per row
68/// * `avg_cell_size` - Average size of cell values
69///
70/// # Returns
71/// Estimated string capacity for JSON array
72#[inline]
73pub const fn estimate_json_array_capacity(
74    num_rows: usize,
75    num_cols: usize,
76    avg_cell_size: usize,
77) -> usize {
78    // Each cell: "value", (avg_cell_size + 3 quotes)
79    // Row: [cell,cell,] (3 extra chars)
80    // Array: [rows...] (2 brackets)
81    let cell_capacity = (avg_cell_size + 3) * num_cols;
82    let row_capacity = cell_capacity + 3;
83    row_capacity * num_rows + 2
84}
85
86/// Extension trait for efficient string building
87pub trait StringBuilder {
88    /// Build a String from an iterator with pre-allocated capacity
89    fn from_iter_with_capacity<I>(iter: I, estimated_capacity: usize) -> Self
90    where
91        I: IntoIterator<Item = String>;
92}
93
94impl StringBuilder for String {
95    fn from_iter_with_capacity<I>(iter: I, estimated_capacity: usize) -> Self
96    where
97        I: IntoIterator<Item = String>,
98    {
99        let mut result = String::with_capacity(estimated_capacity);
100        for s in iter {
101            result.push_str(&s);
102        }
103        result
104    }
105}
106
107/// Join cell references efficiently (e.g., ["A", "1"] -> "A1")
108#[inline]
109pub fn join_cell_reference(col: &str, row: usize) -> String {
110    let mut result = String::with_capacity(col.len() + 4);
111    result.push_str(col);
112    result.push_str(&row.to_string());
113    result
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_join_with_capacity() {
122        let parts = vec!["hello", "world", "test"];
123        let result = join_with_capacity(&parts, ", ", 5);
124        assert_eq!(result, "hello, world, test");
125    }
126
127    #[test]
128    fn test_join_cell_reference() {
129        assert_eq!(join_cell_reference("A", 1), "A1");
130        assert_eq!(join_cell_reference("AB", 123), "AB123");
131    }
132
133    #[test]
134    fn test_estimates() {
135        assert_eq!(estimate_csv_row_capacity(5), 55);
136        assert_eq!(estimate_json_array_capacity(10, 3, 10), (10 * 3 * 13) + 32);
137    }
138
139    #[test]
140    fn test_string_builder() {
141        let parts = vec!["a".to_string(), "b".to_string(), "c".to_string()];
142        let result = String::from_iter_with_capacity(parts.into_iter(), 10);
143        assert_eq!(result, "abc");
144    }
145}