Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

Consider:

h[1][tag1] = 1;
h[1][tag2] = 2;
h[2][tag1] = 3;
h[2][tag2] = 4;

Is there a fast way to obtain downvalues for only h[1], or h[2]? For example

DownValues[h[1]]

does not work, and neither

DownValues[h]

(OwnValues gives the same behavior).

share|improve this question
2  
Are these not SubValues? Try SubValues[h]. Also a quick search for SubValues led to this question and this one linked therein that might be of relevance –  gpap Mar 26 at 10:32
    
great! SubValues[h] works. thanks! But SubValues[h[1]] doesn't. Of course, one can extract these from SubValues[h] but that might be problematic for many subvalues. I still wonder if there is a way to index them directly. –  zorank Mar 26 at 10:37
    
Yep, I think you'd have to find a good way to extract them from the list of subvalues. I don't trust myself to make up a pattern that is failsafe (hence I am not answering this) but Cases[SubValues[h], HoldPattern[_[h[1][_]] :> _]] works here. –  gpap Mar 26 at 10:52
    
@gpap nice comment, it encouraged me to look further. Note that the HoldPattern in your Cases gets ignored, and then the _[] inside the HoldPattern will actually match the HoldPattern. If this was intentional to avoid the behaviour of RuleDelayed inside cases, well then good job :). –  Jacob Akkerboom Mar 26 at 11:25
    
@JacobAkkerboom Yes, the HoldPattern in my Cases 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. The HoldPattern in the subvalues of h is ignored and I've used _[] to denote a generic head (that here happens to be HoldPattern as well. But I doubt that's the best way you can write this :) –  gpap Mar 26 at 12:00

2 Answers 2

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.

share|improve this answer
h[1][tag1] = 1;

h // SubValues

gives

{HoldPattern[h[1][tag1]] :> 1}

Note that really DownValues don't play a role here. For what it's worth here is my personal opinion. I don't like definitions that generate SubValues very much, I avoid them even more than UpValues. In this case you can avoid SubValues and have DownValues instead as follows. f plays the role of h here.

f[1] = g;
g[tag1] = 1;

Then

f[1][tag1]

gives

1

You can now inspect/use the definitions by evaluating DownValues[g] and DownValues[f].

If you really want to get the definitions from the SubValues

This is a modification of gpap's comment.

SetAttributes[getDefs, HoldAll];
getDefs[expr_] :=
 Function[
   Null,
   Cases[SubValues[#], 
    Unevaluated[
     Verbatim[RuleDelayed][Verbatim[HoldPattern][expr[___]], _]]]
   ,
   HoldAll
   ] @@ Hold[expr][[All, 0]]

Then

getDefs[h[1]]

gives

{HoldPattern[h[1][tag1]] :> 1, HoldPattern[h[1][tag2]] :> 2}

About getDefs

The code assumes you have a definition like h[a][b]:=c, it won't work if you have more brackets.

Symbols can have OwnValues and SubValues simultaneously (which is something I recommend you to avoid even more). getDefs should work correctly even if this is the case. The trick with Hold[expr][[All,0]] is used to deal with this case. The code can be made shorter, but then the user has to tell you what the relevant symbol is, even though that can be derived from expr.

Verbatim was needed twice, once because if HoldPattern occurs in the second argument of MatchQ, it is ignored. Verbatim was needed another time because if an expression with head RuleDelayed is the second argument of Cases, this means something special (i.e. replace the patterns found).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.