3
$\begingroup$

I have a nested list of {x,y,z} and I want to find out the values of x and y where z is minimum. I can write a nested For loop and do it

mya = {{0, 1, 10}, {1, 1, 20}, {0, 2, 5}, {1, 2, 15}}

For[i = 1, i <= Length[mya[[All, 3]]], i++, 
If[mya[[All, 3]][[i]] == Min[mya[[All, 3]]], Print[mya[[i]]]]]

I get the desired output:

{0, 2, 5}

I know this problem is simple, but if someone can tell me a more elegant way to do it, it will be helpful as I wanna do it for a very large list.

$\endgroup$
3
  • $\begingroup$ Cases[mya, x_ /; x[[3]] == Min[mya[[All, 3]]]] is another option $\endgroup$
    – Nasser
    Feb 14, 2014 at 12:42
  • 1
    $\begingroup$ @Nasser : I posted a similar answer. Concerning your solution, I thought the part Min[mya[[All, 3]]] is evaluated Length@mya times. It may be better to let it only evaluate once as I did. $\endgroup$
    – Yi Wang
    Feb 14, 2014 at 12:49
  • $\begingroup$ I have marked this question a duplicate because I believe that any method that works for maximum can be directly adapted for minimum, making the solutions effectively identical. (This question will remain as pointer.) If anyone disagrees with this action please leave a comment. $\endgroup$
    – Mr.Wizard
    Jul 26, 2014 at 23:39

5 Answers 5

6
$\begingroup$

I think @halirutan's answer is quite nice and clean. Nevertheless just give an alternative one:

findLastMin[mat_] := Cases[mat, {__, Min@mat[[All, -1]]}]

findLastMin[mya]

{{0, 2, 5}}

There is additional {...} outside the desired output by the OP, because if there are multiple equal minimal values, it returns them all.

$\endgroup$
7
$\begingroup$

How about using SortBy to sort your list by the last element and then take the first entry?

First[SortBy[mya, Last]]
(* {0, 2, 5} *)

A simple iterative approach to go through your list exactly once and remember the minimum element can be written as

Block[{min = First[mya]},
 Do[If[Last[min] > Last[elm], min = elm], {elm, Rest[mya]}];
 min
]

Although my tests showed that this is a bit slower (about 2 seconds for 10^7 elements) as the first approach.

An faster approach then the two above is to first extract the minimum of all z-values and then go through the list until you hit the first match

Block[{min = Min[Last[Transpose[mya]]]},
 Do[If[Last[elm] === min, Return[elm]], {elm,mya}]
]
$\endgroup$
3
  • $\begingroup$ Thanks. I was trying to do this with a list of 60,000 length and sort seems to work much faster than For loop!! $\endgroup$ Feb 14, 2014 at 12:31
  • $\begingroup$ Man, you are on a roll today :). This is my third upvote to your today's answers. Looks like you have some plot against Mr.Wizard :). $\endgroup$ Feb 14, 2014 at 12:41
  • $\begingroup$ @LeonidShifrin For the last weeks I barely had time to look at the site and today I just took some minutes and found some nice questions. But thanks. $\endgroup$
    – halirutan
    Feb 14, 2014 at 12:46
3
$\begingroup$

Thanks everyone for the reply. Didn't expect such an overwhelming response. I did a quick check on the speed of each of the solutions by making a random list of 2x10^7 elements and compared the timing (given in bold) using the 4 solutions given by Yi Wang, halirutan and sakra:

a = RandomInteger[1000, {2*10^7, 3}];

Method 1:

findLastMin[mat_] := Cases[mat, {__, Min@Last@Transpose@mat}]
findLastMin[a] // Timing

{8.020000, {{710, 337, 0}, {347, 509, 0}, <<19744>>, {609, 151, 0}, {553, 806, 0}}}

Method 2:

First[SortBy[a, Last]] // Timing

{18.216000, {0, 28, 0}}

Method 3:

Block[{min = Min[Last[Transpose[a]]]}, 
Do[If[Last[elm] === min, Return[elm]], {elm, a}]] // Timing

{2.536000, {710, 337, 0}}

Method 4:

Fold[If[Last[#2] < Last[#1], #2, #1] &, {0, 0, Infinity}, a] // Timing

{29.132000, {710, 337, 0}}

Method 1 gives all solutions and is fairly quick. Once again thanks for all the solutions.

$\endgroup$
3
$\begingroup$

An alternate solution using Fold:

Fold[If[Last[#2] < Last[#1], #2, #1] &, {0, 0, Infinity}, mya]

If the list is known to be non-empty, the following solution is faster:

Fold[If[Last[#2] < Last[#1], #2, #1] &, First[mya], Rest[mya]]
$\endgroup$
1
$\begingroup$

Not very efficient, I suspect, but two other (related) possibilities:

#[[Position[Ordering@Ordering@#[[All, 3]], 1, 1, 1][[1, 1]]]] &@mya

=>

{0, 2, 5}

Pick[#, Ordering@Ordering@#[[All, 3]], 1] &@mya

=>

{{0, 2, 5}}

$\endgroup$

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