4

My question is very simple but I am not able to find the optimal solution. What I am trying to do is to have a function that give me the position in a list at which the value at position i is bigger than at position i+1. I need only the first of such term since afterwards, I will commute the two via another a function and then test again if everything is ordered. Basically, I am searching for

In[] : findpos[{1,2,3,4,7,4}]
Out [] : 5

My current solution is the following

findpos[a_] := 
Module[{index = {}}, 
For[i = 1, i <= Length[a] - 1, i++, If[a[[i]] > a[[i + 1]], index = Append[index, i]]]; 
Return[First[index]]]

which gives

findpos[{1,2,3,4,7,4}]
5

I will have to call this function maybe many time, so I would like something which run fast and I am almost convinced that a module + a For loop will not be the optimal solution speaking of computation time. I looked at many other questions somehow related ; especially this one How to find the position of elements in a list satisfying criteria

I think I am searching for something of the form

Position[{2, 4, 6, 8, 10}, _?(# > 7 &)]     

But who compare two elements instead of each element to a scalar. I tried to adapt it to my case without success. I tried something of the form

Position[{4, 5, 2, 1}, _?(#1 >= #2 &)]

Any suggestion ?

Ezareth
  • 379
  • 1
  • 9

2 Answers2

6

A couple of these methods were incorrect, one terribly so. I believe they are now corrected.


Here are some different ideas:

f1 = SequencePosition[#, {a_, b_} /; a > b, 1][[1, 1]] &;
f2 = Select[Range@Length@#, x \[Function] #[[x]] > #[[x + 1]], 1][[1]] &;
f3 = Module[{i}, LengthWhile[#, # >= i && (i = #; True) &]] &;
f4 = Position[UnitStep@Differences@#, 0, 1, 1][[1, 1]] &;
f5 = SparseArray[UnitStep@Differences@#, Automatic, 1]["AdjacencyLists"][[1]] &;

#[{1, 2, 3, 4, 7, 4}] & /@ {f1, f2, f3, f4, f5}
{5, 5, 5, 5, 5}
#[{2, 2, 5, 4, 7, 4}] & /@ {f1, f2, f3, f4, f5}
{3, 3, 3, 3, 3}

And a beautifully terse one from Alexey Popkov:

f6 = Length@First@Split[#, LessEqual] &;

Benchmarking

A benchmark with the position one third of the way through the list.

Including kglr's functions, and now on a packed array where performance can be highest.

Needs["GeneralUtilities`"]

BenchmarkPlot[
  {f1, f2, f3, f4, f5, f6, fa, fb, fc, fd}, 
  ReplacePart[Sort@RandomReal[1, #], ⌈#/3⌉ -> 2`] &,
  10,
  ImageSize -> 500
]

enter image description here

Shootout

It seems that the old warhorse SparseArray still rules the noncompiled methods. Let's compare it to a WVM compiled f4:

f4C = Compile[{{x, _Real, 1}}, Position[UnitStep@Differences@x, 0][[1, 1]]];

BenchmarkPlot[
  {f4, f5, f4C},
  ReplacePart[Sort@RandomReal[1, #], ⌈#/3⌉ -> 2`] &,
  10
]

enter image description here

So it seems f5 and f4C are pretty similar once we get past a constant factor in f5. f4C is probably easier for most people to understand, and it might be made faster still with compilation to C.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 1
    f6 = Length@First@Split[#, Less] &; – Alexey Popkov Mar 12 '17 at 10:43
  • @AlexeyPopkov Are you not going to post that as an answer? – Mr.Wizard Mar 12 '17 at 10:43
  • It is better if you include this in yours. – Alexey Popkov Mar 12 '17 at 10:44
  • @Alexey With credit, I will. – Mr.Wizard Mar 12 '17 at 10:44
  • If you need all the positions where the next list entry is smaller, Alexey's method can be generalized easily as FoldList[Plus, Length /@ Split[#, Less]] &. – evanb Mar 12 '17 at 11:57
  • @evanb I think all of these methods beside LengthWhile would be easily generalized for that case, typically making the code shorter in the process. – Mr.Wizard Mar 12 '17 at 12:01
  • 1
    @Mr.Wizard For an input of {1, 2, 5, 4, 7, 4}, the functions return {3, 3, 5, 3, 5}. Something seems to be incorrect. – Anjan Kumar Mar 12 '17 at 12:08
  • @Anjan I am feeling really stupid right now. I really don't know what i was thinking on some of these, other than I wasn't. – Mr.Wizard Mar 12 '17 at 12:18
  • @AnjanKumar would you kindly check to see if my code is working now? – Mr.Wizard Mar 12 '17 at 12:36
  • @AlexeyPopkov I think we need LessEqual in that function and in my latest edit I made this change, but clearly I am not thinking too well today so please check my logic on that as well. – Mr.Wizard Mar 12 '17 at 12:37
  • @Mr.Wizard They all work now :). Thanks for enlightening us with your solutions. – Anjan Kumar Mar 12 '17 at 12:38
  • @Mr.Wizard You are correct, I didn't read the question sufficiently carefully. BTW, we can achieve additional speedup for f4 by adding 1 as fourth argument to Position or replacing it with FirstPosition. – Alexey Popkov Mar 12 '17 at 13:26
  • @Anjan That is kind of you. I have rewritten f3 to work the way that I had originally envisioned, though I got it terribly wrong the first time. (1) its current form might interest you (2) if I got it wrong again please let me know! – Mr.Wizard Mar 12 '17 at 13:26
  • @Alexey That seems obvious after you say it. I'll amend the answer yet again. Thank you. – Mr.Wizard Mar 12 '17 at 13:28
4

Also

ClearAll[fa, fb, fc, fd]
fa = Position[Partition[#, 2, 1], _?(Greater @@ # &), 1, 1] //. {x_} :> x &;
fb = 1 + LengthWhile[Range@Length@#, Function[{x}, #[[x]] <= #[[x + 1]]]] &;
fc = Block[{i = 1}, While[#[[i]] <= #[[i + 1]], i++]; i] &
fd = FirstPosition[Differences@#,_?Negative][[1]]&

#@{1, 2, 3, 4, 7, 4} & /@ {fa, fb, fc, fd}

{5, 5, 5, 5}

#@{1, 2, 5, 4, 7, 4} & /@ {fa, fb, fc, fd}

{3, 3, 3, 3}

Update: The functions fb, fc and fd above work in cases where a position that satisfies condition exists in the input list. To handle the general case where the output may be empty, they can be modified as follows:

fb2 = 1 + LengthWhile[Range[Length[#]-1], Function[{x}, #[[x]] <= #[[x + 1]]]] /. 
      Length[#] -> {} &;
fc2 = Block[{i = 1}, While[i < Length@# && #[[i]] <= #[[i + 1]], i++];
     i /. Length[#] -> {}] &;
fd2 = FirstPosition[Differences@#, _?Negative][[1]] /. "NotFound" -> {} &;

#@{1, 2, 3, 4, 7, 8} & /@ {fa, fb2, fc2, fd2}

{{}, {}, {}, {}}

kglr
  • 394,356
  • 18
  • 477
  • 896