use std::path::Path;
use std::sync::Arc;
use tuitab::data::dataframe::DataFrame;
use tuitab::data::dedup::{deduplicate, duplicate_rows, Keep};
use tuitab::data::io::load_file;
fn with_repeats() -> DataFrame {
let mut df = load_file(Path::new("test_data/sample.csv"), None).unwrap();
df.row_order = Arc::new(vec![0, 1, 2, 3, 4]);
df
}
fn department(df: &DataFrame) -> usize {
df.column_index("department").unwrap()
}
#[test]
fn duplicates_are_the_rows_whose_key_repeats() {
let df = with_repeats();
let dept = department(&df);
let mut found = duplicate_rows(&df, &[dept]);
found.sort();
assert_eq!(found, vec![0, 2, 4]);
}
#[test]
fn whole_row_duplicates_find_nothing_when_every_row_differs() {
let df = with_repeats();
assert!(
duplicate_rows(&df, &[]).is_empty(),
"no two rows are identical"
);
}
#[test]
fn keeping_first_and_last_pick_the_ends_of_each_group() {
let df = with_repeats();
let dept = department(&df);
assert_eq!(
deduplicate(&df, &[dept], Keep::First).unwrap(),
vec![0, 1, 3],
"Alice for Engineering, then the two singletons"
);
assert_eq!(
deduplicate(&df, &[dept], Keep::Last).unwrap(),
vec![1, 3, 4],
"Eve is the last Engineering row"
);
}
#[test]
fn min_and_max_use_the_tiebreaker_column_numerically() {
let df = with_repeats();
let dept = department(&df);
let age = df.column_index("age").unwrap();
assert_eq!(
deduplicate(&df, &[dept], Keep::Min(age)).unwrap(),
vec![1, 2, 3]
);
assert_eq!(
deduplicate(&df, &[dept], Keep::Max(age)).unwrap(),
vec![1, 3, 4]
);
}
#[test]
fn the_random_keeper_is_reproducible_from_its_seed() {
let df = with_repeats();
let dept = department(&df);
let once = deduplicate(&df, &[dept], Keep::Random(42)).unwrap();
let again = deduplicate(&df, &[dept], Keep::Random(42)).unwrap();
assert_eq!(once, again);
assert_eq!(once.len(), 3, "one row per department");
}
#[test]
fn deduplicating_without_keys_is_refused() {
let df = with_repeats();
assert!(deduplicate(&df, &[], Keep::First).is_err());
}
#[test]
fn an_out_of_range_column_is_refused_rather_than_ignored() {
let df = with_repeats();
assert!(deduplicate(&df, &[99], Keep::First).is_err());
assert!(deduplicate(&df, &[0], Keep::Min(99)).is_err());
}
#[test]
fn the_gd_key_produces_the_shared_dedup() {
use tuitab::types::Action;
let mut app = tuitab::app::App::new_as(Path::new("test_data/sample.csv"), None, None).unwrap();
{
let s = app.stack.active_mut();
let dept = s.dataframe.column_index("department").unwrap();
s.dataframe.columns[dept].pinned = true;
}
app.handle_action(Action::DeduplicateByPinned);
app.handle_action(Action::DeduplicateByPinned);
let produced = &app.stack.active().dataframe;
assert_eq!(produced.visible_row_count(), 4);
let full = load_file(Path::new("test_data/sample.csv"), None).unwrap();
let dept = full.column_index("department").unwrap();
let expected = deduplicate(&full, &[dept], Keep::First).unwrap();
assert_eq!(produced.row_order.to_vec(), expected);
}
use tuitab::data::dedup::sample_rows;
#[test]
fn a_sample_is_reproducible_from_its_seed_and_keeps_table_order() {
let df = load_file(Path::new("test_data/sample.csv"), None).unwrap();
let once = sample_rows(&df, 5, 7);
let again = sample_rows(&df, 5, 7);
assert_eq!(once, again, "the same seed must give the same rows");
assert_eq!(once.len(), 5);
let mut sorted = once.clone();
sorted.sort();
assert_eq!(
once, sorted,
"rows come back in table order, not draw order"
);
}
#[test]
fn asking_for_more_rows_than_exist_returns_all_of_them() {
let df = load_file(Path::new("test_data/sample.csv"), None).unwrap();
assert_eq!(sample_rows(&df, 500, 1).len(), 20);
}
#[test]
fn a_sample_draws_only_from_the_visible_rows() {
let mut df = load_file(Path::new("test_data/sample.csv"), None).unwrap();
df.row_order = Arc::new(vec![3, 7, 11]);
let chosen = sample_rows(&df, 2, 99);
assert_eq!(chosen.len(), 2);
assert!(
chosen.iter().all(|r| [3, 7, 11].contains(r)),
"a filtered-out row must not reappear: {:?}",
chosen
);
}
#[test]
fn sampling_from_a_large_table_finishes() {
use polars::prelude::{Column, NamedFrom, Series};
use tuitab::data::column::ColumnMeta;
let n = 200_000;
let values: Vec<i64> = (0..n as i64).collect();
let pdf = polars::prelude::DataFrame::new_infer_height(vec![Column::from(Series::new(
"id".into(),
&values,
))])
.unwrap();
let df = DataFrame::from_parts(pdf, vec![ColumnMeta::new("id".to_string())]);
let chosen = sample_rows(&df, 2_000, 11);
assert_eq!(chosen.len(), 2_000);
let mut sorted = chosen.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted, chosen, "distinct rows, in table order");
}
#[test]
fn rows_that_merely_display_alike_are_not_duplicates() {
use polars::prelude::{Column, NamedFrom, Series};
use tuitab::data::column::ColumnMeta;
use tuitab::types::ColumnType;
let pdf = polars::prelude::DataFrame::new_infer_height(vec![Column::from(Series::new(
"amount".into(),
&[1.504f64, 1.496, 1.504],
))])
.unwrap();
let mut meta = ColumnMeta::new("amount".to_string());
meta.col_type = ColumnType::Float;
meta.precision = 2;
let df = DataFrame::from_parts(pdf, vec![meta]);
let shown: Vec<String> = (0..3).map(|r| df.format_display(r, 0)).collect();
assert_eq!(shown, vec!["1.50", "1.50", "1.50"]);
let mut duplicates = duplicate_rows(&df, &[0]);
duplicates.sort();
assert_eq!(duplicates, vec![0, 2]);
assert_eq!(
deduplicate(&df, &[0], Keep::First).unwrap(),
vec![0, 1],
"1.496 survives; it is not the same number as 1.504"
);
}