Wenn du in einer Sequenz nach gleitenden Fenstern oder festen Blöcken suchen willst, sind windows und chunks die zwei Iterator-Adapter, die du brauchst. windows(n) liefert überlappende Fenster der Länge n — perfekt für Pattern-Suche, Bigram-Analyse, Sliding-Average. chunks(n) liefert disjunkte Blöcke — ideal für Block-weise Verarbeitung wie SIMD-Vektorisierung, Bild-Tiles, Audio-Frames. Dazu kommt chunks_exact für garantierte Block-Längen und chunks_mut für In-Place-Verarbeitung. Dieser Artikel zeigt alle Varianten mit konkreten Praxis-Beispielen.
windows(n) — überlappende Fenster
windows(n) gibt einen Iterator über überlappende Fenster der Länge n zurück:
fn main() {
let v = [1, 2, 3, 4, 5];
for fenster in v.windows(3) {
println!("{fenster:?}");
}
// [1, 2, 3]
// [2, 3, 4]
// [3, 4, 5]
}Jedes Fenster ist ein &[T] der Länge n. Aufeinanderfolgende Fenster überlappen um n-1 Elemente. Bei einem Slice der Länge L gibt es L - n + 1 Fenster (oder 0, wenn L < n).
Eigenschaften
- Längen-Garantie: jedes Fenster hat genau
nElemente. - Bei
n == 0: Panic. - Bei Slice kürzer als
n: leerer Iterator. - Read-only: gibt
&[T]zurück, nicht&mut [T]. (Mutable Windows gäbe es nicht, weil sie überlappen.)
chunks(n) — disjunkte Blöcke
chunks(n) gibt einen Iterator über nicht-überlappende Blöcke der Länge n zurück. Der letzte Block kann kürzer sein, wenn die Gesamtlänge kein Vielfaches von n ist.
fn main() {
let v = [1, 2, 3, 4, 5, 6, 7];
for block in v.chunks(3) {
println!("{block:?}");
}
// [1, 2, 3]
// [4, 5, 6]
// [7] ← letzter Block ist kürzer
}Bei einem Slice der Länge L gibt es ceil(L / n) Blöcke. Jeder Block ist ein &[T], der erste bis vorletzte hat Länge n, der letzte kann zwischen 1 und n haben.
chunks_exact(n) — garantierte Blöcke
Wenn du garantiert Blöcke der Länge n willst (und Rest-Bytes separat behandeln möchtest), nutzt du chunks_exact:
fn main() {
let v = [1, 2, 3, 4, 5, 6, 7];
let chunks = v.chunks_exact(3);
let rest = chunks.remainder(); // letzte 1 Element
for block in chunks {
println!("{block:?}");
}
println!("Rest: {rest:?}");
// [1, 2, 3]
// [4, 5, 6]
// Rest: [7]
}Vorteile gegenüber chunks:
- Jeder iterierte Block hat exakt
nElemente — kein „letzter Block kürzer". remainder()gibt die übrigen Elemente separat.- Compiler-freundlich: bei festen Block-Größen kann der Compiler die Loop auto-vektorisieren (SIMD).
chunks_exact ist die idiomatische Wahl für performance-kritische Block-Verarbeitung.
Mutable Varianten
chunks_mut
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
for block in v.chunks_mut(2) {
// block ist &mut [i32]
for x in block.iter_mut() {
*x *= 10;
}
}
assert_eq!(v, vec![10, 20, 30, 40, 50, 60]);
}chunks_exact_mut
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let mut iter = v.chunks_exact_mut(2);
for block in &mut iter {
block[0] += 100;
block[1] += 200;
}
let rest = iter.into_remainder(); // letzter Wert
for x in rest {
*x = 0;
}
assert_eq!(v, vec![101, 202, 103, 204, 0]);
}Keine windows_mut
Es gibt bewusst kein windows_mut in der Stdlib — überlappende mutable Slices wären Aliasing-Konflikte. Wer ähnliches braucht: über Indices iterieren mit split_at_mut-Tricks oder spezialisierte Crates.
rchunks und rwindows
Beide Adapter haben rückwärts-Varianten — Iteration vom Ende her:
fn main() {
let v = [1, 2, 3, 4, 5, 6, 7];
for block in v.rchunks(3) {
println!("{block:?}");
}
// [5, 6, 7]
// [2, 3, 4]
// [1]
// (rückwärts gruppiert!)
}Nützlich, wenn die natürliche Lese-Richtung das Ende ist (z. B. Hash-Vergleiche von hinten, Multi-Byte-Integer-Lesen).
Vergleich windows vs. chunks
windows(n) | chunks(n) | |
|---|---|---|
| Überlappung | ja, je n-1 Elemente | nein |
| Anzahl Iterationen | L - n + 1 | ceil(L / n) |
| Längen-Garantie | jeder Block n | letzter kann kürzer |
| Mutable Variante | nein (Aliasing) | ja (chunks_mut) |
| Typische Anwendung | Pattern-Suche, Sliding-Average | Block-weise Verarbeitung |
| Edge Case bei kurzem Slice | leerer Iterator | letzter Block kürzer |
Praxis: windows und chunks im echten Code
Pattern-Suche in Bytes (windows)
pub fn finde_pattern(daten: &[u8], pattern: &[u8]) -> Option<usize> {
daten.windows(pattern.len())
.position(|fenster| fenster == pattern)
}
fn main() {
let data = b"Hallo Welt mit Magic-PNG-Header: \x89PNG\r\n\x1a\n weiter";
let png_magic = b"\x89PNG\r\n\x1a\n";
assert_eq!(finde_pattern(data, png_magic), Some(33));
}windows(n) plus position — klassische Pattern-Suche. Effizient und ohne externe Dependency.
Sliding-Window-Average (windows)
pub fn moving_average(daten: &[f64], fenstergroesse: usize) -> Vec<f64> {
if daten.len() < fenstergroesse { return Vec::new(); }
daten.windows(fenstergroesse)
.map(|w| w.iter().sum::<f64>() / fenstergroesse as f64)
.collect()
}
fn main() {
let werte = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let avg = moving_average(&werte, 3);
assert_eq!(avg, vec![2.0, 3.0, 4.0]); // (1+2+3)/3, (2+3+4)/3, (3+4+5)/3
}Klassischer DSP-/Time-Series-Algorithmus. Drei Mittelwerte für vier Fenster aus fünf Eingangs-Werten.
Bigram-Analyse für Text (windows)
use std::collections::HashMap;
pub fn bigram_haeufigkeiten(woerter: &[&str]) -> HashMap<(String, String), u32> {
let mut counts = HashMap::new();
for paar in woerter.windows(2) {
let key = (paar[0].to_string(), paar[1].to_string());
*counts.entry(key).or_insert(0) += 1;
}
counts
}
fn main() {
let w = ["der", "schnelle", "braune", "fuchs", "der", "schnelle"];
let bigrams = bigram_haeufigkeiten(&w);
assert_eq!(bigrams.get(&("der".into(), "schnelle".into())), Some(&2));
}RGB-Pixel-Verarbeitung (chunks_exact)
pub fn invertieren_rgb(pixels: &mut [u8]) {
for pixel in pixels.chunks_exact_mut(3) {
pixel[0] = 255 - pixel[0];
pixel[1] = 255 - pixel[1];
pixel[2] = 255 - pixel[2];
}
// Falls Länge nicht durch 3 teilbar — Rest unverändert (Stand der iter).
}
fn main() {
let mut bild = vec![100u8, 150, 200, 50, 100, 150];
invertieren_rgb(&mut bild);
assert_eq!(bild, vec![155, 105, 55, 205, 155, 105]);
}chunks_exact_mut(3) iteriert in Drei-Byte-Schritten. Wegen der festen Block-Größe kann der Compiler den Code oft auto-vektorisieren.
Audio-Frame-Verarbeitung (chunks)
pub fn frame_peaks(samples: &[i16], frame_size: usize) -> Vec<i16> {
samples.chunks(frame_size)
.map(|frame| frame.iter().map(|s| s.abs()).max().unwrap_or(0))
.collect()
}
fn main() {
let samples: Vec<i16> = (-5..=5).collect();
let peaks = frame_peaks(&samples, 3);
// Frames: [-5,-4,-3], [-2,-1,0], [1,2,3], [4,5]
assert_eq!(peaks, vec![5, 2, 3, 5]);
}Hash-Vergleich blockweise (chunks)
pub fn sind_blockgleich(a: &[u8], b: &[u8], block: usize) -> Option<usize> {
// Gibt den Index des ersten Blocks zurück, der NICHT gleich ist
for (i, (ba, bb)) in a.chunks(block).zip(b.chunks(block)).enumerate() {
if ba != bb {
return Some(i);
}
}
None
}chunks plus zip — paarweise Block-Vergleich.
Sliding-Maximum (windows)
pub fn sliding_max(daten: &[i32], n: usize) -> Vec<i32> {
daten.windows(n)
.map(|w| *w.iter().max().unwrap())
.collect()
}
fn main() {
let v = vec![1, 3, 2, 5, 4, 1, 6];
assert_eq!(sliding_max(&v, 3), vec![3, 5, 5, 5, 6]);
}Triplet-Erkennung (windows + match)
pub fn finde_aufsteigend(daten: &[i32]) -> Option<usize> {
for (i, fenster) in daten.windows(3).enumerate() {
if let [a, b, c] = fenster {
if a < b && b < c {
return Some(i);
}
}
}
None
}
fn main() {
let v = [5, 3, 1, 2, 4, 7, 6];
assert_eq!(finde_aufsteigend(&v), Some(2)); // 1, 2, 4
}Kombination aus windows(3) und Slice-Pattern-Matching — sehr lesbar.
Daten-Partitionierung in feste Größen (chunks)
pub fn process_batches<T, F>(daten: &[T], batch_size: usize, mut handler: F)
where
F: FnMut(&[T]),
{
for batch in daten.chunks(batch_size) {
handler(batch);
}
}
fn main() {
let user_ids: Vec<u64> = (1..=10).collect();
process_batches(&user_ids, 3, |b| {
println!("Batch: {b:?}");
});
// Batch: [1, 2, 3]
// Batch: [4, 5, 6]
// Batch: [7, 8, 9]
// Batch: [10]
}Klassisches Batch-Pattern für API-Calls, DB-Inserts, Network-Sends.
Vector-Pair-Operation (chunks_exact)
pub fn skalar_produkt(a: &[f32], b: &[f32]) -> f32 {
a.chunks_exact(4)
.zip(b.chunks_exact(4))
.map(|(ca, cb)| {
ca[0]*cb[0] + ca[1]*cb[1] + ca[2]*cb[2] + ca[3]*cb[3]
})
.sum()
}Mit chunks_exact(4) wird die innere Schleife vom LLVM-Optimizer typischerweise auf SIMD-Instruktionen abgebildet — automatischer Vector-Speedup.
Interessantes
windows(n) überlappt, chunks(n) nicht.
Das ist der zentrale Unterschied. windows für gleitende Pattern-Suche, chunks für disjunkte Block-Verarbeitung. Wer das durcheinander bringt, bekommt entweder zu viele oder zu wenige Iterationen.
chunks_exact ist Performance-freundlich.
Die feste Block-Größe macht es dem LLVM-Optimizer leicht, die Schleife zu vektorisieren (SIMD). In numerischem Code mit chunks_exact(4) oder chunks_exact(8) oft Faktor 2-4× schneller als chunks.
Es gibt kein windows_mut.
Überlappende mutable Slices würden die Aliasing-Regel brechen. Wer ähnliches braucht: über Indices iterieren mit split_at_mut-Tricks, oder die arrayvec/itertools-Crates für spezialisierte Adapter.
Bei kurzem Slice: windows liefert leere Iteration, chunks liefert einen kurzen Block.
[1, 2].windows(3) ergibt 0 Iterationen. [1, 2].chunks(3) ergibt 1 Iteration mit [1, 2]. Wer garantiert volle Blöcke braucht: chunks_exact plus remainder() für den Rest.
n == 0 panickt bei allen Varianten.
windows(0) und chunks(0) panicken zur Laufzeit. Bei dynamischem n vorher prüfen.
rchunks und rwindows für rückwärtige Iteration.
Iteration vom Slice-Ende her. Bei [1,2,3,4,5].rchunks(2) kommen [4,5], [2,3], [1]. Sehr nützlich bei Algorithmen, die natürlich am Ende beginnen.
chunks_exact-Iterator hat remainder().
Während des Iterierens ist der Rest noch verfügbar via iter.remainder(). Nach into_remainder() (verbrauchende Variante) bekommst du den Rest als &[T] für separate Behandlung.
windows kombiniert sich gut mit Slice-Patterns.
for win in daten.windows(3) { if let [a, b, c] = win { ... } } ist ein idiomatischer Weg, Triplets in einer Sequenz zu prüfen. Pattern-Matching plus Slice-Adapter — sehr elegant.
Weiterführende Ressourcen
Externe Quellen
- std::primitive.slice – windows
- std::primitive.slice – chunks
- std::primitive.slice – chunks_exact
- std::primitive.slice – rchunks
- The Rust Performance Book – SIMD