1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use linfa::traits::Fit;
use linfa_clustering::KMeans;
use ndarray::Array2;
/// Agrupa las oraciones por similitud semántica y selecciona las oraciones más cercanas
/// a los centroides de cada uno de los `k` clusters para lograr compresión extractiva sin redundancia.
pub fn compress_text_by_clustering(
sentences: &[String],
embeddings: &[Vec<f32>],
k: usize,
) -> Vec<String> {
if sentences.is_empty() || embeddings.is_empty() {
return Vec::new();
}
let n = embeddings.len();
let d = embeddings[0].len();
// Si hay menos oraciones que clusters deseados, retornar todas las oraciones
if n <= k {
return sentences.to_vec();
}
// 1. Construir la matriz de ndarray (linfa utiliza f64 para cálculos de precisión)
let mut data = Array2::zeros((n, d));
for (i, row) in embeddings.iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
data[[i, j]] = val as f64;
}
}
// Envolver los datos en un Dataset de Linfa
let dataset = linfa::Dataset::from(data);
// 2. Ejecutar el algoritmo K-Means con linfa-clustering
let model = match KMeans::params(k)
.max_n_iterations(100)
.tolerance(1e-4)
.fit(&dataset)
{
Ok(m) => m,
Err(e) => {
eprintln!("⚠️ Error al ejecutar K-Means: {}. Usando fallback de primer bloque.", e);
return sentences.iter().take(k).cloned().collect();
}
};
// 3. Obtener los centroides de los clusters calculados
let centroids = model.centroids();
// 4. Para cada centroide, buscar la oración más cercana en el espacio vectorial (Distancia Euclidiana)
let mut selected_indices = Vec::new();
for cluster_idx in 0..centroids.nrows() {
let centroid = centroids.row(cluster_idx);
let mut min_dist = f64::MAX;
let mut best_idx = 0;
for sentence_idx in 0..n {
let mut dist = 0.0;
for j in 0..d {
let diff = embeddings[sentence_idx][j] as f64 - centroid[j];
dist += diff * diff;
}
if dist < min_dist {
min_dist = dist;
best_idx = sentence_idx;
}
}
// Evitar duplicar la misma oración si es la más cercana a múltiples centroides
if !selected_indices.contains(&best_idx) {
selected_indices.push(best_idx);
}
}
// 5. Ordenar los índices para que las oraciones mantengan su orden de lectura original en el documento
selected_indices.sort();
selected_indices
.into_iter()
.map(|idx| sentences[idx].clone())
.collect()
}