1
$\begingroup$

I am working with lists of the following form:

data = {
<|"id" -> "name1", "p1" -> v1, "p2" -> v2, "p3" -> v3, "p4" -> v4|>,
<|"id" -> "name2", "p1" -> v1, "p2" -> v2, "p3" -> v3, "p4" -> v4|>,
<|"id" -> "name3", "p1" -> v1, "p2" -> v2, "p3" -> v3, "p4" -> v4|>,
<|"id" -> "name4", "p1" -> v1, "p2" -> v2, "p3" -> v3, "p4" -> v4|>
};

In essence, a list of associations where the keys are strings, and the values are all approximate real numbers except for the value associated with the "id" key. Any of the values can be any approximate real number, i.e., simply because v1 is used in multiple associations doesn't mean that it is the same value in different associations. It could be the same, but most likely it won't be. Some of the values will be Indeterminate. Also, although the number of properties in the above sample apart from the id is four, the associations could have any number of properties: I chose four properties simply for compactness.

My end goal is to take such lists and calculate things like Min or Max on various values and be able to return the entire record (association). I have come up with a general approach that works, however, my guess is that it is clunky, for it requires creating sub lists and doing substitutions. E.g., if I execute Max as follows:

sampleList = {1., 2., Indeterminate, 3000.};
Position[sampleList, Max[sampleList]]

I get {{3}} when what I expected was {{4}}. So, for a function like Max, I modify the example as follows so that it will give me what I expect:

sampleList = {1., 2., Indeterminate, 3000.} /. Indeterminate -> -\[Infinity];
Position[sampleList, Max[sampleList]]

and it gives the answer {{4}}.

If I wanted to utilize Min, I would modify the example like so:

sampleList = {1., 2., Indeterminate, 3000.} /. Indeterminate -> \[Infinity]
Position[sampleList, Min[sampleList]]

to get the expected answer of {{1}}.

I know that there are many ways to solve things in Mathematica, but what I'm looking for is a solution to the problem that is more efficient and takes advantage of 'deeper' aspects of Mathematica so I can learn. What follows is the function I wrote for obtaining the record with the maximum value for any given property name:

ClearAll[GetMax];
GetMax::NoMax = "Position returned an empty list";
GetMax[data_List, key_String] := Module[
   {pos, sub},
   sub = (data[[All, key]] /. Indeterminate -> -\[Infinity]);
   pos = Position[sub, Max[sub]];
   If[0 == Length[pos], Return[$Failed, GetMax::NoMax]];
   data[[pos[[1]]]]
   ];

Some sample data to drive the function:

data = {
<|"id" -> "id1", "p1" -> 2.33333, "p2" -> 2.25, "p3" -> 2.,"p4" -> 4, "p5" -> 0.433013, "p6" -> 1.1547|>,
<|"id" -> "id2", "p1" -> Indeterminate, "p2" -> 2., "p3" -> 2.,"p4" -> 10., "p5" -> 0., "p6" -> Indeterminate|>,
<|"id" -> "id3", "p1" -> 2.36391, "p2" -> 2.57143, "p3" -> 2.,"p4" -> 7, "p5" -> 0.728431, "p6" -> 0.859894|>,
<|"id" -> "id4", "p1" -> 1., "p2" -> 2.5, "p3" -> 2., "p4" -> 4,"p5" -> 0.5, "p6" -> 0.|>,
<|"id" -> "id5", "p1" -> 2.33333, "p2" -> 2.25, "p3" -> 2.,"p4" -> 4, "p5" -> 0.433013, "p6" -> 1.1547|>,
<|"id" -> "id6", "p1" -> 1.16667, "p2" -> 2.4, "p3" -> 2.,"p4" -> 5, "p5" -> 2.489898, "p6" -> 0.408248|>,
<|"id" -> "id7", "p1" -> Indeterminate, "p2" -> 2., "p3" -> 4.1,"p4" -> 6, "p5" -> 0., "p6" -> Indeterminate|>,
<|"id" -> "id8", "p1" -> 1.49308, "p2" -> 2.85714, "p3" -> 3.,"p4" -> 7, "p5" -> 0.832993, "p6" -> 0.27238|>,
<|"id" -> "id9", "p1" -> 1.5, "p2" -> 2.66667, "p3" -> 3.,"p4" -> 3, "p5" -> 0.471405, "p6" -> -0.707107|>,
<|"id" -> "id10", "p1" -> 1., "p2" -> 2.5, "p3" -> 2., "p4" -> 4,"p5" -> 0.5, "p6" -> 0.|>
};

