use crate::data::dataframe::DataFrame;
use indexmap::IndexMap;
use std::collections::HashSet;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Keep {
First,
Last,
Min(usize),
Max(usize),
Random(u64),
}
impl Keep {
pub fn parse(name: &str, seed: Option<u64>, column: Option<usize>) -> Result<Self, String> {
Ok(match name {
"first" => Self::First,
"last" => Self::Last,
"min" => Self::Min(column.ok_or("'min' needs a column to compare")?),
"max" => Self::Max(column.ok_or("'max' needs a column to compare")?),
"random" => Self::Random(seed.unwrap_or_else(random_seed)),
other => {
return Err(format!(
"Unknown keep rule '{}'. Available: first, last, min, max, random",
other
))
}
})
}
}
pub fn random_seed() -> u64 {
rand::random()
}
fn compare_cells(a: &str, b: &str) -> std::cmp::Ordering {
if let (Ok(x), Ok(y)) = (a.trim().parse::<f64>(), b.trim().parse::<f64>()) {
return x.partial_cmp(&y).unwrap_or(std::cmp::Ordering::Equal);
}
a.cmp(b)
}
fn group_rows(df: &DataFrame, key_cols: &[usize]) -> IndexMap<Vec<String>, Vec<usize>> {
let mut groups: IndexMap<Vec<String>, Vec<usize>> = IndexMap::new();
for &physical in df.row_order.iter() {
let key: Vec<String> = key_cols
.iter()
.map(|&c| df.get_physical(physical, c))
.collect();
groups.entry(key).or_default().push(physical);
}
groups
}
pub fn duplicate_rows(df: &DataFrame, key_cols: &[usize]) -> Vec<usize> {
let all: Vec<usize>;
let keys = if key_cols.is_empty() {
all = (0..df.columns.len()).collect();
&all
} else {
key_cols
};
group_rows(df, keys)
.into_values()
.filter(|rows| rows.len() > 1)
.flatten()
.collect()
}
pub fn deduplicate(df: &DataFrame, key_cols: &[usize], keep: Keep) -> Result<Vec<usize>, String> {
if key_cols.is_empty() {
return Err("deduplication needs at least one column to compare".to_string());
}
for &c in key_cols {
if c >= df.columns.len() {
return Err(format!("column index {} is out of range", c));
}
}
if let Keep::Min(c) | Keep::Max(c) = keep {
if c >= df.columns.len() {
return Err(format!("tiebreaker column index {} is out of range", c));
}
}
let groups = group_rows(df, key_cols);
let keepers: HashSet<usize> = match keep {
Keep::First => groups
.values()
.filter_map(|rows| rows.first().copied())
.collect(),
Keep::Last => groups
.values()
.filter_map(|rows| rows.last().copied())
.collect(),
Keep::Random(seed) => {
use rand::seq::IndexedRandom;
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
groups
.values()
.filter_map(|rows| rows.choose(&mut rng).copied())
.collect()
}
Keep::Min(col) | Keep::Max(col) => {
let want_greater = matches!(keep, Keep::Max(_));
groups
.values()
.filter_map(|rows| {
let mut best = *rows.first()?;
let mut best_value = df.get_physical(best, col);
for &physical in rows.iter().skip(1) {
let value = df.get_physical(physical, col);
let ordering = compare_cells(&value, &best_value);
let better = if want_greater {
ordering == std::cmp::Ordering::Greater
} else {
ordering == std::cmp::Ordering::Less
};
if better {
best = physical;
best_value = value;
}
}
Some(best)
})
.collect()
}
};
Ok(df
.row_order
.iter()
.copied()
.filter(|r| keepers.contains(r))
.collect())
}
pub fn sample_rows(df: &DataFrame, n: usize, seed: u64) -> Vec<usize> {
use rand::SeedableRng;
let visible = df.row_order.len();
let take = n.min(visible);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut chosen: Vec<usize> = rand::seq::index::sample(&mut rng, visible, take).into_vec();
chosen.sort_unstable();
chosen.into_iter().map(|i| df.row_order[i]).collect()
}