22

As a simple example of what I would like to do, suppose I have a list a of all real numbers. I would like to perform a simple check to see if some element of a is positive. Of course, I could do this with a simple loop, but I feel as if Mathematica would have a more efficient way of doing this, in the spirit of functional programming. Is there, or do I just have to do this with a clumsy loop:

test=False; For[counter=1;counter<=Length[a];counter++;If[a[[counter]]>0,test=True;];];
rm -rf
  • 88,781
  • 21
  • 293
  • 472
Jonathan Gleason
  • 697
  • 5
  • 11
  • 1
    A general guidance for how to "map" loop constructions to functional ones is found in this question, see "alternatives to loops" – FredrikD Jul 24 '12 at 05:41
  • Thanks for the Accept. You've been away some while. – Mr.Wizard Mar 08 '13 at 03:24
  • @Mr.Wizard Sorry about that. Quite honestly, for some reason I thought I had accepted an answer awhile ago, and didn't realize that was not the case until yesterday when I got a notification that I had received a badge for the question. My bad. – Jonathan Gleason Mar 08 '13 at 18:46
  • 1
    Jonathan, my comment wasn't meant to complain. Frankly I didn't expect to see you on the site again as you had been away for a while. I appreciated you taking the effort to Accept an answer despite the fact that you're apparently not spending a lot of time on the site these days. So thanks again, and welcome back. – Mr.Wizard Mar 08 '13 at 22:29

9 Answers9

29

If I understand you correctly, simply test if the maximum value in the list is Positive:

Positive @ Max @ a

Speed comparison with other methods that were posted:

timeAvg = 
  Function[func,
    Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 15}],
    HoldFirst];

a = RandomInteger[{-1*^7, 2}, 1*^7];

MemberQ[a, _?Positive] // timeAvg

Total@UnitStep[-a] =!= Length@a // timeAvg

Positive@Max@a // timeAvg

0.593

0.0624

0.01148


Early-exit methods

Although very fast, especially with packed lists, the method above does scan the entire list with no possibility for an early exit when a positive elements occurs near the front of the list. In that case a test that does not scan the entire list may be faster, such as the one that R.M posted. Exploring such methods I propose this:

! VectorQ[a, NonPositive]

Unlike MemberQ, VectorQ does not unpack a packed list.

Timings compared to MemberQ and Max, first with an early positive appearance:

SeedRandom[1]
a = RandomReal[{-1*^7, 1000}, 1*^7];

Positive @ Max @ a        // timeAvg
! VectorQ[a, NonPositive] // timeAvg
MemberQ[a, _?Positive]    // timeAvg
0.008736

0.00013984

0.2528

(Most of the MemberQ time is spent unpacking the list.)

Then no positive appearance (full scan):

a = RandomInteger[{-1*^7, 0}, 1*^7];

Positive @ Max @ a        // timeAvg
! VectorQ[a, NonPositive] // timeAvg
MemberQ[a, _?Positive]    // timeAvg
0.01148

1.544

2.528

Finally a mid-range appearance of a positive value in an unpacked list:

a = RandomReal[{-50, 0}, 1*^7];
a[[5*^6]] = 1;

Positive @ Max @ a        // timeAvg
! VectorQ[a, NonPositive] // timeAvg
MemberQ[a, _?Positive]    // timeAvg
0.212

0.702

1.045

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
22

I think the canonical way would be to use an "any" function which you can find in this question. Using a variant of my answer, you can use

MemberQ[list, _?Positive]

to check if any element is positive.

rm -rf
  • 88,781
  • 21
  • 293
  • 472
18
l = RandomChoice[Range[-100, 1], 50];

Simplest to understand is

Or @@ Positive[l]

Perhaps faster for long lists is

Total@UnitStep[-l] =!= Length@l
Rojo
  • 42,601
  • 7
  • 96
  • 188
10

