minimized
This commit is contained in:
parent
44bb9dcd13
commit
b8859cefc8
2 changed files with 41 additions and 191 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
202
src/lib.rs
202
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<f32>, // Z-score, to be calculated later
|
||||
span_length: f32,
|
||||
num_elements: usize,
|
||||
centroid: f32,
|
||||
z_score: Option<f32>,
|
||||
}
|
||||
|
||||
|
||||
fn load_dataset(filename: &str) -> io::Result<Vec<Point>> {
|
||||
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<ClusterGapInfo> {
|
||||
let mut results: Vec<ClusterGapInfo> = 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::<f32>() / (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 {
|
||||
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 as f32 + window[1].value as f32) / 2.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::<f32>() / 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<String> = env::args().collect();
|
||||
let mut dataset: Vec<Point> = 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: {} <filename> <factor> <min_cluster_size>", args[0]);
|
||||
eprintln!("Or pipe in a list of integers and provide <factor> <min_cluster_size>");
|
||||
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<String> {
|
||||
// Convert Python list to Rust Vec<Point>
|
||||
let mut dataset: Vec<Point> = 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<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);
|
||||
|
||||
// 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::<pyo3::exceptions::PyException, _>(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(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue