9
$\begingroup$

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)?

$\endgroup$
  • $\begingroup$ For those seeking to understand Queryat a deeper level, take a look at (98193). $\endgroup$ – gwr Mar 21 '17 at 10:52
7
$\begingroup$

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 *) 

]
$\endgroup$
6
$\begingroup$

@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" -> "γ"|>|>}

$\endgroup$
  • $\begingroup$ Someone who is more knowledgeable in the inner workings of Query, please correct me if my last paragraph is wrong. $\endgroup$ – Sascha Mar 21 '17 at 8:47
  • $\begingroup$ 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. $\endgroup$ – gwr Mar 21 '17 at 11:20
  • 1
    $\begingroup$ 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), $\endgroup$ – gwr Mar 21 '17 at 11:23

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.