Utilization of the function to retrieve the record associated with the maximum value of any given property:

GetMax[data, "p1"]
GetMax[data, "p2"]
GetMax[data, "p3"]
GetMax[data, "p4"]
GetMax[data, "p5"]
GetMax[data, "p6"]

It works, but as I stated at the outset, seems klunky compared to many of the elegant MMA solutions I see here on the forums. I also realize that GetMax will have problems if the values are integers because Position will potentially match on the FullForm of \[Infinity], but that's a different problem!

$\endgroup$
1
  • 1
    $\begingroup$ Rather than a rule Indeterminate -> +- Infinity, you could do something like Select[sampleList, NumericQ] before you look for max/min. Also, rather than Position[sub, Max[sub]] you could use PositionLargest / PositionSmallest (after filtering out the non-numerics). $\endgroup$
    – lericr
    Commented Aug 15 at 17:42

1 Answer 1

1
$\begingroup$

Here is an approach. Not sure if it integrates well with your workflow. There might be better ways to do this in your context, but just based on what you've shown, something along these lines might work for you.

GetMax[data_List, key_String] :=
  MaximalBy[Select[data, NumberQ@*Lookup[key]], Lookup[key], 1]

I didn't include the failure message.

$\endgroup$
6
  • $\begingroup$ that works great! However, I don't understand how it works. The @* syntax is totally new to me. My understanding is that it is a short form for Composition. It then seems to me that NumberQ@*Lookup[key] should be equivalent to NumberQ[Lookup[key]], but it's not. I get an empty list when I make that change. I've been looking at the documentation for an hour and I'm still scratching my head. I saw that Composition has the attribute Flat, but I don't see what that would change. What am I missing, if you please? $\endgroup$
    – anonmous
    Commented Aug 16 at 3:34
  • 1
    $\begingroup$ Let's compose two functions when there is an explicit argument: f[g[x]]. Composition lets you compose two functions without explicitly using an argument: f@*g. To apply the composition: (f@*g)[x]. What you tried with NumberQ[Lookup[key]] is analogous to f[g], which is not a composition, but just f applied to g, and that almost certainly won't make any sense. Part of the confusion might be that Lookup[key] looks like g[x], but it's different. Lookup[key] is an "operator form", basically currying. The expression Lookup[key] acts like a function. $\endgroup$
    – lericr
    Commented Aug 16 at 14:31
  • 1
    $\begingroup$ So, mapping our f to NumberQ and g to Lookup[key], we get f[g[x]] -> NumberQ[Lookup[key][x]], f@*g -> NumberQ@*Lookup[key], (f@*g)[x] -> (NumberQ@*Lookup[key])[x]. $\endgroup$
    – lericr
    Commented Aug 16 at 14:35
  • $\begingroup$ a ha! I think I finally get it. Still took me a bit, but now, as I understand it, what you wrote NumberQ@*Lookup[key] is essentially a function that is 'missing' an argument. (Would it be legitimate to think of the construct as defining an anonymous function of sorts?) The argument that eventually is supplied is the association, so that, as you pointed out, in the context of Select it becomes NumberQ[Lookup[key][x]] where x is an association, which is equivalent to NumberQ[Lookup[x,key]] where x is the association. Very cool, thank you. $\endgroup$
    – anonmous
    Commented Aug 16 at 15:28
  • 1
    $\begingroup$ Right. An alternate form would have been NumberQ[Lookup[#, key]] &. You can then go through various transformation steps. Use the operator form of Lookup: NumberQ[Lookup[key][#]] &. Notice that this is a composition, so use Composition: Composition[NumberQ, Lookup[key]][#] &. Now the #/& form is superfluous, so simplify Composition[NumberQ, Lookup[key]]. Then use the infix form of Composition: NumberQ@*Lookup[key]. $\endgroup$
    – lericr
    Commented Aug 16 at 15:50

Your Answer

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

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