use std::path::Path;
use crate::{DistanceMatrix, Error, Result, SparseDistanceMatrix};
use super::scanner::{Parse, is_separator, is_skipped, parse_numbers, tokens};
use super::window::for_each_window;
pub fn read_lower_distance_matrix(path: &Path, threads: usize) -> Result<DistanceMatrix> {
let name = path.display().to_string();
let mut sink = CondensedSink::new(threads);
for_each_window(path, threads, |first_line, text| {
sink.window(&name, first_line, text)
})?;
DistanceMatrix::from_condensed(sink.data)
}
pub fn parse_condensed(name: &str, text: &str, threads: usize) -> Result<Vec<f64>> {
let mut sink = CondensedSink::new(threads);
sink.window(name, 1, text)?;
Ok(sink.data)
}
pub(super) struct CondensedSink {
pub(super) parse: Parse,
pub(super) data: Vec<f64>,
}
impl CondensedSink {
pub(super) fn new(threads: usize) -> Self {
Self {
parse: Parse::new(threads),
data: Vec::new(),
}
}
pub(super) fn window(&mut self, name: &str, first_line: usize, text: &str) -> Result<usize> {
let (chunks, newlines) = self.parse.map(text, first_line, |first, chunk| {
condensed_chunk(name, first, chunk)
});
let total: usize = chunks
.iter()
.map_while(|chunk| chunk.as_ref().ok())
.map(Vec::len)
.sum();
self.data.reserve(total);
for chunk in chunks {
let mut chunk = chunk?;
if self.data.is_empty() {
self.data = chunk;
} else {
self.data.append(&mut chunk);
}
}
Ok(newlines)
}
}
fn condensed_chunk(name: &str, first_line: usize, text: &str) -> Result<Vec<f64>> {
let mut data = Vec::with_capacity(text.len() / 12);
for (idx, line) in text.lines().enumerate() {
let lineno = first_line + idx;
if is_skipped(line) {
continue;
}
parse_numbers(line, |v| data.push(v))
.map_err(|t| Error::InvalidInput(format!("{name}:{lineno}: not a number: {t:?}")))?;
}
Ok(data)
}
fn parse_index(bytes: &[u8], mut i: usize) -> Option<(usize, usize)> {
let start = i;
let mut value = 0usize;
while i < bytes.len() && bytes[i].is_ascii_digit() {
value = value
.checked_mul(10)?
.checked_add(usize::from(bytes[i] - b'0'))?;
i += 1;
}
(i > start && (i == bytes.len() || is_separator(bytes[i]))).then_some((value, i))
}
pub fn read_sparse_matrix(path: &Path, threads: usize) -> Result<SparseDistanceMatrix> {
let name = path.display().to_string();
let mut sink = TripletSink::new(threads);
for_each_window(path, threads, |first_line, text| {
sink.window(&name, first_line, text)
})?;
SparseDistanceMatrix::from_triplets(sink.n, &sink.triplets)
}
pub type Triplet = (usize, usize, f64);
pub fn parse_triplets(name: &str, text: &str, threads: usize) -> Result<(usize, Vec<Triplet>)> {
let mut sink = TripletSink::new(threads);
sink.window(name, 1, text)?;
Ok((sink.n, sink.triplets))
}
pub(super) struct TripletSink {
pub(super) parse: Parse,
pub(super) n: usize,
pub(super) triplets: Vec<Triplet>,
}
impl TripletSink {
pub(super) fn new(threads: usize) -> Self {
Self {
parse: Parse::new(threads),
n: 0,
triplets: Vec::new(),
}
}
pub(super) fn window(&mut self, name: &str, first_line: usize, text: &str) -> Result<usize> {
let (chunks, newlines) = self.parse.map(text, first_line, |first, chunk| {
triplet_chunk(name, first, chunk)
});
let total: usize = chunks
.iter()
.map_while(|chunk| chunk.as_ref().ok())
.map(|(_, triplets)| triplets.len())
.sum();
self.triplets.reserve(total);
for chunk in chunks {
let (chunk_n, mut chunk_triplets) = chunk?;
self.n = self.n.max(chunk_n);
if self.triplets.is_empty() {
self.triplets = chunk_triplets;
} else {
self.triplets.append(&mut chunk_triplets);
}
}
Ok(newlines)
}
}
fn triplet_chunk(name: &str, first_line: usize, text: &str) -> Result<(usize, Vec<Triplet>)> {
let mut triplets: Vec<Triplet> = Vec::with_capacity(text.len() / 24);
let mut n = 0usize;
for (idx, line) in text.lines().enumerate() {
let lineno = first_line + idx;
if is_skipped(line) {
continue;
}
let (i, j, d) = parse_triplet(line)
.ok_or(())
.or_else(|()| parse_triplet_slow(name, lineno, line))?;
if i == usize::MAX || j == usize::MAX {
return Err(Error::InvalidInput(format!(
"{name}:{lineno}: vertex index out of range"
)));
}
n = n.max(i + 1).max(j + 1);
triplets.push((i, j, d));
}
Ok((n, triplets))
}
fn parse_triplet(line: &str) -> Option<(usize, usize, f64)> {
if !line.is_ascii() {
return None;
}
let bytes = line.as_bytes();
let n = bytes.len();
let skip = |mut i: usize| {
while i < n && is_separator(bytes[i]) {
i += 1;
}
i
};
let (i, pos) = parse_index(bytes, skip(0))?;
let (j, pos) = parse_index(bytes, skip(pos))?;
let pos = skip(pos);
let (d, used) = fast_float2::parse_partial::<f64, _>(&bytes[pos..]).ok()?;
let pos = pos + used;
if pos < n && !is_separator(bytes[pos]) {
return None;
}
(skip(pos) == n).then_some((i, j, d))
}
fn parse_triplet_slow(name: &str, lineno: usize, line: &str) -> Result<(usize, usize, f64)> {
let fields: Vec<&str> = tokens(line).collect();
if fields.len() != 3 {
return Err(Error::InvalidInput(format!(
"{name}:{lineno}: expected 'i j d', got {} fields",
fields.len()
)));
}
let parse_vertex = |t: &str| {
t.parse::<usize>()
.map_err(|_| Error::InvalidInput(format!("{name}:{lineno}: not a vertex index: {t:?}")))
};
let i = parse_vertex(fields[0])?;
let j = parse_vertex(fields[1])?;
let d = fields[2].parse::<f64>().map_err(|_| {
Error::InvalidInput(format!("{name}:{lineno}: not a number: {:?}", fields[2]))
})?;
Ok((i, j, d))
}