2024-02-29 21:04:50 +02:00
|
|
|
use pyo3::prelude::*;
|
|
|
|
|
use pyo3::types::PyList;
|
2024-03-01 12:26:05 +02:00
|
|
|
use pyo3::wrap_pyfunction;
|
2024-02-10 19:58:33 +02:00
|
|
|
use serde::Serialize;
|
|
|
|
|
use serde_json;
|
2024-03-01 12:26:05 +02:00
|
|
|
use std::fs::File;
|
|
|
|
|
use std::io::{self, BufRead, BufReader};
|
2024-02-10 14:05:25 +02:00
|
|
|
|
2024-02-10 19:58:33 +02:00
|
|
|
#[derive(Clone, Debug, Serialize)]
|
2024-02-10 14:05:25 +02:00
|
|
|
struct Point {
|
|
|
|
|
value: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Point {
|
|
|
|
|
fn new(value: u32) -> Self {
|
2024-02-10 17:28:59 +02:00
|
|
|
Point { value }
|
2024-02-10 14:05:25 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-10 19:58:33 +02:00
|
|
|
#[derive(Debug, Clone, Serialize)]
|
2024-02-10 17:28:59 +02:00
|
|
|
struct ClusterGapInfo {
|
2024-03-01 12:26:05 +02:00
|
|
|
span_length: f32,
|
|
|
|
|
num_elements: usize,
|
|
|
|
|
centroid: f32,
|
|
|
|
|
z_score: Option<f32>,
|
2024-02-10 17:28:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn calculate_densities_and_gaps(dataset: &[Point], factor: f32, min_cluster_size: usize) -> Vec<ClusterGapInfo> {
|
2024-03-01 12:26:05 +02:00
|
|
|
if dataset.len() < 2 { return Vec::new(); }
|
2024-02-10 15:07:21 +02:00
|
|
|
|
2024-02-10 17:28:59 +02:00
|
|
|
let mean_distance = dataset.windows(2)
|
2024-03-01 12:26:05 +02:00
|
|
|
.map(|w| (w[1].value - w[0].value) as f32)
|
2024-02-10 17:28:59 +02:00
|
|
|
.sum::<f32>() / (dataset.len() - 1) as f32;
|
2024-03-01 12:26:05 +02:00
|
|
|
let cluster_threshold = mean_distance / factor;
|
2024-02-10 17:28:59 +02:00
|
|
|
let gap_threshold = factor * mean_distance * 2.0;
|
|
|
|
|
|
2024-03-01 12:26:05 +02:00
|
|
|
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,
|
|
|
|
|
});
|
2024-02-10 14:05:25 +02:00
|
|
|
}
|
2024-03-01 12:26:05 +02:00
|
|
|
acc
|
|
|
|
|
})
|
2024-02-29 21:04:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[pyfunction]
|
2024-03-01 12:26:05 +02:00
|
|
|
fn lyagushka(_py: Python, int_list: &PyList, factor: f32, min_cluster_size: usize) -> PyResult<String> {
|
|
|
|
|
let dataset: Vec<Point> = int_list.into_iter()
|
|
|
|
|
.map(|py_any| py_any.extract::<u32>().map(Point::new))
|
|
|
|
|
.collect::<PyResult<Vec<Point>>>()?;
|
|
|
|
|
let cluster_gap_infos = calculate_densities_and_gaps(&dataset, factor, min_cluster_size);
|
|
|
|
|
|
|
|
|
|
serde_json::to_string_pretty(&cluster_gap_infos)
|
|
|
|
|
.map_err(|e| PyErr::new::<pyo3::exceptions::PyException, _>(format!("JSON Serialization Error: {}", e)))
|
2024-02-29 21:04:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[pymodule]
|
2024-03-01 12:26:05 +02:00
|
|
|
fn lyagushka_module(py: Python, m: &PyModule) -> PyResult<()> {
|
|
|
|
|
m.add_function(wrap_pyfunction!(lyagushka, m)?)?;
|
2024-02-10 14:05:25 +02:00
|
|
|
Ok(())
|
2024-02-10 19:58:33 +02:00
|
|
|
}
|