matrix_operations/
csv.rs

1//! This module will perform the CSV parsing and writing.
2//!
3//! In tests, the CSV files are located in the resources folder.
4//! The CSV `test_read.csv` looks like this:
5//!
6//! ```text
7//! col1,col2
8//! 1,2
9//! 3,4
10//! 5,6
11//! ```
12//!
13//! # Usage
14//!
15//! ```
16//! use matrix_operations::csv::{load_matrix_from_csv, write_matrix_to_csv};
17//!
18//! let path = "resources/test_read.csv";
19//! let separator = ",";
20//!
21//! let mut matrix = load_matrix_from_csv::<u32>(path, separator).unwrap();
22//!
23//! matrix[0][0] = 10;
24//! matrix[0][1] = 20;
25//!
26//! matrix.add_row(matrix.shape().0).unwrap();
27//!
28//! write_matrix_to_csv(&matrix, "resources/test_write.csv", separator).unwrap();
29//! ```
30
31use std::error::Error;
32use std::fmt::Display;
33use std::fs::File;
34use std::io::{BufRead, BufReader, Write};
35use std::str::FromStr;
36use crate::Matrix;
37
38/// Load a CSV file into a Matrix.
39///
40/// # Examples
41///
42/// ```
43/// use matrix_operations::csv::load_matrix_from_csv;
44///
45/// let path = "resources/test_read.csv";
46/// let separator = ",";
47///
48/// let matrix = load_matrix_from_csv::<f32>(path, separator).unwrap();
49///
50/// assert_eq!(matrix.shape(), (3, 2));
51/// assert_eq!(matrix[0], vec![1.0, 2.0]);
52/// assert_eq!(matrix[1], vec![3.0, 4.0]);
53/// assert_eq!(matrix[2], vec![5.0, 6.0]);
54/// ```
55///
56/// # Panics
57///
58/// This function will panic if the file cannot be opened or if the separator is not found.
59///
60/// ```should_panic
61/// use matrix_operations::csv::load_matrix_from_csv;
62///
63/// let path = "../resources/test_does_not_exist.csv";
64/// let separator = ",";
65///
66/// let matrix = load_matrix_from_csv::<f32>(path, separator).unwrap();
67/// ```
68pub fn load_matrix_from_csv<T: Default + Copy + FromStr>(path: &str, separator: &str) -> Result<Matrix<T>, Box<dyn Error>> where T: Default + Copy + FromStr, <T as FromStr>::Err: 'static, <T as FromStr>::Err: Error {
69    let file = File::open(path)?;
70    let reader = BufReader::new(file);
71
72    let data: Vec<T> = Vec::new();
73    let mut matrix = Matrix::new(data, (0, 0))?;
74
75    let mut header = true;
76    for line in reader.lines() {
77        if header {
78            header = false;
79            continue;
80        }
81        let line_tmp = line?;
82        let split: Vec<&str> = line_tmp.split(separator).collect();
83        let mut row: Vec<T> = Vec::new();
84        for i in 0..split.len() {
85            let value = split[i].parse::<T>()?;
86            row.push(value);
87        }
88        matrix.add_row_from_vec(matrix.shape.0, row)?;
89    }
90
91    Ok(matrix)
92}
93
94/// Load a CSV file and put columns data to a Matrix.
95///
96/// # Examples
97///
98/// ```
99/// use matrix_operations::csv::load_matrix_from_csv_columns;
100///
101/// let path = "resources/test_read.csv";
102/// let separator = ",";
103/// let columns = vec!["col1".to_string()];
104///
105/// let matrix = load_matrix_from_csv_columns::<f32>(path, separator, columns).unwrap();
106///
107/// assert_eq!(matrix.shape(), (3, 1));
108/// assert_eq!(matrix[0], vec![1.0]);
109/// assert_eq!(matrix[1], vec![3.0]);
110/// assert_eq!(matrix[2], vec![5.0]);
111/// ```
112///
113/// If columns are not found, the function will ignore them.
114///
115/// ```
116/// use matrix_operations::csv::load_matrix_from_csv_columns;
117///
118/// let path = "resources/test_read.csv";
119/// let separator = ",";
120/// let columns = vec!["col1".to_string(), "col3".to_string()];
121///
122/// let matrix = load_matrix_from_csv_columns::<f32>(path, separator, columns).unwrap();
123///
124/// assert_eq!(matrix.shape(), (3, 1));
125/// assert_eq!(matrix[0], vec![1.0]);
126/// assert_eq!(matrix[1], vec![3.0]);
127/// assert_eq!(matrix[2], vec![5.0]);
128/// ```
129///
130/// # Panics
131///
132/// This function will panic if the file cannot be opened or if the separator is not found.
133///
134/// ```should_panic
135/// use matrix_operations::csv::load_matrix_from_csv_columns;
136///
137/// let path = "../resources/test_does_not_exist.csv";
138/// let separator = ",";
139/// let columns = vec!["col1".to_string()];
140///
141/// let matrix = load_matrix_from_csv_columns::<f32>(path, separator, columns).unwrap();
142/// ```
143pub fn load_matrix_from_csv_columns<T: Default + Copy + FromStr>(path: &str, separator: &str, columns: Vec<String>) -> Result<Matrix<T>, Box<dyn Error>> where T: Default + Copy + FromStr, <T as FromStr>::Err: 'static, <T as FromStr>::Err: Error {
144    let file = File::open(path)?;
145    let reader = BufReader::new(file);
146
147    let data: Vec<T> = Vec::new();
148    let mut matrix = Matrix::new(data, (0, 0))?;
149
150    let mut header = true;
151    let mut index_columns: Vec<usize> = Vec::new();
152    for line in reader.lines() {
153        let line_tmp = line?;
154        if header {
155            header = false;
156            index_columns = get_columns_headers_index(&columns, line_tmp, separator);
157            continue;
158        }
159        let split: Vec<&str> = line_tmp.split(separator).collect();
160        let mut row: Vec<T> = Vec::new();
161        for i in 0..split.len() {
162            if !index_columns.contains(&i) {
163                continue;
164            }
165            let value = split[i].parse::<T>()?;
166            row.push(value);
167        }
168        matrix.add_row_from_vec(matrix.shape.0, row)?;
169    }
170
171    Ok(matrix)
172}
173
174/// Load a CSV file without gaven columns data to a Matrix.
175///
176/// # Examples
177///
178/// ```
179/// use matrix_operations::csv::load_matrix_from_csv_excluding_columns;
180///
181/// let path = "resources/test_read.csv";
182/// let separator = ",";
183/// let columns = vec!["col1".to_string()];
184///
185/// let matrix = load_matrix_from_csv_excluding_columns::<f32>(path, separator, columns).unwrap();
186///
187/// assert_eq!(matrix.shape(), (3, 1));
188/// assert_eq!(matrix[0], vec![2.0]);
189/// assert_eq!(matrix[1], vec![4.0]);
190/// assert_eq!(matrix[2], vec![6.0]);
191/// ```
192///
193/// If columns are not found, the function will ignore them.
194///
195/// ```
196/// use matrix_operations::csv::load_matrix_from_csv_excluding_columns;
197///
198/// let path = "resources/test_read.csv";
199/// let separator = ",";
200/// let columns = vec!["col3".to_string()];
201///
202/// let matrix = load_matrix_from_csv_excluding_columns::<f32>(path, separator, columns).unwrap();
203///
204/// assert_eq!(matrix.shape(), (3, 2));
205/// assert_eq!(matrix[0], vec![1.0, 2.0]);
206/// assert_eq!(matrix[1], vec![3.0, 4.0]);
207/// assert_eq!(matrix[2], vec![5.0, 6.0]);
208/// ```
209///
210/// # Panics
211///
212/// This function will panic if the file cannot be opened or if the separator is not found.
213///
214/// ```should_panic
215/// use matrix_operations::csv::load_matrix_from_csv_excluding_columns;
216///
217/// let path = "../resources/test_does_not_exist.csv";
218/// let separator = ",";
219/// let columns = vec!["col1".to_string()];
220///
221/// let matrix = load_matrix_from_csv_excluding_columns::<f32>(path, separator, columns).unwrap();
222/// ```
223pub fn load_matrix_from_csv_excluding_columns<T: Default + Copy + FromStr>(path: &str, separator: &str, columns: Vec<String>) -> Result<Matrix<T>, Box<dyn Error>> where T: Default + Copy + FromStr, <T as FromStr>::Err: 'static, <T as FromStr>::Err: Error {
224    let file = File::open(path)?;
225    let reader = BufReader::new(file);
226
227    let data: Vec<T> = Vec::new();
228    let mut matrix = Matrix::new(data, (0, 0))?;
229
230    let mut header = true;
231    let mut index_columns: Vec<usize> = Vec::new();
232    for line in reader.lines() {
233        let line_tmp = line?;
234        if header {
235            header = false;
236            index_columns = get_columns_headers_index(&columns, line_tmp, separator);
237            continue;
238        }
239        let split: Vec<&str> = line_tmp.split(separator).collect();
240        let mut row: Vec<T> = Vec::new();
241        for i in 0..split.len() {
242            if index_columns.contains(&i) {
243                continue;
244            }
245            let value = split[i].parse::<T>()?;
246            row.push(value);
247        }
248        matrix.add_row_from_vec(matrix.shape.0, row)?;
249    }
250
251    Ok(matrix)
252}
253
254fn get_columns_headers_index(columns: &Vec<String>, header: String, separator: &str) -> Vec<usize> {
255    let mut index_columns: Vec<usize> = Vec::new();
256    let split: Vec<&str> = header.split(separator).collect();
257    for i in 0..split.len() {
258        for j in 0..columns.len() {
259            if split[i] == columns[j] {
260                index_columns.push(i);
261            }
262        }
263    }
264    index_columns
265}
266
267/// Write a Matrix to a CSV file.
268///
269/// # Examples
270///
271/// ```
272/// use matrix_operations::csv::write_matrix_to_csv;
273///
274/// let matrix = matrix_operations::Matrix::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], (3, 2)).unwrap();
275/// let path = "resources/test_write.csv";
276///
277/// write_matrix_to_csv(&matrix, path, ",").unwrap();
278/// ```
279pub fn write_matrix_to_csv<T: Display>(matrix: &Matrix<T>, path: &str, separator: &str) -> Result<(), Box<dyn Error>> {
280    let mut file = File::create(path)?;
281    let mut header = String::new();
282    for i in 0..matrix.shape.1 {
283        header.push_str(&format!("col_{}", i));
284        if i != matrix.shape.1 - 1 {
285            header.push_str(separator);
286        }
287    }
288    header.push_str("\n");
289    file.write_all(header.as_bytes())?;
290    for i in 0..matrix.shape.0 {
291        let mut row = String::new();
292        for j in 0..matrix.shape.1 {
293            row.push_str(&format!("{}", matrix[i][j]));
294            if j != matrix.shape.1 - 1 {
295                row.push_str(separator);
296            }
297        }
298        row.push_str("\n");
299        file.write_all(row.as_bytes())?;
300    }
301    Ok(())
302}
303
304/// Write a Matrix to a CSV file with headers.
305///
306/// # Examples
307///
308/// ```
309/// use matrix_operations::csv::write_matrix_to_csv_with_headers;
310///
311/// let matrix = matrix_operations::Matrix::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], (3, 2)).unwrap();
312/// let path = "resources/test_write_headers.csv";
313/// let headers = vec!["col_1_header".to_string(), "col_2_header".to_string()];
314///
315/// write_matrix_to_csv_with_headers(&matrix, path, ",", headers).unwrap();
316/// ```
317///
318/// # Errors
319///
320/// This function will return an error if the number of headers does not match the number of columns in the matrix.
321///
322/// ```
323/// use matrix_operations::csv::write_matrix_to_csv_with_headers;
324///
325/// let matrix = matrix_operations::Matrix::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], (3, 2)).unwrap();
326/// let path = "resources/test_write_headers.csv";
327/// let headers = vec!["col_1_header".to_string()];
328///
329/// let result = write_matrix_to_csv_with_headers(&matrix, path, ",", headers);
330///
331/// assert!(result.is_err());
332/// ```
333pub fn write_matrix_to_csv_with_headers<T: Display>(matrix: &Matrix<T>, path: &str, separator: &str, headers: Vec<String>) -> Result<(), Box<dyn Error>> {
334    if headers.len() != matrix.shape.1 {
335        return Err("The number of headers does not match the number of columns in the matrix.".into());
336    }
337    let mut file = File::create(path)?;
338    let mut header = String::new();
339    for i in 0..matrix.shape.1 {
340        header.push_str(&format!("{}", headers[i]));
341        if i != matrix.shape.1 - 1 {
342            header.push_str(separator);
343        }
344    }
345    header.push_str("\n");
346    file.write_all(header.as_bytes())?;
347    for i in 0..matrix.shape.0 {
348        let mut row = String::new();
349        for j in 0..matrix.shape.1 {
350            row.push_str(&format!("{}", matrix[i][j]));
351            if j != matrix.shape.1 - 1 {
352                row.push_str(separator);
353            }
354        }
355        row.push_str("\n");
356        file.write_all(row.as_bytes())?;
357    }
358    Ok(())
359}