Skip to content

← Ground Rules: Values, Types, Control Flow step 18 of 24

Easy Primitives

Return minimum, maximum and mean as a tuple

Complete the function so it returns (minimum, maximum, mean) for a slice:

pub fn min_max_mean(values: &[i32]) -> (i32, i32, f64)
Input Result
[1, 2, 3] (1, 3, 2.0)
[1, 2] (1, 2, 1.5)
[-4, -2] (-4, -2, -3.0)
[5] (5, 5, 5.0)
[] (0, 0, 0.0)

The empty result is this function's convention: an empty collection has no ordinary minimum, maximum or arithmetic mean. Count every element, including duplicates, when computing the mean. Do not round it to an integer.

The starter already handles empty input and calculates the minimum, maximum and sum. Fix its final expression so the third tuple field has the promised type and keeps the fractional part of the mean.

A tuple carries several values

A tuple has a fixed number of fields, each with its own type. The fields may have different types, as they do in (i32, i32, f64). Their order matters: returning maximum before minimum would violate this function's contract.

let pair: (i32, f64) = (7, 1.5);
assert_eq!(pair.0, 7);
assert_eq!(pair.1, 1.5);
let single = (7,); // The comma makes this a one-element tuple.
assert_eq!(single.0, 7);

Tuple fields use fixed positions such as .0 and .1. A runtime variable cannot replace the field number, and a nonexistent field is a compile error. () is the empty tuple, also called unit.

Destructuring assigns names to the fields in one binding:

let values = [1, 2, 3];
let (min, max, mean) = min_max_mean(&values);
assert_eq!((min, max, mean), (1, 3, 2.0));
let (_, _, mean_only) = min_max_mean(&values);
assert_eq!(mean_only, 2.0);

The left side is a pattern. Each _ ignores that field. Positional access and destructuring are both useful; choose names when they make the result easier to read. A struct is another option when named fields would clarify an API, but this exercise asks for a tuple.

Divide in floating point

The starter ends with:

(min, max, sum / values.len())

sum is i64, while the length is usize. Rust does not provide that mixed integer division. Converting the length to i64 alone would still produce an integer, not the f64 required by the return type.

Convert both operands to f64 before dividing. For [1, 2], integer division would truncate 3 / 2 to 1; converting that result afterward cannot recover 1.5. Negative integer division also truncates toward zero.

Keep the wider accumulator

The sum can exceed i32 even though every input fits. Two values equal to i32::MAX sum to 4294967294, for example. An i32 accumulator would overflow, whereas the supplied i64 accumulator holds that sum exactly.

Convert each value before adding it to the wider sum. Widening a result after an overflowing addition is too late. Exercise inputs have running sums that fit in i64; a wider accumulator is not unlimited protection for arbitrarily large inputs.

Converting a large integer to f64 can round, and the mean may not have an exact binary floating-point representation. The grader allows a numerical tolerance. Return the calculated mean without extra decimal rounding.