9

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?

alancalvitti
  • 15,143
  • 3
  • 27
  • 92
  • What objects are "stored" in the association values? Strings and/or numbers or more general expressions? – Anton Antonov Jan 11 '17 at 20:05
  • @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. – alancalvitti Jan 11 '17 at 22:00

2 Answers2

12

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:

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
6

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 *)
WReach
  • 68,832
  • 4
  • 164
  • 269
  • 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. – alancalvitti Jan 11 '17 at 18:54