Given a list of newline-separated numbers, return the multiplication of the two numbers that add up to
Coming from other languages, one may be tempted to reach for
for loops. This results in some more manual work with respect to ownership and borrowing, so you might end up just cloning instead.
So we have a mutable
nums vec that we parse numbers into, and a loop for every combination of numbers. So refactoring our code from part 1 to part 2 means: adding a third for loop, remembering to add a third number and remembering to multiply a third number. The latter two being actions the type system doesn't help us remember to do: it will accept
num + num2 just as easily as
num + num2 + num3.
We can rectify the cloning, reduce our ownership battles, and make refactoring less error prone by using iterators.
input.lines() hands us an iterator we can keep using.
maping over the string to parse it, then producing the set of combinations of all numbers using itertools and .combinations. Note that in the for loop example we were also producing more pairs than we needed to, because for just the combinations we'd want to start each nested for loop at an offset.
.combinations approach allows us to more easily pass in configuration to our function, such as the number of items to pair in a combination. Combined with the usage of
.product(), we no longer have to manually maintain the list of added or multiplied numbers, reducing the possibility for human error when refactoring. In fact, refactoring from part 1 to part two only requires changing
While I like the
combinations approach the best for code maintainability and refactoring ease, itertools also offers
cartesian_product, which can be used as follows without worrying about borrowing.
or with a vec allocation
We have four examples above,
Here are the criterion benches for each, sorted.
|example||lower bound||best guess||upper bound|
|for-loops||1.0216 ms||1.0239 ms||1.0263 ms|
|cartesian-product-iter||2.5539 ms||2.5596 ms||2.5658 ms|
|cartesian-product-clone||21.010 ms||21.059 ms||21.111 ms|
|combinations||32.727 ms||32.832 ms||32.948 ms|
As shown, for loops are the fastest with the cartesian-product-iter example being second at a little over ~2.5x time (1ms vs 2.5ms). The clone version performs dramatically worse and the combinations example being the worst at 32ms.
note that we're really talking milliseconds here and unless you've measured and it matters, I'd still opt for the combinations approach, with the cartesian-product-iter a close second.