1
$\begingroup$

I have a function, which takes the numeric values d1,d2,p1,p2,s as input, and should return:

  • {d1,d2} if d1 <= p1*s and d2 <= p2*s
  • {d1,s-d1} if d1 <= p1*s and d2 > p2*s
  • {s-d2,d2} if d1 > p1*s and d2 <= p2*s
  • {p1*s,p2*s} if d1 > p1*s and d2 > p2*s

An easy way to implement this function is the following:

f[d1_,d2_,p1_,p2_,s_] := Which[d1 <= p1*s && d2 <= p2*s, {d1,d2}, d1 <= p1*s && d2 > p2*s, {d1,s-d1}, d1 > p1*s && d2 <= p2*s, {s-d2,d2}, True, {p1*s,p2*s}]

However, instead of only calling this function for one set of values, I want to call this function for a list of values. Specifically, I have given a list Px that consists of pairs {p1,p2}, a list Sx that consists of values s, and a list Dx that consists of values {d1,d2}.

In an effort to make the implementation more efficient, I did the following:

checkCase = Thread[#[[1]] <= #[[2]]*#[[3]]] & /@ Transpose@{Dx, Sx, Px};
functionValue = Flatten[Which[
     #[[1]] == {True, True}, #[[2]], 
     #[[1]] == {True, False}, {#[[2]][[1]], #[[3]] - #[[2]][[1]]},
     #[[1]] == {False, True}, {#[[3]] - #[[2]][[2]], #[[2]][[2]]},
     True, #[[4]]*#[[3]]
     ] & /@ Transpose@{checkCase, Dx, Sx, Px}];

However, this is of course a very basic speed-up, just using the map-operator. I feel like there are is much more potential in speeding-up the above computation over the three lists, using vertorized operators, and would be very grateful if someone would be able to point out what functions and/or operators would be useful here.

$\endgroup$
2
  • $\begingroup$ You can use your function f on your lists by combing them and then MapApplying. Eg: Dx = RandomInteger[{0, 9}, {3, 2}]; Px = RandomInteger[{0, 9}, {3, 2}]; Sx = RandomInteger[{0, 9}, 3]; input = Flatten /@ Thread[{Dx, Px, Sx}]; f @@@ input $\endgroup$
    – ydd
    Aug 23, 2023 at 15:37
  • 1
    $\begingroup$ Perhaps BoolEval, written by our very own @Szabolcs, could be helpful here. $\endgroup$
    – MarcoB
    Aug 24, 2023 at 1:40

1 Answer 1

1
$\begingroup$

With functions such as UnitStep, you can apply your condition in a Listable fashion to enjoy its performance, like below:

Here I chose 1 million random numbers for each column:

Block[{d1, d2, p1, p2, s, temp1, temp2, temp1not, temp2not, result1, 
  result2, result3, result4},

 {d1, d2, p1, p2, s} = RandomInteger[10, {5, 1000000}];

 temp1 = UnitStep[p1*s - d1];
 temp2 = UnitStep[p2*s - d2];
 temp1not = 1 - temp1;
 temp2not = 1 - temp2;
 
 result1 = Pick[Transpose@{d1, d2}, temp1*temp2, 1];
 result2 = Pick[Transpose@{d1, s - d2}, temp1*temp2not, 1];
 result3 = Pick[Transpose@{s - d2, d2}, temp1not*temp2, 1];
 result4 = Pick[Transpose@{p1*s, p2*s}, temp1not*temp2not, 1];
 
 ]

(* Out: {0.131124, Null} *)

Your function on similar data was around 4 times slower on my machine with Mathematica 13.3.1:

Block[{d = RandomInteger[10, {1000000, 5}]},
  f @@@ d;
  ] // AbsoluteTiming

(* Out: {3.84095, Null} *)

We can squeeze our code further if we Pick elements first before calculating the final expression like s-d2, d2 or by using Compile* functions in Mathematica.

Since version 12.1, we can execute Julia directly from the notebook. In computation-intensive workflows, it's a good reference to compare. In this case, a similar approach was 5 times faster than the provided solution.

Here is the code in Julia:

using Random
n= 1000000
d1 = rand(Int, n)
d2 = rand(Int, n)
p1 = rand(Int, n)
p2 = rand(Int, n)
s = rand(Int, n)

temp1 = d1 .<= p1.*s
temp2 = d2 .<= p2.*s

i = temp1 .* temp2
result1 = [d1[i] d2[i]]

i = temp1 .* .!temp2
result2 = [d1[i] s[i]-d2[i]]

i = .!temp1 .* temp2
result3 = [s[i]-d2[i] d2[i]]

i = .!temp1 .* .!temp2
result4 = [p1[i].*s[i] p2[i].*s[i]]

The main part was executed in 0.021768 seconds (second run).

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.