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]] :> Temph66, HoldPattern[h[2]] :> Temph67,
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[Temph66[tag1]] :> 1, HoldPattern[Temph66[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`*"]
(* {"Temph66", "Temph67"} *)
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 forSubValuesled to this question and this one linked therein that might be of relevance – gpap Mar 26 '14 at 10:32Cases[SubValues[h], HoldPattern[_[h[1][_]] :> _]]works here. – gpap Mar 26 '14 at 10:52HoldPatternin yourCasesgets ignored, and then the_[]inside theHoldPatternwill actually match theHoldPattern. If this was intentional to avoid the behaviour ofRuleDelayedinside cases, well then good job :). – Jacob Akkerboom Mar 26 '14 at 11:25HoldPatternin myCasesmatching 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. TheHoldPatternin the subvalues of h is ignored and I've used_[]to denote a generic head (that here happens to beHoldPatternas well. But I doubt that's the best way you can write this :) – gpap Mar 26 '14 at 12:00HoldPattern, but this comes at the cost of more complex code. Another alternative isCases[SubValues[h], Unevaluated[_[_[h[1][___]], _]]], whereRuleDelayedwas replaced by a generic head. That is consistent with makingHoldPatterngeneric, and another advantage is that we can now useUnevaluatedrather thanHoldPatternto avoid evaluation. – Jacob Akkerboom Mar 26 '14 at 12:09