9

I'd like to get specific slices from a nested database-like Association through Query. Take for example this toy database of letters in different alphabets

db = <|"English" -> <|"Num" -> Range[26], "Letter" -> Alphabet[]|>, 
         "Greek" -> <|"Num" -> Range[24], "Letter" -> Alphabet["Greek"]|>, 
         "Russian" -> <|"Num" -> Range[33], "Letter" -> Alphabet["Russian"]|>|>;

Using Query one can find for instance the first entry for the english and greek alphabet

db // Query[{"English", "Greek"}, All, 1]

<|"English" -> <|"Num" -> 1, "Letter" -> "a"|>, "Greek" -> <|"Num" -> 1, "Letter" -> "α"|>|>

or only the letter entries

db // Query[{"English", "Greek"}, "Letter", 1]
<|"English" -> "a", "Greek" -> "α"|>

What I'd like to obtain is a list of these slices using part specification e.g. the first three of these slices like in

Table[db // Query[{"English", "Greek"}, "Letter", n], {n, 1, 3}]
{<|"English" -> "a", "Greek" -> "α"|>, 
 <|"English" -> "b",  "Greek" -> "β"|>, 
 <|"English" -> "c", "Greek" -> "γ"|>}

Is this possible using Query alone (without using Query multiple times)?

Sascha
  • 8,459
  • 2
  • 32
  • 66

2 Answers2

7

This will work:

db // Query[ Drop[ #, None, {3} ]& @* Transpose, 2, 1 ;; 3 ]

{<|"English" -> "a", "Greek" -> "α"|>, <|"English" -> "b", "Greek" -> "β"|>, <|"English" -> "c", "Greek" -> "γ"|>}

Equivalently we could have written:

db // Query[ Transpose /* ( Drop[ #, None, {3} ]&), 2, 1 ;; 3 ]

or thinking positively (instead of dropping elements):

db // Query[ (1 ;; 2) /* Transpose, 2, 1;;3 ]

To understand how the queries work, we can take a look by using Normal as WReach has shown in his excellent post, that I have linked above. So:

Query[ (1 ;; 2) /* Transpose , 2, 1 ;; 3 ] // Normal

reveals

GeneralUtilitiesSlice[1;;2]/*GeneralUtilitiesSlice[All,2,1;;3]/*GeneralUtilities`AssociationTranspose

Here Slice simply is an operator form of Part. So we can simulate the Query by doing:

db // RightComposition[
    #[[1;;2]] &,
    #[[ All, 2, 1 ;; 3]]&, (* or: Map[ #[[2, 1;; 3]]& ] *)
    GeneralUtilities`AssociationTranspose
]

Following WReach's suggestion, we have to think of a query as a sequence of

descending operations /* ascending operations

on each level of the expression starting with level 0. In the above case we have:

Query[

    (* descending   /* ascending *)

    #[[ 1;; 2]] &   /* AssociationTranspose, (* Level 0 *)
    Map[ #[[2]]& ]  /* Map[ Identity ],      (* Level 1 *)
    Map[ #[[1;;3]]& /* Map[ Identity ]       (* Level 2 *) 

]
gwr
  • 13,452
  • 2
  • 47
  • 78
6

@gwr led me to try Transpose after the first subquery as an ascending operator (/*).

db // Query[{"English", "Greek"} /* Transpose, "Letter", 1 ;; 3]

{<|"English" -> "a", "Greek" -> "α"|>, <|"English" -> "b", "Greek" -> "β"|>, <|"English" -> "c", "Greek" -> "γ"|>}

As far as I understand the internal order of evaluation in this case it is something like this (with -> indicating a descending operator and <- indicating an ascending operator)

db -> select the rows "English" and "Greek" -> select the "Letter" column from the result of the previous subquery -> take the parts 1 through 3 from the result of the previous subquery <- jump up the resulting association two levels from where the last subquery ended and apply transpose at this level.

Edit:

To get slices with multiple entries per row (not only the Letter-column) one has to apply Transpose as an ascending operator at the level of column selection as well.

db // Query[{"English", "Greek"} /* Transpose, {"Num","Letter"} /* Transpose, 1;;3]

{ <|"English" -> <|"Num" -> 1, "Letter" -> "a"|>, "Greek" -> <|"Num" -> 1, "Letter" -> "α"|>|>, <|"English" -> <|"Num" -> 2, "Letter" -> "b"|>, "Greek" -> <|"Num" -> 2, "Letter" -> "β"|>|>, <|"English" -> <|"Num" -> 3, "Letter" -> "c"|>, "Greek" -> <|"Num" -> 3, "Letter" -> "γ"|>|>}

Sascha
  • 8,459
  • 2
  • 32
  • 66
  • Someone who is more knowledgeable in the inner workings of Query, please correct me if my last paragraph is wrong. – Sascha Mar 21 '17 at 08:47
  • Note, that you will not need to take parts on the first level (there are only twoo), thus db // Query[{"English", "Greek"} /* Transpose, Transpose, 1 ;; 3] will do the job. – gwr Mar 21 '17 at 11:20
  • 1
    Your statement that the subquery 1;;3 will take parts 1 through 3 from the result of the previous subquery is not correct. On the deeper levels the descending operation will be effectively Map[ #[[1;;3]]& ] (cf. WReach's excellent post that I have linked), – gwr Mar 21 '17 at 11:23