I haven't been doing nearly as much Mathematica as I really should for the past few months, and in order to get my head back in the game I've been using it to run roughshod over easy Project Euler problems. In this case, one involved finding Pythagorean triples which totaled to 1000. The number of possibilities is small enough that there's no reason not to brute-force:
(triples = IntegerPartitions[1000, {3}]) // Length
(* 83333 *)
Remembering that IntegerPartitions
lists the parts from largest to smallest, Select
should work, and it does, but way more slowly than I expected!
Select[triples,
Apply[Function[{c, b, a}, a^2 + b^2 == c^2]]] // AbsoluteTiming
(* {0.257232, {{425, 375, 200}}} *)
In the grand scheme of things, a quarter-second isn't a long time, but it's still taking microseconds per list element, which seems like a lot. Switching to Cases
is a little faster:
Cases[triples, {c_, a_, b_} /; c^2 == a^2 + b^2] // AbsoluteTiming
(* {0.144264, {{425, 375, 200}}} *)
Still, a seventh of a second is a long time with a modern computer. Heck, writing my own Do
-based solution is faster:
Reap[
Do[
If[MatchQ[triple, {c_, a_, b_} /; c^2 == a^2 + b^2],
Sow[triple]],
{triple, triples}]][[-1, 1]] // AbsoluteTiming
(* {0.176861, {{425, 375, 200}}} *)
After a few minutes tweaking, I did come up with something a couple orders of magnitude faster, but it's certainly not the most perspicuous code I've ever written:
Pick[
triples,
{1, -1, -1}.Transpose[triples^2], 0]
(* {0.006377, {{425, 375, 200}}} *)
I assume this a matter of packing and unpacking arrays, but the whole thing is way off from my intuitions about Mathematica performance; I'd expect Select
to be a bit slower than the optimized Pick
-based solution, but nothing like this.
(Even transposing before squaring makes a substantial difference in performance.)
What's going on?
FWIW, this is with Mathematica 11.1.0 on a slightly elderly MacBook Pro.
{a, b, c} = Transpose[triples]; BoolPick[triples, a^2 == b^2 + c^2]
. It returns{{425, 375, 200}}
. It takes about 0.0014 s on a 2014 MacBook Pro, as measured by RepeatedTiming in version 11.1. In spirit, it is very similar to your last solution. The role of the package is to provide a simple, human-readable notation. $\endgroup${a, b, c} = Transpose[triples];
alone takes 0.013 seconds. What BoolEval transforms the computation to is1 - Unitize[a^2 - b^2 - c^2]
, as can be seen withClear[a, b, c]; BoolEval[a^2 == b^2 + c^2]
. $\endgroup$IntegerPartitions
does not return a packed array. Manually packing it speeds up these types of vectorized calculations quite significantly. Now{a, b, c} = Transpose[triples];
is down to 0.0004 s, the BoolPick stays the same, and your Pick is down to 0.0014 s. The Select is up to 0.30 s from 0.25 s (due to unpacking). $\endgroup${a,b,c} = Transpose[triples];
with a 2x slowdown in 11.1.0 (compared to the other two versions) from 0.007 to about 0.014. $\endgroup$