I’ll have to assume a few things you don’t specify explicitly.
I’ll assume there can be an arbitrary number of such matrices.
I assume “processing” refers to something different from just “parsing” them. The parsing part might be easiest if it’s done separately from the further “processing”.
There’s some different approaches; you could kind-of “stream” the file, i.e. do the processing as you read it; or you could do a sequence of batch-processing steps, i.e. first read the whole file into memory, then parse the whole file into some data structure, then process everything.
I’ll assume the files are rather small, so putting everything into memory is feasible, which can be more straightforward than a “streaming” approach. I’ll also assume you want some form of 5x5-matrix-like structure, and I’ll assume the entries are positive nonnegative integers, so let’s try to achieve some [[u32; 5]; 5]
2-dimensional arrays.
First step, read the file. Using read_to_string in std::fs - Rust is simplest.
let data = fs::read_to_string("...path...");
On a String
, you can split it into lines, using the lines method.
let lines = data.lines(); // this is an iterator of lines (type &str)
This will contain the empty lines, and the non-empty lines. Let’s ignore leading and trailing whitespace on each line, actually, let’s do that in the same line of code, so instead of the above,
let lines = data.lines().map(|line| line.trim());
Now we need to find starting points indicated by empty lines, and for each of them consider the next 5 lines. Or something like that… see… you’re not quite clear how lenient or strict we’re supposed to be about the file format; we might as well just expect the whole file to be of the form:
- empty line
- 5 lines of 5 numbers each
- end of file or repeat the above
and error (for now, via panic) if anything doesn’t match. Actually that’s probably easier. If this doesn’t match your use-case, you might want to specify what else is allowed; e.g. additional empty lines, additional kinds of content without preceding empty lines, or whatever. Well in that case, we could just take chunks of 6 lines and make sure everything looks as expected. Let’s use the itertools crate for this.
let six_lines_each = lines.chunks(6);
Then, we can process the chunks and collect there resulting matrices in a Vec
. Proper error handling would probably be nicer if this was a “production ready” piece of software, but panics for when anything goes wrong are probably easier.
So preparing a place to store the results
let mut matrices: Vec<[[u32; 5]; 5]> = Vec::new();
then iterate over the chunks
for chunk in &six_lines_each {
// ...
}
Now, for each chunk
, we expect, initially, an empty line
assert!(chunk.next().unwrap().is_empty());
Then 5 lines of contents, with 5 space-separated numbers. One way to do this is using zip
. Prepare the array we want to write the result to, then zip with an iterator of lines/words, and do the writing for each entry. To also validate there are exactly more 5 lines available and 5 words per line, using zip_eq from itertools
might be a good idea. (It’s like zip
, but also validates the lengths match.)
So prepare the resulting matrix:
let mut matrix = [[0_u32; 5]; 5];
Then zip_eq for the lines
for (line, row) in chunk.zip_eq(&mut matrix) {
// ..
}
Here, the line
is a &str
, and the row
is a &mut [u32; 5]
where the line’s contents shall be written to.
Another zip_eq for the numbers, well, as soon as we have them split by whitespace
for (number, entry) in line.split_whitespace().zip_eq(row) {
}
so we have a number: &str
that still needs parsing, and an entry: &mut u32
where the result shall be written.
Finally,
*entry = number.parse().unwrap();
to parse the string into an u32
, and store the result.
And after the inner loops, save the matrix
matrices.push(matrix);
In the end you’ll have a Vec<[[u32; 5]; 5]>
that you can process further however you like
→ Look here to see the whole code in the playground.