11
$\begingroup$

I have an association:

association = {<|"basicData" -> <|"ID" -> "test", 
 "gameLength" -> "regulation"|>, "away" -> <|"Hits" -> 33, 
 "golieData" -> {<|"number" -> 30, 
    "link" -> "http://www.nhl.com/ice/player.htm?id=8470147", 
    "No." -> 30, "Player" -> "C. McElhinney", "EV" -> {32, 33}, 
    "PP" -> {2, 2}, "SH" -> {0, 0}, "Saves - Shots" -> {34, 35}, 
    "Sv%" -> 0.971, "PIM" -> 0, "TOI" -> "60:00"|>}
 |>, "home" -> <|"Hits" -> 24., 
 "golieData" -> {<|"number" -> 40, 
    "link" -> "http://www.nhl.com/ice/player.htm?id=8471695", 
    "No." -> 40, "Player" -> "T. Rask", "EV" -> {21, 22}, 
    "PP" -> {7, 8}, "SH" -> {0, 0}, "Saves - Shots" -> {28, 30}, 
    "Sv%" -> 0.933, "PIM" -> 0, 
    "TOI" -> "59:26"|>, <|"number" -> 45, 
    "link" -> "http://www.nhl.com/ice/player.htm?id=8470169", 
    "No." -> 40, "Player" -> "T. Rask", "EV" -> {21, 22}, 
    "PP" -> {7, 8}, "SH" -> {0, 0}, "Saves - Shots" -> {28, 30}, 
    "Sv%" -> 0.933, "PIM" -> 0, "TOI" -> "59:26"|>}
 |> |>,  <|"basicData" -> <|"ID" -> "blah", "gameLength" -> "playoff"|>, "away" -> <|"Hits" -> 12, 
 "golieData" -> {<|"number" -> 30, 
    "link" -> "http://www.nhl.com/ice/player.htm?id=8468524", 
    "No." -> 30, "Player" -> "I. Bryzgalov", "EV" -> {17, 17}, 
    "PP" -> {5, 6}, "SH" -> {0, 0}, "Saves - Shots" -> {22, 23}, 
    "Sv%" -> 0.957, "PIM" -> 0, "TOI" -> "60:00"|>}
 |>, "home" -> <|"Hits" -> 4, 
 "golieData" -> {<|"number" -> 30, 
    "link" -> "http://www.nhl.com/ice/player.htm?id=8460703", 
    "No." -> 30, "Player" -> "T. Thomas", "EV" -> {22, 23}, 
    "PP" -> {2, 3}, "SH" -> {3, 3}, "Saves - Shots" -> {27, 29}, 
    "Sv%" -> 0.931, "PIM" -> 0, "TOI" -> "59:15"|>}|>|>}

and I would like to select the match where the player link is equal to "http://www.nhl.com/ice/player.htm?id=8470169".

I have tried:

Query[Select[#[["home", "golieData", All,"link"]] == "http://www.nhl.com/ice/player.htm?id=8470169" &]]@association

Query[Select[#[["home", "golieData", 2, "link"]] == "http://www.nhl.com/ice/player.htm?id=8470169" &]]@association

When I try to select by the first player link, it works. I can not seem to access the second players link.

This works for the first player link:

Query[Select[#[["home", "golieData", 1, "link"]] == "http://www.nhl.com/ice/player.htm?id=8471695" &]]@association

but only when the link I am looking for is in the first position.

Is there a better way to structure this data to make this query possible?

Thanks!

$\endgroup$
10
$\begingroup$

The following query will retrieve all matches with a given goalie link:

link = "http://www.nhl.com/ice/player.htm?id=8470169";

association // Query[Select[Query["home", "golieData", MemberQ[link], "link"]]]

If only the first such match is desired, change Select to SelectFirst.

How It Works

To see how this works, let's build it up piece-by-piece.

We can retrieve a list of links for each match like this:

association // Query[All, "home", "golieData", All, "link"]

(* { {"http://www.nhl.com/ice/player.htm?id=8471695",
      "http://www.nhl.com/ice/player.htm?id=8470169" }, 
     {"http://www.nhl.com/ice/player.htm?id=8460703"} } *)

We can test each of those lists to see whether they contain the desired link by changing the rightmost All to MemberQ[link]:

association // Query[All, "home", "golieData", MemberQ[link], "link"]
(* {True, False} *)

All that remains is to retain only those matches that return True for this test. We change the left-most All in the previous query to a Select that applies the test by means of a subquery:

association // Query[Select[Query["home", "golieData", MemberQ[link], "link"]]]

(* {<| basicData -> <|...|>
     , away -> <|Hits->33,golieData->{<|number->30,...|>
     , home-> <|Hits->24.,golieData->{<|number->40,...|>,<|number->45,...|>}|>
     |>} *)

MemberQ is a so-called "ascending operator". Queries are processed in two phases: a "descending" phase that primarily applies filters on a top-down basis, and an "ascending" phase that generally performs aggregations on a bottom-up basis. The sequence of events in the subquery is as follows:

Descending Phase

  1. From the list of matches, "home" selects only the home matches.
  2. From each match, "golieData" selects only the list of goalies.
  3. MemberQ is ignored in the descending stage since it is an ascending operator. No filter is applied to each goalie list (i.e. it is as if this operator were All for descending purposes).
  4. From each goalie, "link" selects only the link value.

Ascending Phase

  1. "link" is ignored in the ascending stage since it is a descending operator. No function is applied to each link (i.e. it is as if this operator were Identity for ascending purposes).
  2. MemberQ, being an ascending operator, is applied to each list of links thereby converting it into a boolean.
  3. The descending operator "golieData" is treated as Identity, leaving the boolean unchanged.
  4. The descending operator "home" treated as Identity, once again leaving the boolean unchanged.

Thus, each subquery returns a boolean. The Select operator uses this boolean to filter the list of matches.

Re-using Queries

It is often helpful to re-use queries such as this. A helper function makes this possible:

goalie[where:"home"|"away", id_String] :=
  Query[where, "golieData", MemberQ["http://www.nhl.com/ice/player.htm?id="~~id], "link"]

The function is used like this:

association // Query[SelectFirst[goalie["away", "8468524"]]]

(* <| basicData -> <|...|>
    , away -> <|Hits->12,golieData->{<|number->30,...|>}|>
    , home -> <|Hits->4,golieData->{<|number->30,...}|>
    |> *)
$\endgroup$
  • $\begingroup$ This is a great solution. $\endgroup$ – Ray Troy Jan 19 '15 at 15:24
  • $\begingroup$ Didn't even think to use MemberQ. I'm not clear on why the MemberQ needs to be put at that position in the key. I would think it needed to be at a different place. Also, is there another way to apply this to the association other than using the //? I also thought of another way Extract[AllMatches, Partition[ Position[parsedMatches, "http://www.nhl.com/ice/player.htm?id=8471695", Infinity][[All, 1]], 1]] $\endgroup$ – Ray Troy Jan 19 '15 at 15:31
  • 1
    $\begingroup$ "That position" for MemberQ is the level that represents the list of links. MemberQ converts the each list into a boolean. I used // purely out of personal preference. You can write Query[...] @ association as in the question if you prefer. $\endgroup$ – WReach Jan 19 '15 at 16:00
  • $\begingroup$ I expanded the How It Works section to discuss the details of how MemberQ affects the query. $\endgroup$ – WReach Jan 19 '15 at 16:58
  • $\begingroup$ @WReach, great solution, however, when the value set (eg "home" | "away") grows, are you forced to manually list Alternatives? $\endgroup$ – alancalvitti Jan 19 '15 at 19:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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