Problem overview
The function positive_squares receives a vector of integers, nums, which may be empty, with every number between -1000 and 1000. It returns a new vector with the square of every number above 0, in the order of the original numbers. Zero and the negative numbers are left out, and if nothing is above 0 the result is an empty vector.
For [3, -1, 0, 4] the answer is [9, 16]. For [-5, -2, 0] it is [], and for [1, 2, 3] it is [1, 4, 9].
Approach
There are two jobs: keep some numbers, and transform the ones that stay. An iterator chain does both. filter keeps the items for which its closure returns true, and map turns each remaining item into a new value. The order matters: filter first, then square. Squaring first would turn -3 into 9, which then passes the "above 0" test and ends up in the answer.
That wrong order really does give a wrong result. On [3, -1, 0, 4], squaring first and then keeping values above 0 produces [9, 1, 16]: the 1 comes from -1.
Step by step
- Start from
nums.into_iter(). This hands over the numbers themselves and uses up the vector, which is fine because the function returns a new one. - Filter with
|num| *num > 0. The closure receives a reference to each item, so the test reads*num. Without the star the compiler reports mismatched types, because a reference is being compared with a number. - Map with
|num| num * num. Afterfilter, the items passing through are plain numbers again, so no star is needed here. - Collect into a
Vec<i32>namedsquares. The type after the name tellscollect()what to build. - Return
squares. When no number is above 0, the vector is empty, and that is the right answer.
The solution in Rust
impl Solution {
fn positive_squares(nums: Vec<i32>) -> Vec<i32> {
let squares: Vec<i32> = nums.into_iter().filter(|num| *num > 0).map(|num| num * num).collect();
return squares;
}
}
The numbers are at most 1000 in size, so a square is at most 1,000,000 and fits easily in an i32. An empty input vector just produces an empty output: the chain has nothing to process.
Why the extra star
The closures in a chain receive different things. map gets each item by value, so with into_iter() it gets a plain i32. filter only inspects an item and must hand it on, so it passes the closure a reference to the item. That is why one closure uses *num and the other does not, even though both are named num.
Another way
The same result with a loop and an if, which is the form to reach for when the logic grows beyond two small steps:
let mut out = Vec::new();
for num in nums {
if num > 0 {
out.push(num * num);
}
}
On [3, -1, 0, 4] it also gives [9, 16]. The iterator chain says the same thing in one line: keep, then transform, then gather.
Testing
The three examples and an empty vector give:
[9, 16]
[]
[1, 4, 9]
[]
Edge cases and common mistakes
- Zero. The test is strictly above 0, so 0 is dropped. Writing
>= 0would add a 0 to the answer. - Squaring before filtering, as shown above.
- Forgetting
collect(), which leaves an iterator instead of a vector.
