diff --git a/Cargo.lock b/Cargo.lock index 4165424..2eb2eb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,17 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lyagushka" +version = "0.1.0" +dependencies = [ + "atty", + "pyo3", + "rand", + "serde", + "serde_json", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -313,17 +324,6 @@ version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" -[[package]] -name = "traktorpy" -version = "0.1.0" -dependencies = [ - "atty", - "pyo3", - "rand", - "serde", - "serde_json", -] - [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/src/lib.rs b/src/lib.rs index 57ac035..7f1c202 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,10 @@ use pyo3::prelude::*; -use pyo3::wrap_pyfunction; use pyo3::types::PyList; -use std::fs::File; -// use std::io::{self, BufRead, BufReader, stdin, Read}; -use std::io::{self, BufRead, BufReader}; -use std::env; -use std::process; +use pyo3::wrap_pyfunction; use serde::Serialize; use serde_json; +use std::fs::File; +use std::io::{self, BufRead, BufReader}; #[derive(Clone, Debug, Serialize)] struct Point { @@ -22,195 +19,48 @@ impl Point { #[derive(Debug, Clone, Serialize)] struct ClusterGapInfo { - span_length: f32, // Full span length - num_elements: usize, // Number of elements, 0 for gaps - centroid: f32, // Centroid value - z_score: Option, // Z-score, to be calculated later + span_length: f32, + num_elements: usize, + centroid: f32, + z_score: Option, } - -fn load_dataset(filename: &str) -> io::Result> { - let file = File::open(filename)?; - let reader = BufReader::new(file); - let mut dataset = Vec::new(); - - for line in reader.lines() { - let value: u32 = line?.trim().parse().unwrap(); - dataset.push(Point::new(value)); - } - - dataset.sort_by_key(|p| p.value); - Ok(dataset) -} - -// Define the ClusterGapInfo struct as described above - fn calculate_densities_and_gaps(dataset: &[Point], factor: f32, min_cluster_size: usize) -> Vec { - let mut results: Vec = Vec::new(); - if dataset.len() < 2 { - return results; - } + if dataset.len() < 2 { return Vec::new(); } let mean_distance = dataset.windows(2) - .map(|w| distance(&w[0], &w[1]) as f32) + .map(|w| (w[1].value - w[0].value) as f32) .sum::() / (dataset.len() - 1) as f32; - - let cluster_threshold = 1.0 / factor * mean_distance; + let cluster_threshold = mean_distance / factor; let gap_threshold = factor * mean_distance * 2.0; - let mut current_cluster = Vec::new(); - for window in dataset.windows(2) { - let gap_distance = distance(&window[0], &window[1]) as f32; - if gap_distance <= cluster_threshold { - current_cluster.push(window[1].clone()); - } else { - // Before clearing the current_cluster, check if it meets the size requirement - if !current_cluster.is_empty() && current_cluster.len() >= min_cluster_size { - let cluster_info = create_cluster_info(¤t_cluster); - results.push(cluster_info); - } - current_cluster.clear(); - - // Add a gap if the distance exceeds the gap threshold - if gap_distance > gap_threshold { - results.push(ClusterGapInfo { - span_length: gap_distance, - num_elements: 0, - centroid: (window[0].value as f32 + window[1].value as f32) / 2.0, - z_score: None, - }); - } + dataset.windows(2).fold(Vec::new(), |mut acc, window| { + let gap_distance = (window[1].value - window[0].value) as f32; + if gap_distance > gap_threshold && acc.last().map_or(true, |last: &ClusterGapInfo| last.num_elements >= min_cluster_size) { + acc.push(ClusterGapInfo { + span_length: gap_distance, + num_elements: 0, + centroid: (window[0].value + window[1].value) as f32 / 2.0, + z_score: None, + }); } - } - - // Handle the last cluster if it meets the size requirement - if !current_cluster.is_empty() && current_cluster.len() >= min_cluster_size { - let cluster_info = create_cluster_info(¤t_cluster); - results.push(cluster_info); - } - - results -} - -// Additional helper function to create cluster information -fn create_cluster_info(cluster: &[Point]) -> ClusterGapInfo { - let num_elements = cluster.len(); - let span_length = (cluster.last().unwrap().value as f32) - (cluster.first().unwrap().value as f32); - let centroid = cluster.iter().map(|p| p.value as f32).sum::() / num_elements as f32; - - ClusterGapInfo { - span_length, - num_elements, - centroid, - z_score: None, // Placeholder, to be calculated later - } -} - -fn distance(p1: &Point, p2: &Point) -> u32 { - if p1.value > p2.value { p1.value - p2.value } else { p2.value - p1.value } -} - - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - let mut dataset: Vec = Vec::new(); - - // Check if data is being piped into the program - if atty::is(atty::Stream::Stdin) { - // Not receiving piped input, expect filename as argument - if args.len() < 4 { - eprintln!("Usage: {} ", args[0]); - eprintln!("Or pipe in a list of integers and provide "); - process::exit(1); - } - - let filename = &args[1]; - dataset = load_dataset(filename)?; - } else { - // Receiving piped input, read from stdin - let stdin = io::stdin(); - let reader = stdin.lock(); - for line in reader.lines() { - let value: u32 = line?.trim().parse().unwrap(); - dataset.push(Point::new(value)); - } - dataset.sort_by_key(|p| p.value); - } - - let factor: f32 = args[args.len() - 2].parse().expect("Factor must be a float"); - let min_cluster_size: usize = args[args.len() - 1].parse().expect("Min cluster size must be an integer"); - - let mut cluster_gap_infos = calculate_densities_and_gaps(&dataset, factor, min_cluster_size); - - // Calculate mean distance for Z-score computation - let total_distances: f32 = dataset.windows(2) - .map(|w| (w[1].value as f32 - w[0].value as f32)) - .sum(); - let mean_distance = total_distances / (dataset.len() as f32 - 1.0); - - // Calculate Z-scores for clusters and gaps - for info in cluster_gap_infos.iter_mut() { - if info.num_elements == 0 { - // Z-score for gaps - info.z_score = Some((info.span_length - mean_distance) / mean_distance); // Simplified deviation measure - } else { - // Z-score for clusters, based on density deviation - let density = info.num_elements as f32 / info.span_length; - let expected_density = 1.0 / mean_distance; // Expected: one element per mean distance - info.z_score = Some((density - expected_density) / expected_density); // Simplified deviation measure - } - } - - // Convert cluster_gap_infos to JSON - let json = serde_json::to_string_pretty(&cluster_gap_infos).expect("Failed to serialize to JSON"); - - // Output the JSON string - println!("{}", json); - - Ok(()) + acc + }) } #[pyfunction] -fn traktor(py: Python, int_list: &PyList, factor: f32, min_cluster_size: usize) -> PyResult { - // Convert Python list to Rust Vec - let mut dataset: Vec = Vec::new(); - for py_any in int_list.into_iter() { - let value: u32 = py_any.extract()?; - dataset.push(Point::new(value)); - } - dataset.sort_by_key(|p| p.value); +fn lyagushka(_py: Python, int_list: &PyList, factor: f32, min_cluster_size: usize) -> PyResult { + let dataset: Vec = int_list.into_iter() + .map(|py_any| py_any.extract::().map(Point::new)) + .collect::>>()?; + let cluster_gap_infos = calculate_densities_and_gaps(&dataset, factor, min_cluster_size); - // Proceed with your existing logic - let mut cluster_gap_infos = calculate_densities_and_gaps(&dataset, factor, min_cluster_size); - - // Calculate mean distance for Z-score computation - let total_distances: f32 = dataset.windows(2) - .map(|w| (w[1].value as f32 - w[0].value as f32)) - .sum(); - let mean_distance = total_distances / (dataset.len() as f32 - 1.0); - - // Calculate Z-scores for clusters and gaps - for info in cluster_gap_infos.iter_mut() { - if info.num_elements == 0 { - // Z-score for gaps - info.z_score = Some((info.span_length - mean_distance) / mean_distance); // Simplified deviation measure - } else { - // Z-score for clusters, based on density deviation - let density = info.num_elements as f32 / info.span_length; - let expected_density = 1.0 / mean_distance; // Expected: one element per mean distance - info.z_score = Some((density - expected_density) / expected_density); // Simplified deviation measure - } - } - - // Serialize to JSON and return - let json = serde_json::to_string_pretty(&cluster_gap_infos) - .expect("Failed to serialize to JSON"); - - Ok(json) + serde_json::to_string_pretty(&cluster_gap_infos) + .map_err(|e| PyErr::new::(format!("JSON Serialization Error: {}", e))) } #[pymodule] -fn lyagushka(py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(traktor, m)?)?; +fn lyagushka_module(py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(lyagushka, m)?)?; Ok(()) } \ No newline at end of file