21

For example, given

list = {{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {-9, 2, 6}}

should return:

{-5, 6, 4, -9}

Updated

I found a undocumented function called Internal`MaxAbs,but it only accept two args,for example:

Internal`MaxAbs[2, -3]
Internal`MaxAbs[{1, -3, -5}, {2, 1, 6}]
(*
 3
 {2, 3, 6}
*)

How can I make it can accept multiple parameters?

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
expression
  • 5,642
  • 1
  • 19
  • 46
  • 3
    To be sure: what happens if a sublist is e.g. {1,2,2}? You'd pick 2 just once or twice? – Pinguin Dirk Aug 30 '13 at 08:39
  • 2
    @PinguinDirk or even more interesting what if {1,-2,2} :) – Kuba Aug 30 '13 at 09:22
  • @Kuba, Just pick once, if {1,-2,2} should return 2. – expression Aug 30 '13 at 10:17
  • @explorer -2 is single result which fits too:) My MapIndexed method will give you the first of "equal" numbers. Switching to <= will result in the last one of them but there is a case of {-2,2,-2}. Then You will always get -2 unless you restate the question to be more precise that you want positive value from the "equal" set. – Kuba Aug 30 '13 at 10:22
  • 1
    Possible duplicate: http://mathematica.stackexchange.com/q/11795/5 – rm -rf Aug 30 '13 at 11:12
  • so many solutions now ;) – Nasser Aug 30 '13 at 11:32
  • @rm-rf I don't think this is a duplicate of that question, but it's certainly related. – Mr.Wizard Sep 01 '13 at 10:25
  • The function you added, while interesting, does not appear to preserve the sign of the numbers, therefore I do not believe it is applicable to your problem. – Mr.Wizard Sep 07 '13 at 19:37

14 Answers14

24

(Edited with a slight tweak for a tiny bit more speed)

For the non-duplicate version I am finding this quite fast:

f = If[+## > 0, ##] & @@ {Max[#], Min[#]} &

f /@ list
(* {-5, 6, 4, -9} *)
Simon Woods
  • 84,945
  • 8
  • 175
  • 324
14

I'd use Ordering, as Nasser did, but with the default sort as it will be much faster:

# ~Extract~ Ordering[Abs@#, -1] & /@ list
{-5, 6, 4, -9}

If duplicates are required we can substitute Position but Pinguin Dirk's method is faster.

list = {{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {-9, 2, 6}, {1, -2, 2}}

# ~Extract~ (Position[#, Max@#] &@Abs@#) & /@ list
{{-5}, {6}, {4}, {-9}, {-2, 2}}

Here is a concise use of Pick that appears to be competitive for the with-duplicates case:

f[a_] := Pick[a, #, Max@#] & @ Abs @ a

f /@ list
{{-5}, {6}, {4}, {-9}, {-2, 2}}

Timings in version 7 (Pick should be faster in v8 and after):

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

list = RandomInteger[{-30, 30}, {50000, 20}];

# ~Extract~ (Position[#, Max@#] &@Abs@#) & /@ list  // timeAvg
Pick[list, UnitStep[# - Max[#]] & /@ (Abs@list), 1] // timeAvg
f /@ list                                           // timeAvg

0.2496

0.2184

0.1996

Nasser's timings in version 9 are: 0.328, 0.112, 0.203 showing that Pinguin Dirk's code is by far the fastest in recent versions.

And timings for the non-duplicate methods:

list = RandomReal[{-9, 9}, {50000, 20}];

# ~Extract~ Ordering[Abs@#, -1] & /@ list                                  // timeAvg
(SortBy[#, Abs] & /@ list)[[All , -1]]                                     // timeAvg
If[+## > 0, ##] & @@ {Max[#], -Max[-#]} & /@ list                          // timeAvg
Pick[Flatten@#, Flatten[Ordering@Ordering@Abs[#] & /@ #], 3] &@list        // timeAvg
MapThread[Extract, {list, Ordering[#, -1, (Abs@#1 < Abs@#2) &] & /@ list}] // timeAvg
Max /@ Pick[list, UnitStep[# - Max[#]] & /@ (Abs@list), 1]                 // timeAvg

0.078

0.1092

0.128

0.2308

0.842

0.2652

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

With the clarification, I think I can reuse my solution I initially provided by just mapping Max over it again:

Old:

Pick[list, UnitStep[# - Max[#]] & /@ (Abs@list), 1]

New:

Max /@ Pick[list, UnitStep[# - Max[#]] & /@ (Abs@list), 1]

(note: the old solution returned any value that has max abs value, i.e. {1,2,-2} returned 2,-2).

Based on this new information, this approach is definitely not the best out there, see @Mr.Wizard's answer.

Pinguin Dirk
  • 6,519
  • 1
  • 26
  • 36
  • Yes, I don't expect anything non-compiled to beat my first method for the non-duplicate version, but your method is very nice for the duplicate version; it's a great complement. – Mr.Wizard Aug 30 '13 at 10:32
  • yes, totally agree, @Mr.Wizard. Based on this new information in the question, this is how I tried to save my skin (and the upvotes I got) :) & thanks! – Pinguin Dirk Aug 30 '13 at 10:34
  • Actually since the original didn't specify your answer is entirely valid. If you feel it isn't I'll edit the question to specifically request both ways. :-) – Mr.Wizard Aug 30 '13 at 10:38
  • Thanks for the offer :) I gotta run now anyway, and I think my answer now works in both ways - not as quickly as your idea, but I think the disclaimer is in my answer :) – Pinguin Dirk Aug 30 '13 at 10:40
11

Update: Added to test table below new 3 answers.

I came up with an improvement to my earlier method. Instead of running the Ordering on the original list, why not run it on a much smaller list? of only 3 items !

This new list is first generated from the original list like this:

{Abs[#], Max[#], Min[#]} & /@ list

Now with only 3 elements in each list, it is much faster to do the same thing as before. The idea is to check if first element same as Abs of 3rd element. If not, then use the middle element, else use the third. So it is only an If added to each element. Min/Max/Abs so the heavy work:

If[#[[1]] == Abs@#[[3]],#[[3]], #[[2]]] & /@({Max@Abs[#], Max[#], Min[#]}& /@ list) 

UPDATE TIMING

All run on V 9.01, windows 7, 64 bit. Intel core i7. Using this list

  list = RandomInteger[{-30, 30}, {50000, 20}];

Mathematica graphics

Using this list

list = RandomReal[{-9, 9}, {50000, 20}];

Mathematica graphics

Appendix

Test code:

SetAttributes[timeAvg, HoldFirst]
timeAvg[func_] := 
 Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 15}]
MrWizardPick[a_] := Pick[a, #, Max@#] &@Abs@a  ;
(*list=RandomInteger[{-30,30},{50000,20}];*)
MaxBy[list_, fun_] := list[[First@Ordering[fun /@ list, -1]]];
list = RandomInteger[{-30, 30}, {50000, 20}];
gpap = Block[{M, m}, M = Max@#;
    m = Min@#;
    If[M > -m, M, m]] &;

Grid[{
  {"Gpap", gpap /@ list // timeAvg},
  {"Simon", If[+## > 0, ##] & @@ {Max[#], Min[#]} & /@ list // timeAvg},
  {"Nasser", If[#[[1]] == Abs@#[[3]], #[[3]], #[[2]]] & /@ ({Max@Abs[#], Max[#], 
         Min[#]} & /@ list) // timeAvg},
  {"Blackbird",Table[If[
      Min[list[[i]]] < 0 && Abs[Min[list[[i]]]] > Max[list[[i]]], 
      Min[list[[i]]], Max[list[[i]]]], {i, Length[list]}] // timeAvg},
  {"Tom",Pick[Flatten@#, Flatten[Ordering@Ordering@Abs[#] & /@ #], 3] &@ list // timeAvg},
  {"MrWizard", #~Extract~Ordering[Abs@#, -1] & /@ list // timeAvg},
  {"Pinguin",Max /@ Pick[list, UnitStep[# - Max[#]] & /@ (Abs@list), 1] // timeAvg},
  {"Kuba", (SortBy[#, Abs] & /@ list)[[;; , -1]] // timeAvg},
  {"Rojo", Pick[list, 
      With[{absList = Abs@list}, 
       With[{max = Max /@ absList}, # - max & /@ Transpose@absList] //
          Unitize // Transpose], 0] // Flatten // timeAvg},
  {"MrWizardPick", MrWizardPick /@ list // timeAvg},
  {"Szabolcs", MaxBy[#, Abs] & /@ list // timeAvg}
  }, Frame -> All, Alignment -> Left, Spacings -> {.5, 1}]
Nasser
  • 143,286
  • 11
  • 154
  • 359
  • @Mr.Wizard corrected and updated. Gpap and Simon tests seem to give an almost identical performance on these 2 lists. – Nasser Aug 30 '13 at 20:56
11

Using the function introduced here, it becomes a trivial exercise:

MaxBy[#, Abs] & /@ list

(* ==> {-5, 6, 4, -9} *)

It's probably not the fastest, but if you're familiar with the pattern, it's the least mental effort solution.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • fyi, just added your test to the test cases above in my post. (too many ways to do the same thing in Mathematica) ! – Nasser Aug 30 '13 at 13:27
  • Thanks @Nasser! So it turns out that mine is the slowest in your benchmark. But it's good to point out that the timings (and the ranking of methods) depends on the dimensions of the input---small list length vs number of small lists. If I change it to {5000, 200} from {50000, 20}, the results change considerably. – Szabolcs Aug 30 '13 at 13:58
  • @Nasser Simon's seems to win in all situations, regardless of the dimensions of the input. – Szabolcs Aug 30 '13 at 14:06
  • 1
    @Szabolcs, presumably the relative slowness of MaxBy comes from explicitly mapping Abs over the list. Perhaps you could include a check to use f[list] when f is Listable. – Simon Woods Aug 30 '13 at 14:19
  • I don't like this method. It's only slightly simpler than my more direct use of Ordering and doesn't use Abs properly (Listability); I see this as a needless abstraction. – Mr.Wizard Aug 30 '13 at 20:22
  • 1
    @Mr.Wizard I see it as a mental abstraction that helps me arrive at the solution with less effort/thinking and allows me to concentrate on the problem at hand instead of the implementation of a detail. If you put any effort into solving this particular problem, then you'll necessarily arrive at a faster/better solution, such as yours (which is pretty much the same as this one except with better use of Abs). – Szabolcs Aug 30 '13 at 21:14
10
list = {{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {2, -9, 6}};

One way would be:

(SortBy[#, Abs] & /@ list)[[;; , -1]]
{-5, 6, 4, -9}

But I bet it is not effective since we do not have to sort each list to obtain this. However for short sublists it may be good approach.


Ok, this scans only once but maybe MapIndexed is not the fastest scanner:

max[_] = 0;
f[z_, {i_, j_}] := max[i] = If[Abs[max[i]] > Abs@z, max[i], z]

MapIndexed[f, list, {2}];

max /@ Range[Length[list]]
{-5, 6, 4, -9}
Kuba
  • 136,707
  • 13
  • 279
  • 740
8

Ok, so a bit late but I want to play as well. It's a variation on a theme by @SimonWoods:

ClearAll@f

f = Block[{M, m},
   M = Max@#;
   m = Min@#;
   If[M < -m, m, M]
   ] &

but in my machine it works slightly faster.

list = {{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {-9, 2, 6}};
f /@ list
(*out*) {-5, 6, 4, -9}

--EDIT--

Changed If[M > -m, M, m] to If[M < -m, m, M] to pick the positive of two numbers with equal absolute value.

gpap
  • 9,707
  • 3
  • 24
  • 66
6

I'll join the fun:

r = Ordering[#, -1, (Abs@#1 < Abs@#2) &] & /@ list;
MapThread[Extract, {list, r}]

Mathematica graphics

Nasser
  • 143,286
  • 11
  • 154
  • 359
4

More fun:

Pick[Flatten@#, Flatten[Ordering@Ordering@Abs[#] & /@ #], 3] &@list

=> {-5, 6, 4, -9}

user1066
  • 17,923
  • 3
  • 31
  • 49
4

I saw so many answers that I just had to participate. This won't work for a ragged array

Pick[list,
  With[{absList = Abs@list},
   With[{max = Max /@ absList},
      # - max & /@ Transpose@absList] // Unitize // Transpose
   ], 0] // Flatten
Rojo
  • 42,601
  • 7
  • 96
  • 188
4
Table[If[Min[list[[i]]] < 0, Min[list[[i]]], Max[list[[i]]]], {i, 
  Length[list]}]

{-5, 6, 4, -9}

For list = RandomInteger[{-30, 30}, {50000, 20}];

Time = 0.040058

list = RandomReal[{-9, 9}, {50000, 20}];

Time = 0.050072

Edit: As a valid mistake found by @simonWoods, I have rectified the snippet and now its generating better result.

Table[If[Min[list[[i]]] < 0 && Abs[Min[list[[i]]]] > Max[list[[i]]], 
  Min[list[[i]]], Max[list[[i]]]], {i, Length[list]}]

For list = {{-1, 10}}; (*{10}*)

For list = {{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {-9, 2, 6}} (*{-5, 6, 4, -9}*)

For list = {{-1, -10, 10}}; (*{10}*)

For list = RandomInteger[{-30, 30}, {50000, 20}];

Time = 0.120173

For list = RandomReal[{-9, 9}, {50000, 20}];

Time = 0.130187(Ranging from .012.. to .014.. on my system)

Pankaj Sejwal
  • 2,063
  • 14
  • 23
4

One way to ensure the + wins in the case of equal Abs..

(Last@SortBy[ #, {Abs[#], #} & ]) & /@
   {{1, 2, -2}, {1, 5, -5}, {1, -3, 3}, {1, -4}}

(* {2, 5, 3, -4} *)

or this may be faster..dont feel like running for time.

(Last@SortBy[{Max[#], Min[#]}, Abs]) & /@
  {{1, 2, -2}, {1, -5, 5}, {1, -3, 3}, {1, -4}}

Edit -- oops not nesessary SortBy autmaticaly breaks the tie based on signed value so just this works..

(Last@SortBy[ #, Abs ]) & /@
   {{1, 2, -2}, {1, 5, -5}, {1, -3, 3}, {1, -4}}

" If some of the f[e_i] are the same, then the canonical order of the corresponding e_i is used. "

george2079
  • 38,913
  • 1
  • 43
  • 110
2

In versions 10+, you can use MaximalBy and TakeLargestBy:

list = {{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {-9, 2, 6}};

Flatten[MaximalBy[Abs]/@list]

 {-5, 6, 4, -9}

Flatten[TakeLargestBy[Abs, 1]/@list]

 {-5, 6, 4, -9}

TakeLargestBy is the faster of the two. Both are much slower than other methods posted so far.

kglr
  • 394,356
  • 18
  • 477
  • 896
1

As first pointed out by @rm-rf, there exist internal, undocumented functions Random`Private`MapThreadMin and Random`Private`MapThreadMax, and these are useful for this purpose. Here is a function that uses these internal functions:

maxByAbs[list_] := With[{t = Transpose@list},
    With[{min = Random`Private`MapThreadMin[t], max = Random`Private`MapThreadMax[t]},
        max + UnitStep[Abs[min] - max] (min-max)
    ]
]

The basic idea is to get the minimum and maximum values of each list, and then use arithmetic to figure out which one is larger. Here is the OP example:

maxByAbs[{{1, -3, -5}, {2, 1, 6}, {0, 2, 4}, {-9, 2, 6}}]

{-5, 6, 4, -9}

And here is a brief timing comparison with the chosen answer:

list=RandomInteger[{-30,30}, {50000,20}];

f = If[+## > 0, ##] & @@ {Max[#], Min[#]} &;

r1 = f/@list; //RepeatedTiming
r2 = maxByAbs[list]; //RepeatedTiming

r1===r2

{0.0068, Null}

{0.0028, Null}

True

Carl Woll
  • 130,679
  • 6
  • 243
  • 355