Method 1: using extra symbols and DownValues
Here is a version which will give you the constant time lookup for your DownValues
of interest, for the price of introducing additional symbols. These symbols however are dealt with in semi-automatic fashion. Here is the code:
ClearAll[h];
h /: (head:Remove|Clear|ClearAll)[h]:=
(UpValues[h]={};Remove@@Most[DownValues[h]][[All,2]];head[h]);
h /: HoldPattern[DownValues[arg_h]]:=
With[{res=arg},
DownValues[res]/;HoldComplete[res]=!=HoldComplete[arg]
];
h[arg_]:= h[arg]=Unique["Temp`h",{Temporary}]
Here are the DownValues
of h
before assignments:
DownValues[h]
(* {HoldPattern[h[arg_]] :> (h[arg] = Unique["Temp`h", {Temporary}])} *)
Now we perform the assignments:
h[1][tag1] = 1;
h[1][tag2] = 2;
h[2][tag1] = 3;
h[2][tag2] = 4;
and now the DownValues
are:
DownValues[h]
(*
{
HoldPattern[h[1]] :> Temp`h66,
HoldPattern[h[2]] :> Temp`h67,
HoldPattern[h[arg_]] :> (h[arg] = Unique["Temp`h", {Temporary}])
}
*)
The memoization is used so that extra symbols are created the first time they are needed.
Now, we also overloaded DownValues
for h
, so that you can query:
DownValues[h[1]]
(* {HoldPattern[Temp`h66[tag1]] :> 1, HoldPattern[Temp`h66[tag2]] :> 2} *)
so the symbol (Temp`h66
here) holds specific values for h[1]
, and the lookup is immediate. You can use things like
h[1][tag2]
(* 2 *)
just as normal, without thinking about all this mechanics. The only practical change here is that you have another level of indirection, so the lookup time is doubled - but in practice this will in most cases not be the main time sink.
Now, we can see which extra symbols are there:
Names["Temp`*"]
(* {"Temp`h66", "Temp`h67"} *)
Once you call Clear
, ClearAll
, or Remove
on h
, those symbols automatically get removed as well, since we overloaded these functions (via UpValues
):
Clear[h]
Names["Temp`*"]
(* {} *)
So, management of these "composite" objects is not much harder than if you only had one symbol.
Method 2: using Associations
(V10)
In V10, there is a new object introduced, Association
, which is an immutable associative array. Using these, one can do without extra symbols, and still have a constant-time lookup for a part of values of interested.
Essentially, you asked about nested hash tables. If we use Association
-s, then here is one possibility:
ClearAll[assoc];
assoc = Association[];
assoc /: (assoc[part_][rest__]=val_):=
Module[{inner=assoc[part]},
If[MatchQ[inner,_Missing],inner=<|{}|>];
inner[rest]=val;
assoc[part]=inner;
val
];
Now, we have:
assoc
(* <||> *)
assoc[1][tag1] = 1;
assoc
(* <|1 -> <|tag1 -> 1|>|> *)
you can see that the inner association has been automatically created and populated. Now:
assoc[1][tag2] = 2;
assoc
(* <|1 -> <|tag1 -> 1, tag2 -> 2|>|> *)
and finally:
assoc[2][tag1] = 3;
assoc[2][tag2] = 4;
assoc
(* <|1 -> <|tag1 -> 1, tag2 -> 2|>, 2 -> <|tag1 -> 3, tag2 -> 4|>|> *)
You can now query the values corresponding to say, key 1:
assoc[1]
(* <|tag1 -> 1, tag2 -> 2|> *)
The advantage of this scheme is that no management of extra state is necessary.
Generalization to any nesting depth
With some extra effort, and perhaps slightly changing the assignment syntax, you can have this work at any level, not just 2. Here is the code:
ClearAll[defAssoc];
SetAttributes[defAssoc,HoldFirst];
defAssoc[sym_Symbol,new:True|False:True]:=
(
If[new,sym=<|{}|>];
sym/:(sym[part_,rest__]=val_):=
set[sym[part,rest],val];
);
ClearAll[set];
SetAttributes[set,HoldAll];
set[sym_Symbol[part_,rest__],val_]:=
Module[{inner=sym[part]},
defAssoc[inner,MatchQ[inner,_Missing]];
inner[rest]=val;
sym[part]=inner;
val
];
Here is how you can use this:
ClearAll[assoc];
defAssoc[assoc];
assoc
(* <||> *)
Now, perform assignments (note a slightly different syntax):
assoc[1, tag1] = 1;
assoc[1, tag2] = 2;
assoc[2, tag1] = 3;
assoc[2, tag2] = 4;
assoc
(* <|1 -> <|tag1 -> 1, tag2 -> 2|>, 2 -> <|tag1 -> 3, tag2 -> 4|>|> *)
So far, this is the same as before. However, now you can also do:
assoc[2, tag3, tag31] = 5
assoc
(* <|1 -> <|tag1 -> 1, tag2 -> 2|>, 2 -> <|tag1 -> 3, tag2 -> 4, tag3 -> <|tag31 -> 5|>|>|> *)
and this will work for any depth. You can then query the parts of definitions which you need, add more or modify existing ones, as needed.
SubValues
? TrySubValues[h]
. Also a quick search forSubValues
led to this question and this one linked therein that might be of relevance $\endgroup$Cases[SubValues[h], HoldPattern[_[h[1][_]] :> _]]
works here. $\endgroup$HoldPattern
in yourCases
gets ignored, and then the_[]
inside theHoldPattern
will actually match theHoldPattern
. If this was intentional to avoid the behaviour ofRuleDelayed
inside cases, well then good job :). $\endgroup$HoldPattern
in myCases
matching is wrapping the whole rule; it's there so that the rule itself is matched as a pattern. I think (hope) that this is the standard way to match rules. TheHoldPattern
in the subvalues of h is ignored and I've used_[]
to denote a generic head (that here happens to beHoldPattern
as well. But I doubt that's the best way you can write this :) $\endgroup$