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}