20
$\begingroup$

Clarification

Although I may be missing his point I currently feel that Leonid's comments below are misleading. I am not looking for anything that is not already a part of Association functionality other than a way to define what is returned for a missing key on a per-association basis. I thought that the Block example below made this clear but perhaps not.

I am seeking a way to do something like this:

asc = <|"a" -> 1, "b" -> 2, _ -> 0|>;

asc["x"]

(* desired output: 0 *)

(* actual output: Missing["KeyAbsent", "x"] *)

Critically I am not looking for general pattern matching of Key names however; I only want a way to define one default value for missing keys.


Original ramblings

It can be very useful to define background or default value for an object that can be incremented or otherwise modified. A simple (and for me, common) use is a counter:

count[_] = 0;

++count[#] & /@ {"a", "a", "b", "a", "b", "a"}
{1, 2, 1, 3, 2, 4}

Since there are advantages to Association such as ease of copying and direct manipulation of keys and values I would like to port this method to new function. However I cannot think of a clean, practical way to define a default. (I consider directly overloading System functions such as Increment undesirable.)

I note that it is possible to increment a missing value and then clean it up afterward:

asc = <||>;

++asc[#] & /@ {"a", "a", "b", "a", "b", "a"} /. _Missing -> 0
{1, 2, 1, 3, 2, 4}

This isn't really the same as setting a default value however and it limits the way this method can be used. Somewhat better I think is to temporarily redefine Missing. This at least gives values that are up-to-date while the operation is performed.

asc = <||>;

Block[{Missing},
  Missing["KeyAbsent", _] = 0;
  ++asc[#] & /@ {"a", "a", "b", "a", "b", "a"}
]

asc
{1, 2, 1, 3, 2, 4}

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

This could be packaged a bit more nicely but it still feels like a bit of a kluge.

Is there an approach I am failing to consider? Or by some slim chance is there a hidden way to do this?

$\endgroup$
  • 2
    $\begingroup$ I think that by nature of associations (immutability, in the first place), mutating them in-place isn't by far as natural as mutating DownValues. And the fact that what you request is problematic in this approach is simply another facet of that: the ability to use defaults has been transferred from association itself to the Lookup function, but then Lookup can not be used to mutate things, bacause it knows nothing about where a given assoc is stored (and doesn't care about it). If you need mutation, why use associations? You can keep using DownValues in such cases. $\endgroup$ – Leonid Shifrin Jul 6 '15 at 4:05
  • $\begingroup$ Also the title seems to be a bit misleading currently: what you ask for is not just default, but mutable default - which makes quite a difference. $\endgroup$ – Leonid Shifrin Jul 6 '15 at 4:07
  • $\begingroup$ @Leonid (1) It may be as natural but the functionality exists and I see no reason not to try to use it. I realize that ultimately performance issues may or may not limit use but I disagree that this is somehow an inherent limitation if I infer that correctly. (2) Does the existence of special default handling in Lookup prevent other handling? I wouldn't think so. (3) Do all of the benefits of associations cease to exist for some reason? Easy copying and manipulation of values, nested associations, etc. still apply do they not? $\endgroup$ – Mr.Wizard Jul 6 '15 at 5:45
  • $\begingroup$ @Leonid Regarding the title I tried to see it from that perspective but I cannot. The default value is not being changed; it is only being used in subsequent computations. Maybe tomorrow I can bring some fresh eyes to it but at the moment it feels like you are critiquing a straw man. $\endgroup$ – Mr.Wizard Jul 6 '15 at 6:05
  • 1
    $\begingroup$ Valid question! It would be nice to be able to do something like: asso /: asso[key_] := Lookup[asso, key, "NoAv"]. P.s. I'd delete everything up to "clarification" :) $\endgroup$ – Kuba Sep 28 '15 at 10:43
5
$\begingroup$

It seems to me that merely defining a global default value enables the kind of application for associations that you are discussing.

asc = <||>; $asc = 0;

(KeyExistsQ[#][asc] || (asc[#] = $asc); ++asc[#]) & /@ 
   {"a", "a", "b", "a", "b", "a"}
{1, 2, 1, 3, 2, 4}
asc
<|"a" -> 4, "b" -> 2|>
bsc = <||>; $bsc = 1;

(KeyExistsQ[#[[1]]][bsc] || (bsc[#[[1]]] = $bsc); bsc[#[[1]]] = bsc[#[[1]]] #[[2]]) & /@ 
   MapThread[Rule, {{"a", "a", "b", "a", "b", "a"}, Range[6]}]
{1, 2, 3, 8, 15, 48}
bsc
<|"a" -> 48, "b" -> 15|>

I am prepared to be instructed on how what I have presented here misses the point.

$\endgroup$
  • $\begingroup$ This is certainly a valid approach. My issue with it is the extra code needed for every operation; ++asc[#] becoming (KeyExistsQ[#][asc] || (asc[#] = $asc); ++asc[#]) is a pretty bad hit to clean coding. It would be possible to define new operators e.g. associationIncrement (possibly with a shorter name) that handle this but one loses commonality in code. I don't know of any satisfying solution but that is why I posted the question. $\endgroup$ – Mr.Wizard Jul 6 '15 at 15:53

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.