Skip to content

← Rust: zero to hero step 10 of 342

Medium Primitives

Clock positions with Euclidean remainder

A clock face has twelve positions, numbered 0 through 11. Starting at start, move the hand by each value in steps: positive steps move forward, and negative steps move backward. Return the position after each step.

pub fn clock_positions(start: i32, steps: &[i32]) -> Vec<i32>

The output has one entry per step, in order. Do not include the initial position. Empty steps produce an empty vector. Both start and every step can be any i32 value, including i32::MIN and i32::MAX.

Every returned position must be in 0..=11. For example:

Start Steps Result
0 [-1] [11]
11 [1] [0]
3 [-15] [0]
-5 [0, 6] [7, 1]

Why % can leave the hand below zero

Rust's integer division rounds toward zero. Its % operator returns the corresponding remainder, which is zero or has the sign of the dividend (the left operand). With valid, representable division, the quotient and remainder satisfy (a / b) * b + a % b == a.

fn main() {
    let pairs: [(i32, i32); 4] = [(7, 2), (-7, 2), (7, -2), (-7, -2)];
    let results: Vec<_> = pairs.iter().map(|&(a, b)| (a / b, a % b)).collect();
    assert_eq!(results, [(3, 1), (-3, -1), (-3, 1), (3, -1)]);
    let full_turn = -12i32;
    assert_eq!(full_turn % 12, 0);
}

The starter therefore returns [-1] for start 0 and step -1. Negative inputs do not always fail: a full backward revolution still produces zero. Test partial backward moves as well as full revolutions.

Normalize with rem_euclid

For this clock, value.rem_euclid(12) always gives a position in 0..=11. It works for negative values too: (-1i32).rem_euclid(12) is 11. Normalize the starting position before processing the steps.

Be careful with addition. Reducing the result after hand + step does not prevent that addition from overflowing. For example, 11 + i32::MAX does not fit in i32, even though its clock position is just 6.

Reduce the step first. Then both terms are between zero and eleven, so their sum is at most twenty-two. Reduce that sum once more to obtain the position. Widening both terms to i64 before adding is another valid approach.

When the divisor is not a fixed positive constant

For a valid signed integer division, rem_euclid returns a nonnegative remainder smaller than the mathematical magnitude of the divisor. Avoid expressing that bound with i32::MIN.abs(), whose result cannot fit in i32.

Division by zero and i32::MIN / -1 panic, including in optimized builds. Rust also rejects the corresponding %, div_euclid and rem_euclid operations. In the remainder case the mathematical result would be zero, but the operation still treats the overflowing quotient as an error.

When a divisor may be invalid, checked methods let you handle that case:

fn main() {
    assert_eq!((-7i32).checked_rem_euclid(2), Some(1));
    assert_eq!(7i32.checked_div(0), None);
    assert_eq!(i32::MIN.checked_rem(-1), None);
    assert_eq!(i32::MIN.checked_rem_euclid(-1), None);
    assert_eq!(1i32.rem_euclid(i32::MIN), 1);
}

The fixed divisor 12 avoids those division errors in this exercise. Your remaining job is to keep the addition within range and append each new position to the output.