How to Set parts of indexed lists?

I can offer a sort of a solution, which has its shortcomings, but will allow you (more or less) to use the syntax you want. The idea is that you mark symbols you need, as "references", and then execute your code in a special dynamic environment where Part is overloaded.

Implementation

Here is the code. This function will mark you symbol as a reference.

ClearAll[makeReference];
makeReference[sym_Symbol] :=
  sym /: Set[sym[index_], rhs_] :=
     With[{index = index},
      With[{heldVal = Hold[sym[index]] /. DownValues[sym]},
        If[heldVal === Hold[sym[index]],
           Module[{ref},
              AppendTo[DownValues[sym], 
                    HoldPattern[sym[index]] :> ref]
           ]
        ];
        Hold[sym[index]] /. DownValues[sym] /. Hold[s_] :> (s = rhs);
      ]];

What happens is that we "softly" overload Set on this particular symbol via UpValues, so that an intermediate symbol is inserted where the actual data will be stored, and our symbol (for a given index) refers to that intermediate symbol. Since the latter has no restrictions on part assignments, we can assign parts of it directly at O(1) time.

However, the subtlety is that when we call Set[Part[s[ind],1,2],something], Set holds its first argument, and therefore, s can not communicate to Set that this is special (UpValues won't work here since the s is too deep inside an expression - on level 2 - while UpValues are only looked at at level 1). To solve this problem, we will overload Part, but do it locally within a local dynamic environment, to make this operation safer. This is a dynamic environment:

ClearAll[withModifiedPart];
SetAttributes[withModifiedPart, HoldAll];
withModifiedPart[code_] :=
   Internal`InheritedBlock[{Part},
      Unprotect[Part];
      Part /: Set[Part[sym_Symbol[index_], inds__], rhs_] :=
        With[{index = index},
          Hold[sym[index]] /. DownValues[sym] /. 
               Hold[s_] :> (s[[inds]] = rhs);
        ];
      Protect[Part];
      code];

Tests

Now, we can test this:

ClearAll[a];
makeReference[a];

and then

withModifiedPart[
   a[1] = Range[10];
   a[1][[2]] = 100;
   a[1]
]
 {1, 100, 3, 4, 5, 6, 7, 8, 9, 10}

Let's now measure some timings:

withModifiedPart[
    a[1] = Range[100000];
    Do[a[1][[i]] = a[1][[i]] + 1, {i, a[1]}];
    a[1]
] // Short // AbsoluteTiming
  {1.5126953,{2,3,4,5,6,7,8,9,<<99984>>,99994,99995,99996,99997,
    99998,99999,100000,100001}}

We can compare this to the time it takes for direct assignments:

aa = Range[100000];
Do[aa[[i]] = aa[[i]] + 1, {i, aa}]; // Short // AbsoluteTiming
 {0.2470703,Null}

So, for massive assignments, we are about 6 times slower (which I think is OK). We can also see how costly is the overloaded Part for normal assignments:

withModifiedPart[
  aa=Range[100000];
  Do[aa[[i]]=aa[[i]]+1,{i,aa}]
 ];//Short//AbsoluteTiming
  {0.2822266,Null}

from where it looks that those are slower by about 15 percents.

Conclusions

The suggested solution requires 2 modifications to your code:

  • call makeReference on symbols which you wish to use as indexed symbols with part assignment, prior to assigning to them.
  • Execute all your code containing such assignments, inside the withModifiedPart environment.

So, your original code will be changed to

ClearAll[matM];
makeReference[matM];

withModifiedPart[
  matM[i] = ConstantArray[1, {10, 10}];
  matM[i][[1, 10]] = 10
]

What about safety? I would argue that, for this particular case, modifying Part is safe enough. This is because, first, Part is overloaded softly, via UpValues. This means that, when Part is not inside some head which holds arguments, it will execute normally before it would even "think" of a new definition. Now, when it is inside some head which holds arguments, the new definition will only work if that head is Set. Note that no ne rules were added to the Set itself. And since normally, assignments to indexed variables are not allowed anyway, we don't risk breaking some internal behavior.

The performance here is worse than for direct assignments to symbol's parts, but overall seems acceptable.


Unexpectedly I don't favor Leonid's approach. I feel it costs too much performance, and performance is a primary reason for using an assignment like this.

My method is to store the data in a direct assignment to a separate symbol and use a special function to effect the assignment.

SetAttributes[set, HoldFirst]

set[sym_ @ idx_, val_] :=
 Module[{x},
   Quiet[sym @ idx =.];

   sym @ idx =. ^:= (sym /: sym @ idx =.; x =.);

   set[sym[idx][[part__]], vv_] := x[[part]] = vv;

   sym @ idx = x;
   x = val
 ]

Use:

set[a[x], {1, 2, 3}];

set[a[x][[2]], 7];

a[x]
{1, 7, 3}

Care is taken by the function to remove old data when new is assigned. It can also be manually cleared with a[x] =. and the hidden symbol will also be cleared.

Leonid cautiously avoids global modification to Part or Set. One could take a more bold approach for convenience sake and probably be safe as the pattern is rather specific. (In this case it will only match for the exact symbol[index] that is initialized with set.) This could be done with:

set[sym_ @ idx_, val_] :=
 Module[{x},
   Quiet[sym @ idx =.];

   sym @ idx =. ^:= (sym /: sym @ idx =.; x =.);

   Unprotect[Part];
   Set[sym[idx][[part__]], vv_] ^:= x[[part]] = vv;
   Protect[Part];

   sym @ idx = x;
   x = val
 ]

One would still need the initial set[a[x], . . .] to set up, but assignments to parts would be done with a[x][[i]] = . . .

Timings compared to Leonid's method

makeReference[a];

withModifiedPart[
  a[1] = Range[100000];
  Do[a[1][[i]] = a[1][[i]] + 1, {i, a[1]}];
]; // AbsoluteTiming
{0.6300009, Null}
AbsoluteTiming[
  set[a[1], Range[100000]];
  Do[set[a[1][[i]], a[1][[i]] + 1], {i, a[1]}];
]
{0.1800003, Null}

This is not a precise answer to the actual question, but the list of answers is from 2012 and IMO does urgently lack a note about Association. Since version 10 (2014) these will solve the underlying problem by offering an alternative for such cases:

ClearAll@matM
matM = Association[]
matM[i] = ConstantArray[1, {10, 10}];
matM[[Key[i], 1, 10]] = 10

For every case where one uses downvalues as a hashtable using Association instead like shown will also have many other advantages. I have not made tests but I would be very surprised if the above wouldn't be much more efficient than any of the alternatives. Of course using an Association will not work in all cases, e.g. when one needs a mixture of patterns and constant "indices" in the downvalues of a symbol. But for a vast majority of appications where one runs into the mentioned problem nowadays Association will certainly be the best solution.