use crate::sparse_io::{COLUMN_SEP, ROW_SEP};
use legume_numeric::matrix::common_io::*;
use std::ops::Range;
pub fn parse_name_file(
name_file: &str,
name_columns: Range<usize>,
name_sep: &str,
) -> anyhow::Result<Vec<String>> {
let name_data = read_lines_of_words(name_file, -1)?;
let names: Vec<String> = name_data
.lines
.iter()
.map(|line| {
(name_columns.start..name_columns.end.min(line.len()))
.filter_map(|i| line.get(i))
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(name_sep)
})
.collect();
Ok(names)
}
pub fn read_row_names(
row_file: Box<str>,
max_row_name_idx: usize,
) -> anyhow::Result<Vec<Box<str>>> {
Ok(parse_name_file(&row_file, 0..max_row_name_idx, ROW_SEP)?
.into_iter()
.map(String::into_boxed_str)
.collect())
}
pub fn read_col_names(
col_file: Box<str>,
max_column_name_idx: usize,
) -> anyhow::Result<Vec<Box<str>>> {
Ok(
parse_name_file(&col_file, 0..max_column_name_idx, COLUMN_SEP)?
.into_iter()
.map(String::into_boxed_str)
.collect(),
)
}
pub fn parse_index_spec(spec: &str) -> anyhow::Result<Vec<usize>> {
let mut out = Vec::new();
for token in spec.split(',') {
let token = token.trim();
if token.is_empty() {
continue;
}
match token.split_once('-') {
Some((start, end)) => {
let start: usize = start
.trim()
.parse()
.map_err(|_| anyhow::anyhow!("invalid range start in `{}`", token))?;
let end: usize = end
.trim()
.parse()
.map_err(|_| anyhow::anyhow!("invalid range end in `{}`", token))?;
if start > end {
anyhow::bail!("range start {} exceeds end {} in `{}`", start, end, token);
}
out.extend(start..=end);
}
None => {
let idx: usize = token
.parse()
.map_err(|_| anyhow::anyhow!("invalid index `{}`", token))?;
out.push(idx);
}
}
}
if out.is_empty() {
anyhow::bail!("no valid indices parsed from `{}`", spec);
}
Ok(out)
}
pub use crate::sparse_io::{MAX_COLUMN_NAME_IDX, MAX_ROW_NAME_IDX};
const TARGET_CHUNK_BYTES: usize = 1024 * 1024;
const MIN_CHUNK_ELEMS: usize = 8192;
pub fn chunk_elems(nelem: usize, elem_bytes: usize) -> usize {
(TARGET_CHUNK_BYTES / elem_bytes.max(1))
.max(MIN_CHUNK_ELEMS)
.min(nelem.max(1))
}
#[cfg(test)]
mod tests {
use super::parse_index_spec;
#[test]
fn singles_and_ranges() {
assert_eq!(parse_index_spec("0,2,5").unwrap(), vec![0, 2, 5]);
assert_eq!(parse_index_spec("1-5").unwrap(), vec![1, 2, 3, 4, 5]);
assert_eq!(
parse_index_spec("1-3,10,20-22").unwrap(),
vec![1, 2, 3, 10, 20, 21, 22]
);
assert_eq!(parse_index_spec(" 7 - 7 , 9 ").unwrap(), vec![7, 9]);
}
#[test]
fn rejects_bad_input() {
assert!(parse_index_spec("5-1").is_err());
assert!(parse_index_spec("abc").is_err());
assert!(parse_index_spec("").is_err());
}
}