From 4df5591c577e7766db052e114531cbf1af025ec9 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 1 Mar 2024 18:46:18 +0200 Subject: [PATCH] updated to HEAD --- .gitignore | 1 + src/main.rs | 150 ++++++++++++++++++++++++++++------------------------ 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..cb7f85d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +*.json \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ed75050..4fee8aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,44 +5,41 @@ use std::process; use serde::Serialize; use serde_json; -#[derive(Clone, Debug, Serialize)] -struct Point { - value: u32, -} - -impl Point { - fn new(value: u32) -> Self { - Point { value } - } -} - #[derive(Debug, Clone, Serialize)] -struct ClusterGapInfo { - span_length: f32, +struct Anomaly { + elements: Vec, + start: i32, + end: i32, + span_length: i32, num_elements: usize, centroid: f32, z_score: Option, } -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; +fn anomaly_info(cluster: &[i32]) -> Anomaly { + let num_elements: usize = cluster.len(); + let start: i32 = *cluster.first().expect("Cluster has no start"); + let end: i32 = *cluster.last().expect("Cluster has no end"); + let span_length: i32 = end - start; + let centroid: f32 = start as f32 + span_length as f32 / 2.0; - ClusterGapInfo { + Anomaly { + elements: cluster.to_vec(), + start, + end, span_length, num_elements, centroid, - z_score: None, + z_score: None, // Placeholder for actual Z-score calculation } } + /// Calculates the densities (clusters) and significant gaps between points in a dataset. /// /// This function iterates over a dataset of points, identifying clusters based on a distance threshold /// (calculated from the mean distance between points and adjusted by a given factor) and identifying significant gaps -/// that exceed a certain threshold. Each cluster or significant gap identified is summarized in a `ClusterGapInfo` object. +/// that exceed a certain threshold. Each cluster or significant gap identified is summarized in a `Anomaly` object. /// /// # Arguments /// * `dataset`: A slice of `Point` objects representing the dataset to be analyzed. @@ -51,57 +48,60 @@ fn create_cluster_info(cluster: &[Point]) -> ClusterGapInfo { /// * `min_cluster_size`: The minimum number of points required for a group of points to be considered a cluster. /// /// # Returns -/// A vector of `ClusterGapInfo` objects, each representing either a cluster of points or a significant gap between points. +/// A vector of `Anomaly` objects, each representing either a cluster of points or a significant gap between points. /// -fn calculate_densities_and_gaps(dataset: &[Point], factor: f32, min_cluster_size: usize) -> Vec { +fn scan_anomalies(dataset: &[i32], factor: f32, min_cluster_size: usize) -> Vec { // Return early if the dataset is too small to form any clusters or gaps. if dataset.len() < 2 { return Vec::new(); } // Calculate the mean distance between consecutive points in the dataset. - let mean_distance = dataset.windows(2) - .map(|w| w[1].value as f32 - w[0].value as f32) - .sum::() / (dataset.len() - 1) as f32; + let mean_distance: f32 = dataset.windows(2) + .map(|w| (w[1] - w[0]) as f32) + .sum::() / (dataset.len() - 1) as f32; // Define thresholds for clustering and gap identification based on the mean distance and factor. - let cluster_threshold = mean_distance / factor; - let gap_threshold = factor * mean_distance * 2.0; + let cluster_threshold: f32 = mean_distance / factor; + let gap_threshold: f32 = factor * mean_distance; - let mut results: Vec = Vec::new(); // Stores the resulting clusters and gaps. - let mut current_cluster: Vec = Vec::new(); // Temporary storage for points in the current cluster. + let mut results: Vec = Vec::new(); // Stores the resulting clusters and gaps. + let mut current_cluster: Vec = Vec::new(); // Temporary storage for points in the current cluster. // Iterate through pairs of consecutive points to find clusters and significant gaps. for window in dataset.windows(2) { - let gap_distance = window[1].value as f32 - window[0].value as f32; + let gap_size: f32 = (window[1] - window[0]) as f32; - // If the distance between points is within the cluster threshold, add to current cluster. - if gap_distance <= cluster_threshold { + if gap_size <= cluster_threshold { + // Add points to the current cluster if current_cluster.is_empty() { - current_cluster.push(window[0].clone()); // Start a new cluster with the first point. + current_cluster.push(window[0]); // Start a new cluster with the first point } - current_cluster.push(window[1].clone()); // Add the second point to the cluster. + current_cluster.push(window[1]); // Add the second point to the cluster } else { - // If the current cluster is large enough, finalize it and prepare for a new cluster. + // End the current cluster and start a new gap if !current_cluster.is_empty() && current_cluster.len() >= min_cluster_size { - results.push(create_cluster_info(¤t_cluster)); + results.push(anomaly_info(¤t_cluster)); current_cluster.clear(); } - // If the gap between points is significant, record it as a gap. - if gap_distance > gap_threshold { - results.push(ClusterGapInfo { - span_length: gap_distance, - num_elements: 0, // Indicating this is a gap, not a cluster. - centroid: (window[0].value as f32 + window[1].value as f32) / 2.0, - z_score: None, // Z-score will be calculated later if necessary. + // Record the gap + if gap_size > gap_threshold { + results.push(Anomaly { + elements: Vec::new(), // No elements in a gap + start: window[0], + end: window[1], + span_length: gap_size as i32, + num_elements: 0, + centroid: (window[0] as f32 + window[1] as f32) / 2.0, + z_score: None, }); } } } - // Finalize the last cluster if it meets the size requirement. + // Finalize the last cluster if applicable if !current_cluster.is_empty() && current_cluster.len() >= min_cluster_size { - results.push(create_cluster_info(¤t_cluster)); + results.push(anomaly_info(¤t_cluster)); } results @@ -125,41 +125,54 @@ fn calculate_densities_and_gaps(dataset: &[Point], factor: f32, min_cluster_size /// # Returns /// Returns a `String` containing the JSON-serialized analysis results, including clusters and gaps with their z-scores. /// -fn lyagushka(dataset: Vec, factor: f32, min_cluster_size: usize) -> String { +fn lyagushka(mut dataset: Vec, factor: f32, min_cluster_size: usize) -> String { + + // Sort the vector + dataset.sort_unstable(); // Calculate clusters and gaps from the dataset using predefined criteria. - let mut cluster_gap_infos = calculate_densities_and_gaps(&dataset, factor, min_cluster_size); + let mut anomalies: Vec = scan_anomalies(&dataset, factor, min_cluster_size); // Calculate the mean density of clusters in the dataset for comparison. - let mean_density: f32 = cluster_gap_infos.iter() - .filter(|info| info.num_elements > 0) - .map(|info| info.num_elements as f32 / info.span_length) - .sum::() / cluster_gap_infos.iter().filter(|info| info.num_elements > 0).count() as f32; + let mean_density: f32 = anomalies.iter() + .filter(|info: &&Anomaly| info.num_elements > 0) + .map(|info: &Anomaly| info.num_elements as f32 / info.span_length as f32) + .sum::() / anomalies.iter().filter(|info: &&Anomaly| info.num_elements > 0).count() as f32; // Calculate the standard deviation of cluster densities to evaluate variation. - let variance_density: f32 = cluster_gap_infos.iter() - .filter(|info| info.num_elements > 0) - .map(|info| info.num_elements as f32 / info.span_length) - .map(|density| (density - mean_density).powi(2)) - .sum::() / cluster_gap_infos.iter().filter(|info| info.num_elements > 0).count() as f32; - let std_dev_density = variance_density.sqrt(); + let variance_density: f32 = anomalies.iter() + .filter(|info: &&Anomaly| info.num_elements > 0) + .map(|info: &Anomaly| info.num_elements as f32 / info.span_length as f32) + .map(|density: f32| (density - mean_density).powi(2)) + .sum::() / anomalies.iter().filter(|info: &&Anomaly| info.num_elements > 0).count() as f32; + let std_dev_density: f32 = variance_density.sqrt(); - // Calculate the average span of all clusters and gaps to assess gap significance. - let average_span: f32 = cluster_gap_infos.iter().map(|info| info.span_length).sum::() / cluster_gap_infos.len() as f32; + // Calculate mean span length + let mean_span_length: f32 = anomalies.iter() + .map(|info: &Anomaly| info.span_length as f32) + .sum::() / anomalies.len() as f32; + + // Calculate variance + let variance: f32 = anomalies.iter() + .map(|info: &Anomaly| (info.span_length as f32 - mean_span_length).powi(2)) + .sum::() / anomalies.len() as f32; + + // Standard deviation is the square root of variance + let std_dev_span_length: f32 = variance.sqrt(); // Update Z-scores for both clusters and gaps based on their deviation from mean metrics. - for info in &mut cluster_gap_infos { + for info in anomalies.iter_mut() { if info.num_elements > 0 { // Calculate and update Z-score for clusters based on density deviation. - let cluster_density = info.num_elements as f32 / info.span_length; + let cluster_density: f32 = info.num_elements as f32 / info.span_length as f32; info.z_score = Some((cluster_density - mean_density) / std_dev_density); } else { // Calculate and update Z-score for gaps based on span length deviation. - info.z_score = Some((info.span_length - average_span) / std_dev_density); + info.z_score = Some((info.span_length as f32 / std_dev_span_length) * -1.0); } } - serde_json::to_string_pretty(&cluster_gap_infos).unwrap_or_else(|_| "Failed to serialize data".to_string()) + serde_json::to_string_pretty(&anomalies).unwrap_or_else(|_| "Failed to serialize data".to_string()) } /// The entry point for the command-line tool that reads a dataset of integers from either a file or stdin, @@ -201,7 +214,7 @@ fn main() -> io::Result<()> { let args: Vec = env::args().collect(); // Input handling - let dataset: Vec = if atty::is(atty::Stream::Stdin) { + let dataset: Vec = if atty::is(atty::Stream::Stdin) { if args.len() != 4 { eprintln!("Usage: {} ", args[0]); process::exit(1); @@ -209,16 +222,15 @@ fn main() -> io::Result<()> { let filename = &args[1]; let file = File::open(filename)?; BufReader::new(file).lines().filter_map(Result::ok) - .filter_map(|line| line.trim().parse::().ok()) - .map(Point::new) + .filter_map(|line| line.trim().parse::().ok()) // Directly parse to i32 .collect() } else { stdin().lock().lines().filter_map(Result::ok) - .filter_map(|line| line.trim().parse::().ok()) - .map(Point::new) + .filter_map(|line| line.trim().parse::().ok()) // Directly parse to i32 .collect() }; + 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");