019: Rotate Left
Difficulty: 1 Level: Foundations Rotate a list left by N positions โ elements that fall off the front wrap around to the back.The Problem This Solves
Rotating a list is everywhere: circular buffers in embedded systems, round-robin scheduling, carousel UI components, shift-register simulations. Given `['a','b','c','d','e','f','g','h']` and `n=3`, the result is `['d','e','f','g','h','a','b','c']` โ the first three elements moved to the end. In Python you'd write `lst[n:] + lst[:n]` for a positive rotation, or use `collections.deque` for efficient in-place rotation. This is clean, but you still need to handle negative `n` and `n > len(lst)` manually. Rust makes both cases explicit and efficient. The modular arithmetic normalizes any `n` โ positive, negative, or larger than the list โ into a single canonical shift, then a slice operation does the rotation in two array copies with no extra logic.The Intuition
In Python: `lst[n:] + lst[:n]` In JavaScript: `[...lst.slice(n), ...lst.slice(0, n)]` A left rotation by `n` is really just: "start reading at position `n`, wrap around when you reach the end." Rust's slice concatenation (`lst[shift..]` + `lst[..shift]`) does exactly that. The tricky part is normalizing `n`. If the list has 8 elements and you rotate by 11, that's the same as rotating by 3 (11 % 8 = 3). If you rotate by -2, that's the same as rotating left by 6 (or right by 2). The formula `((n % len) + len) % len` handles all cases in one line.How It Works in Rust
fn rotate<T: Clone>(lst: &[T], n: i64) -> Vec<T> {
if lst.is_empty() {
return vec![];
}
let len = lst.len() as i64;
// Normalize n: handles negative, zero, and n > len
let shift = ((n % len) + len) as usize % lst.len();
let mut result = lst[shift..].to_vec(); // elements from shift onward
result.extend_from_slice(&lst[..shift]); // wrap-around elements
result
}
- `n % len` โ reduce to range `(-len, len)`
- `+ len` โ make non-negative (shift negatives into positive range)
- `% lst.len()` โ final clamp to `[0, len)`
- `lst[shift..]` + `lst[..shift]` โ two slices joined into one Vec
What This Unlocks
- Circular buffers โ advance the read head by rotating the logical start position.
- Round-robin scheduling โ rotate the task queue after each cycle.
- Shift registers โ simulate hardware shift registers with arbitrary step sizes.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Rotation | Split + `@` (list concat) | Slice concat: `extend_from_slice` |
| Negative rotation | Separate case or `mod` trick | Same formula: `((n % len) + len) % len` |
| Cyclic iteration | `List.init` with modular index | `.cycle().skip(n).take(len)` |
| Signed index | `int` | `i64` (explicit sign), then cast to `usize` |
| Empty list guard | Pattern match on `[]` | `.is_empty()` early return |
// Rotate List Left โ 99 Problems #19
// Rotate a list left by n positions.
// rotate ['a','b','c','d','e','f','g','h'] 3
// โ ['d','e','f','g','h','a','b','c']
// Negative n rotates right.
fn rotate<T: Clone>(lst: &[T], n: i64) -> Vec<T> {
if lst.is_empty() {
return vec![];
}
let len = lst.len() as i64;
// Normalize n to 0..len
let shift = ((n % len) + len) as usize % lst.len();
let mut result = lst[shift..].to_vec();
result.extend_from_slice(&lst[..shift]);
result
}
/// Using iterators with cycle.
fn rotate_cycle<T: Clone>(lst: &[T], n: i64) -> Vec<T> {
if lst.is_empty() {
return vec![];
}
let len = lst.len() as i64;
let shift = (((n % len) + len) as usize) % lst.len();
lst.iter()
.cycle()
.skip(shift)
.take(lst.len())
.cloned()
.collect()
}
fn main() {
let input = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
println!("Input: {:?}", input);
println!("Rotate +3: {:?}", rotate(&input, 3));
println!("Rotate -2: {:?}", rotate(&input, -2));
println!("Rotate 0: {:?}", rotate(&input, 0));
println!("Rotate +11: {:?}", rotate(&input, 11));
println!("Cycle +3: {:?}", rotate_cycle(&input, 3));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rotate_positive() {
let input = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
assert_eq!(
rotate(&input, 3),
vec!['d', 'e', 'f', 'g', 'h', 'a', 'b', 'c']
);
}
#[test]
fn test_rotate_negative() {
let input = vec!['a', 'b', 'c', 'd'];
assert_eq!(rotate(&input, -1), vec!['d', 'a', 'b', 'c']);
}
#[test]
fn test_rotate_zero() {
let input = vec![1, 2, 3, 4];
assert_eq!(rotate(&input, 0), input);
}
#[test]
fn test_rotate_cycle_matches() {
let input = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
assert_eq!(rotate(&input, 3), rotate_cycle(&input, 3));
}
}
(* Rotate Left *)
(* OCaml 99 Problems #19 *)
(* Implementation for example 19 *)
(* Tests *)
let () =
(* Add tests *)
print_endline "โ OCaml tests passed"