9
$\begingroup$

Is there way to slice an Association with named key span?

Span currently doesn't accept named keys:

<|"z" -> 1, "x" -> 2, "b" -> 3, "a" -> 4|>  // Query["x" ;; "a"]

Missing["PartInvalid", "x" ;; "a"]

And neither does passing Key["x"] and Key["a"] to Span.

Here is a workaround using Position - which only matches Values in an Association - so relies on 1. normalizing, 2. projecting the first component of the matching Positions then 3. querying the rebuilt association using the resulting position Span.

keySpan[k1_, k2_][as_Association] := 
  Query[{Identity, Normal /* Position[k1 | k2] /* Map[First] }  /* 
     Replace[{a_, pos_} :> Query[Span[pos]][a]]][as];

Then:

<|"z" -> 1, "x" -> 2, "b" -> 3, "a" -> 4, "c" -> 5|>  // keySpan["x", "a"]

<|"x" -> 2, "b" -> 3, "a" -> 4|>

Is there a method that avoids normalizing the association?

$\endgroup$
2
  • $\begingroup$ What objects are "stored" in the association values? Strings and/or numbers or more general expressions? $\endgroup$ Jan 11 '17 at 20:05
  • $\begingroup$ @AntonAntonov - as far as I know arbitrary expressions can be Values, including eg graphics. Whereas Keys are intended to be verbatim, so not patterns unless perhaps Held or Inactivated- haven't tested that. But Keys can also be compound, eg List, Association, Graphics etc. $\endgroup$ Jan 11 '17 at 22:00
12
$\begingroup$

I don't know if this is useful to you but it seems a little cleaner than your own code:

asc = <|"z" -> 11, "x" -> 22, "b" -> 33, "a" -> 44|>;

keySpan[k_Span][asc_Association] :=
 asc[[k /. First /@ PositionIndex@Keys@asc]]

asc // keySpan["x" ;; "a"]

asc // keySpan["z" ;; "a" ;; 2]

asc // keySpan["b" ;;]
<|"x" -> 22, "b" -> 33, "a" -> 44|>

<|"z" -> 11, "b" -> 33|>

<|"b" -> 33, "a" -> 44|>

Note:

  • If you are going to use this function a lot on the same association it would be beneficial to memoize the PositionIndex output.

With memoization as noted above:

mem : keySpanMem[asc_Association] := mem = First /@ PositionIndex @ Keys @ asc

keySpan[k_Span][asc_Association] := asc[[ k /. keySpanMem @ asc ]]

This will be counterproductive if the Association changes very frequently.

For the purpose of benchmarking (with or without memoization) please use cleanPosIdx from:

$\endgroup$
4
  • $\begingroup$ Agreed, thanks, nice. $\endgroup$ Jan 10 '17 at 20:33
  • $\begingroup$ @alancalvitti You're welcome. Okay, see my update for a bit more flexibility. $\endgroup$
    – Mr.Wizard
    Jan 10 '17 at 20:35
  • $\begingroup$ Yes, even nicer with the ;; syntax and the ability to skip. Would like to do a timing study before accepting, will likely edit the Q including a random test Association. Perhaps you can show the best method for memoization - I usually use a x := y = f pattern. $\endgroup$ Jan 11 '17 at 22:18
  • $\begingroup$ @alancalvitti Example added. $\endgroup$
    – Mr.Wizard
    Jan 11 '17 at 23:36
6
$\begingroup$

From version 10.4 onward, we can define keySpan like this:

keySpan[k1_, k2_] :=
  Replace[<|___, s:PatternSequence[k1 -> _, ___, k2 -> _], ___|> :> <|s|>]

so that:

$a = <| "z" -> 1, "x" -> 2, "b" -> 3, "a" -> 4 |>;

$a // keySpan["x", "a"]

(* <|"x" -> 2, "b" -> 3, "a" -> 4|> *)

We can make this more robust by handling some corner cases:

keySpan[k1_, k2_] :=
  Replace @
    { <|___, s:(k1 -> _), ___|> /; k1 === k2 :> <|s|>
    , <|___, s:PatternSequence[k1 -> _, ___, k2 -> _], ___|> :> <|s|>
    , _Association :> (Message[keySpan::failed, k1, k2]; $Failed)
    , _ :> (Message[keySpan::object]; $Failed)
    }

keySpan::failed = "Cannot find key span '``' to '``'.";
keySpan::object = "Association 

so then:

$a // keySpan["x", "b"]

(* <| "x" -> 2, "b" -> 3 |> *)


$a // keySpan["x", "x"]

(* <| "x" -> 2 |> *)


$a // keySpan["b", "x"]

(* keySpan::failed: Cannot find key span 'b' to 'x'.
   $Failed *)


$a // keySpan["x", "XYZ"]

(* keySpan::failed: Cannot find key span 'x' to 'XYZ'.
   $Failed *)


$a // keySpan["XYZ", "XYZ"]

(* keySpan::failed: Cannot find key span 'XYZ' to 'XYZ'.
   $Failed *)


{1, 2, 3} // keySpan["x", "a"]

(* keySpan::object: Association expected.
   $Failed *)
$\endgroup$
1
  • $\begingroup$ Thanks, this is also an interesting approach. I'd like to do a timing study to compare your answer w/ Mr.Wizard's and numeric Span. $\endgroup$ Jan 11 '17 at 18:54

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.