The fastest I could come up with, using the undocumented Compile`GetElement for indexing (it's the fastest even without):

f = Compile[
  {{l, _Integer, 1}},
  Module[
   {max = -1},
   Do[
    If[max < Compile`GetElement[l, i], max = Compile`GetElement[l, i]],
    {i, 1, Length@l}];
   max > 0
   ],
  CompilationTarget -> "C"
  ]

Using timeAvg from Mr.Wizard's answer,

MemberQ[a, _?Positive] // timeAvg
Total@UnitStep[-a] =!= Length@a // timeAvg
Positive@Max@a // timeAvg
f[a] // timeAvg
(*
1.38034
0.114101
0.0187749
0.00830531
*)
acl
  • 19,834
  • 3
  • 66
  • 91
  • No fair! Blasted v8 compile-to-C... – Mr.Wizard Jul 30 '12 at 19:44
  • I get error messages for RandomReal input. Should timing include Compileoverhead as well? – Yves Klett Jul 31 '12 at 06:47
  • 2
    @Yves he compiled for _Integer values; you'd need to compile a separate function for _Real values, then craft a function to select between them, as well as intelligently handle lists that are not packed and may contain mixed types. I don't think compilation overhead should be included in timings unless it uses pre-calculation. – Mr.Wizard Jul 31 '12 at 11:03
  • @YvesKlett As Mr.W says it's compiled for Integers, and would need to be recompiled for reals. I don't know if timing should include compilation overhead; to be honest, I offer this is what I'd do if I needed it to be as fast as possible. It's clearly not leveraging Mathematica's strengths, nor is particularly pretty or clever. – acl Jul 31 '12 at 13:25
  • How about a short-circuited version of the loop that keeps testing Compile`GetElement[l, i] until it fins something positive? (i.e., use Break[]) somewhere. – J. M.'s missing motivation Apr 17 '13 at 04:41
7

This is even faster than acl's code, for data with positive elements appearing early on, because it stops as soon as it finds a positive.

