Aplatir un Vec<Vec<T>> en Vec<T> en utilisant les iterators
Table des matières
Aplatir avec les Iterators
La façon la plus idiomatique est d'utiliser .flatten() ou .flat_map() :
let nested = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
// Méthode 1: flatten() (pour Vec<Iterables>)
let flat: Vec<_> = nested.iter().flatten().copied().collect();
// Méthode 2: flat_map() (pour transformations personnalisées)
let flat: Vec<_> = nested.into_iter().flat_map(|v| v).collect();
Sortie : [1, 2, 3, 4, 5, 6]
Concaténation Manuelle
Pour comparaison, voici comment tu pourrais le faire manuellement :
let mut flat = Vec::new();
for subvec in nested {
flat.extend(subvec); // ou append() si subvec n'est plus nécessaire
}
Comparaison de Performance
| Méthode | Complexité Temps | Complexité Espace | Allocations | Optimisations |
|---|---|---|---|---|
| Iterator (flatten) | O(n) | O(1) iterator | 1 (résultat) | Peut fuser les iterators |
| Manuel (extend) | O(n) | O(1) espace temp | 1 (résultat) | Pré-allocation possible |
Insights Clés
Avantage de Pré-allocation (Manuel)
Tu peux pré-allouer le Vec cible si la taille totale est connue :
let total_len: usize = nested.iter().map(|v| v.len()).sum();
let mut flat = Vec::with_capacity(total_len); // Critique pour gros datasets
flat.extend(nested.into_iter().flatten());
Laziness des Iterators
.flatten()est lazy, mais.collect()doit quand même allouer le résultat.- Les iterators chaînés (ex :
.filter().flatten()) peuvent mieux optimiser que les boucles manuelles.
Exemple de Benchmark
let nested: Vec<Vec<i32>> = (0..1_000).map(|i| vec![i; 100]).collect();
// Approche iterator
let start = std::time::Instant::now();
let flat = nested.iter().flatten().copied().collect::<Vec<_>>();
println!("flatten: {:?}", start.elapsed());
// Approche manuelle avec pré-allocation
let start = std::time::Instant::now();
let total_len = nested.iter().map(|v| v.len()).sum();
let mut flat = Vec::with_capacity(total_len);
flat.extend(nested.into_iter().flatten());
println!("manual: {:?}", start.elapsed());
Résultat Typique :
- Manuel avec pré-allocation est ~10–20% plus rapide pour gros Vecs.
- Version iterator est plus concise et également rapide pour petites données.
Quand Utiliser Chacune
| Approche | Meilleure Pour | Pièges |
|---|---|---|
| Iterator | Lisibilité, chaîner les opérations | Légèrement plus lent sans pré-allocation |
| Manuel | Performance maximum, grosses données | Verbeux ; nécessite calcul de longueur |
Avancé : Aplatissement Zero-Copy
Si tu as Vec<&[T]> au lieu de Vec<Vec<T>>, utilise .flatten().copied() pour éviter le cloning :
let slices: Vec<&[i32]> = vec![&[1, 2], &[3, 4]];
let flat: Vec<i32> = slices.iter().flatten().copied().collect();
Points Clés
✅ Utilise .flatten() pour :
- Code propre, idiomatique.
- Chaîner avec autres adaptateurs d'iterator (ex :
.filter()).
✅ Utilise extend manuel pour :
- Gros datasets où la pré-allocation compte.
- Cas où tu connais déjà la longueur totale.
🚀 Pré-alloue toujours pour concaténation manuelle de grosses collections !
Essaie Ceci : Comment aplatirais-tu un Vec<Vec<T>> tout en supprimant les doublons ?
Réponse : Combine .flatten() avec .collect::<HashSet<_>>().
Exemples Supplémentaires
Aplatissement avec Transformation
let nested = vec![vec![1, 2], vec![3, 4, 5]];
// Aplatir et doubler chaque élément
let doubled: Vec<_> = nested
.iter()
.flatten()
.map(|&x| x * 2)
.collect();
// Résultat: [2, 4, 6, 8, 10]
Aplatissement Conditionnel
let nested = vec![vec![1, 2, 3], vec![4, 5], vec![6, 7, 8, 9]];
// Aplatir seulement les vecs avec plus de 2 éléments
let filtered_flat: Vec<_> = nested
.iter()
.filter(|v| v.len() > 2)
.flatten()
.copied()
.collect();
// Résultat: [1, 2, 3, 6, 7, 8, 9]
Performance avec Different Tailles
fn benchmark_different_sizes() {
// Nombreux petits vecs
let many_small: Vec<Vec<i32>> = (0..10_000).map(|i| vec![i, i+1]).collect();
// Peu de gros vecs
let few_large: Vec<Vec<i32>> = (0..10).map(|i| (0..10_000).collect()).collect();
// Test des deux approches
// many_small favorise généralement les iterators
// few_large favorise la pré-allocation manuelle
}
Zero-Allocation avec Références
fn process_nested_data(data: &[Vec<i32>]) -> i32 {
// Traite sans allouer un nouveau Vec
data.iter()
.flatten()
.filter(|&&x| x > 0)
.sum()
}
La clé est de choisir l'approche basée sur tes besoins : iterators pour la lisibilité et le chaînage, manuel pour la performance pure avec de grosses données.