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$ Commented Jan 11, 2017 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$ Commented Jan 11, 2017 at 22:00

2 Answers 2

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$ Commented Jan 10, 2017 at 20:33
  • $\begingroup$ @alancalvitti You're welcome. Okay, see my update for a bit more flexibility. $\endgroup$
    – Mr.Wizard
    Commented Jan 10, 2017 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$ Commented Jan 11, 2017 at 22:18
  • $\begingroup$ @alancalvitti Example added. $\endgroup$
    – Mr.Wizard
    Commented Jan 11, 2017 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$ Commented Jan 11, 2017 at 18:54

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.