6
$\begingroup$

I do not think my explicit code will be helpful to illustrate it so here is the "barebones" silly example:

AbsoluteTiming[K = {};
 M = {};
 L = {};
 Do[m = RandomReal[{-10, 10}] ;
  If[m > 0, AppendTo[K, m], 
   If[m == 0, AppendTo[M, m], AppendTo[L, m]]], 100000] ;
 
 K;
 M;
 L;]

I used Apppend originally since that is the only command I knew and I had like 6 sets to append to and a lot of nested If's. I am now optimizing my code, I really need to make it faster, and the first thing I am attempting is fixing these AppendTo. I do not see how to use table so that I get a few lists depending on some if statements. However, I came up with the following code using Sow and Reap:

    AbsoluteTiming[JJ = Reap[Reap[Reap[Do[m = RandomReal[{-10, 10}] ;
       If[m > 0, Sow[m, 1], If[m == 0, Sow[m, 2], Sow[m, 3]]], 10], 
      1], 2], 3] 
   K = Flatten[Flatten[JJ[[2]], 2][[2]]];
 M = Flatten[JJ[[2]], 1][[2]];
 L = Flatten[JJ[[2]], 1][[3]];]

Which seems to be around 80 000 times faster! That is great. Is there any other way to improve this? I do not like the Reap and Sow commands as I will need to deal with like 7 tags and somehow figure out how to get to them from the huge Reap.

Any suggestions would be helpful. Also if you have any hints on how to make nested If's more manageable visually (so that I know which If statement I am in) I would appreciate that.

Edit:

    AbsoluteTiming[yourList = RandomReal[{-10, 10}, 100000];
 f[m_] := Which[m > 0, "positive", m == 0, "zero", m < 0, "negative"];
 grouped = GroupBy[yourList, f];
 grouped["positive"];
 grouped["zero"];
 grouped["negative"];]
$\endgroup$
5
  • $\begingroup$ By the way, you do not need multiple Reaps—one Reap captures every Sow inside! $\endgroup$
    – thorimur
    Jan 22, 2022 at 2:45
  • $\begingroup$ @thorimur the problem is, If I do that, the empty list does not seem to appear. And a lot of the lists I will be dealing with are potentially empty. $\endgroup$
    – 2132123
    Jan 22, 2022 at 2:47
  • 2
    $\begingroup$ Instead of Reap[Reap[Reap[expr, patt1], patt2], patt3] you can use Reap[expr, {patt1, patt2, patt3}]. Each reaped expression should appear then in a separate list, in the order the tags appear. Unless I'm misunderstanding which empty list you want to appear? $\endgroup$
    – thorimur
    Jan 22, 2022 at 3:26
  • $\begingroup$ Tip: Avoid starting variables with a capital, pt. 4 $\endgroup$
    – Michael E2
    Jan 22, 2022 at 6:00
  • $\begingroup$ @thorimur that fixed it! thank you for the nice tip $\endgroup$
    – 2132123
    Jan 22, 2022 at 14:26

2 Answers 2

10
$\begingroup$

Generate the whole list in one go, then group the values using GroupBy with an appropriate "discrimination function".

For instance, in your simple case let's assume that all values are first stored in a list called yourList:

yourList = RandomReal[{-10, 10}, 100000];

Let's create a function $f$ that, when applied to any of your values, "classifies" them according to each case of interest to you:

ClearAll[f]
f[m_] := Which[
    m > 0, "positive",
    m == 0, "zero",
    m < 0, "negative"
  ]

In this simple case the above function is pretty similar to Sign[m], but I'm building it with Which anyway so you can see how to generalize this approach to your own categories for which a built in may not be available.

Finally, use GroupBy to group the elements of yourList according to the output of function $f$ applied to each element. This returns an association whose keys are the unique values of $f$ that are found when it is applied to each element of your list, i.e. "positive", "zero", or "negative".

grouped = GroupBy[yourList, f];

From the association grouped you can extract each sublist by querying with the appropriate key. For instance, if you want the subset of positive numbers, then use grouped["positive"].

$\endgroup$
3
  • 2
    $\begingroup$ I sometimes use Lookup because it has a default: E.g., {k, m, l} = Lookup[GroupBy[RandomReal[{-10, 10}, 100000], Sign], {-1, 0, 1}, {}]; $\endgroup$
    – Michael E2
    Jan 22, 2022 at 6:26
  • $\begingroup$ A side question, since this is faster than Reap and Sow, are there instances where Reap and Sow are actually preferred? $\endgroup$
    – 2132123
    Jan 22, 2022 at 14:54
  • $\begingroup$ @2132123 Reap and Sow can be extremely useful, just not in this context. For instance, off the top of my head, it's very nice to use Sow from inside a function to get intermediate results of calculations that would otherwise not be returned as output. $\endgroup$
    – MarcoB
    Jan 23, 2022 at 16:45
5
$\begingroup$

You construct a list and select elements that fulfill some conditions. This is what Select is for:

Clear[m, k, l]
m = RandomReal[{-10., 10.}, {100000}];
k = Select[m, Positive]
l = Select[m, Negative]

As for the elements equal to zero. Since all zeros are the same, there is no need to select them specifically. It is enough to Count them, e.g., Length[m] - Length[k] - Length[l]. But do not expect too many of them, the probability that a random real number is zero is exactly zero mathematically and very close to zero numerically.

$\endgroup$

Your Answer

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

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