ff = Compile[{{l, _Real, 1}},
Module[{i = 1, n = Length@l},
While[Compile`GetElement[l, i] <= 0. && i <= n, i = i + 1];
i <= n], "RuntimeOptions" -> "Speed", CompilationTarget -> "C"];

Since the OP specifies real numbers I've changed acl's function to take reals:

f = Compile[{{l, _Real, 1}}, 
Module[{max = -1.},
Do[If[max < Compile`GetElement[l, i], max = Compile`GetElement[l, i]], {i, 1, Length@l}];
max > 0], "RuntimeOptions" -> "Speed", CompilationTarget -> "C"];

Here is some timing data where I've inserted a single positive element into the list at varying positions:

b = RandomReal[{-1*^7, 0}, 1*^7];

timedata = Table[
a = b; a[[10^j]] = 1.0; {10^j,
{MemberQ[a, _?Positive] // timeAvg,
Total@UnitStep[-a] =!= Length@a // timeAvg,
Positive@Max@a // timeAvg,
f[a] // timeAvg,
ff[a] // timeAvg}}
, {j, 1, 7}];

ListLogLogPlot[Transpose[Thread /@ timedata], Joined -> True]

enter image description here

Simon Woods
  • 84,945
  • 8
  • 175
  • 324
  • This is surely the optimal way to approach this problem (though likely overkill for the OP). Unfortunately I cannot test it in v7, and as a rule I don't vote for v8-only answers. Not because such answers are not good, but because I think it is important to test things, and it would be unfair to vote for some v8 answers and not others. – Mr.Wizard Jul 31 '12 at 12:08
  • Somehow after @Mr.Wizard's review no one likes my approach. I'll go and have a proper cry ;-) – Yves Klett Jul 31 '12 at 12:14
  • @Mr.Wizard, I agree that it's overkill :-) Especially as the OP was explicitly trying to avoid clumsy loops. – Simon Woods Jul 31 '12 at 12:34
  • @YvesKlett, it's not so much that I don't like your answer, I just thought it was essentially the same algorithm Rojo's UnitStep approach. By the way, why Tr and not Total ? – Simon Woods Jul 31 '12 at 12:39
  • Tr is shorter ;-) although similar in spirit the timing is different for Clip and UnitStep approaches. – Yves Klett Jul 31 '12 at 17:38
  • This is what I've thought first. I'm amazed it is 5th answer not 2nd. @Mr.Wizard It deserves vote for a method :) I would say that testing all elements when OP wants to know if any is positive seems an overkill ;) – Kuba Feb 02 '14 at 10:19
  • @Kuba, the OP was specifically asking for a functional method, so it's not surprising that this procedural loop is not highly voted :-) – Simon Woods Feb 02 '14 at 11:53
6

To add a bit variety, you could try:

l = RandomChoice[Range[-100, 1], 5000000];

Tr[Clip[l, {0, Infinity}]] > 0

The timing for different methods of input shows that, unsurprisingly, some solutions are very dependent on the average number of positive elements and others not so much. @Mr.Wizard´s Positive@Max@a and @acl´s compiled f[a] seem to win every time.

f = Compile[{{l, _Integer, 1}}, 
   Module[{max = -1}, 
    Do[If[max < Compile`GetElement[l, i], 
      max = Compile`GetElement[l, i]], {i, 1, Length@l}];
    max > 0], CompilationTarget -> "C"];

timeAvg = 
  Function[func, 
   Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 
     15}], HoldFirst];

a = RandomInteger[{-1*^7, 1}, 1*^7];

MemberQ[a, _?Positive] // timeAvg
Or @@ Positive[a] // timeAvg
Total@UnitStep[-a] =!= Length@a // timeAvg
Tr[Clip[a, {0, Infinity}]] > 0 // timeAvg
Positive@Max@a // timeAvg
f[a] // timeAvg
4.072   
0.546     
0.0716     
0.04056     
0.01748     
0.01048
a = RandomInteger[{-1*^7, 1*^7}, 1*^7];

MemberQ[a, _?Positive] // timeAvg
Or @@ Positive[a] // timeAvg
Total@UnitStep[-a] =!= Length@a // timeAvg
Tr[Clip[a, {0, Infinity}]] > 0 // timeAvg
Positive@Max@a // timeAvg
f[a] // timeAvg
0.561     
0.359     
0.078     
1.748     
0.01684     
0.01048
Yves Klett
  • 15,383
  • 5
  • 57
  • 124
  • If you can find a case where this is faster than Positive @ Max @ list you'll get my vote. If not it's just considerable complication, IMHO. – Mr.Wizard Jul 29 '12 at 19:02
  • @Mr.Wizard It definitely is a complication and I do not hold high hopes for getting that vote. – Yves Klett Jul 29 '12 at 21:37
  • @YvesKlett, the reason for the much longer timing for your code in the second example is that you are adding up a large number of large integers. If you set the upper bound of Clip to 1 instead of infinity it is quite a bit quicker. – Simon Woods Jul 31 '12 at 14:24
  • @SimonWoods I thought so too but the timings are not really conclusive. Using Reals for the bounds masssively slows Clip down, though. – Yves Klett Jul 31 '12 at 17:39
3

Here is a solution that uses LengthWhile:

test[list_] := Length@list != LengthWhile[list, NonPositive]

This test works well for lists that contain a positive element near the beginning of the list.

j--
  • 598
  • 3
  • 11
2

Since V 10.0 we can also use AnyTrue:

AnyTrue[{-1, -1, 1}, # > 0 &]

True

AnyTrue[{-1, -1}, # > 0 &]

False

eldo
  • 67,911
  • 5
  • 60
  • 168
2

A point-free style

{-1, -1, 1} // AnyTrue[Positive]
AsukaMinato
  • 9,758
  • 1
  • 14
  • 40