17

Suppose I have list

a = Range[10]

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

in which I want to set some elements to be a list

a[[4 ;; 7]] = {1, 2, 3}; 

{1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 8, 9, 10}

Which is fine and dandy unless my part span is the same length as my list:

a[[4 ;; 6]] = {1, 2, 3};

{1, 2, 3, 1, 2, 3, 7, 8, 9, 10}

How can I force the assignment to behave consistently? In my case, I always want the first behaviour. But conceivably someone might always want the second behaviour with errors if the lengths don't match.

wxffles
  • 14,246
  • 1
  • 43
  • 75

4 Answers4

11

Perhaps

a = Range[10]

a[[4 ;; 6]] = Sequence@{1, 2, 3};

Returns

{1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 7, 8, 9, 10}

However, as Mr.Wizard noted in a comment, this is a LIIIEEE. If you do Information[a] you see that the Sequence is actually being saved. I find this weird, but anyway, can be solved with an extra

a=a

Also, forcing the lhs and rhs lists to be a different size should work a[[{4,5,6,4}]]={1, 2, 3}

Rojo
  • 42,601
  • 7
  • 96
  • 188
11
(a[[#]] = {1, 2, 3}) & /@ Range[4, 6];

You get:

In[1]:= a

Out[1]= {1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 7, 8, 9, 10}

A convenient thing to remember is that if your elements are not sequential it is still easy to set up:

(a[[#]] = {1, 2, 3}) & /@ {1, 3, 10};

In[2]:= a

Out[2]= {{1, 2, 3}, 2, {1, 2, 3}, 4, 5, 6, 7, 8, 9, {1, 2, 3}}
Vitaliy Kaurov
  • 73,078
  • 9
  • 204
  • 355
  • 2
    but this doesn't automate keeping the behaviour of Set consistent as the question asks (although it does do what is desired). then again neither does any other answer, nor anything I can come up with... – acl Mar 23 '12 at 01:09
  • The in-place modification aspect of this answer works best for my actual problem. But as acl says, the more general question remains. – wxffles Mar 23 '12 at 03:12
7

Here's a way that's in two-steps, but works nevertheless:

a[[4 ;; 6]] = \[FormalF][1, 2, 3];
a = a /. \[FormalF] -> List
Out[1]= {1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 7, 8, 9, 10}

Here's another solution using MapAt, but this loses the convenience of indexing with Span.

a = MapAt[{1, 2, 3} &, a, Transpose[{Range[4, 6]}]]
Out[2]= {1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 7, 8, 9, 10}

But the standard way to replace elements in a safe manner and for cases where you want different replacements for each, is to use ReplacePart as:

a = ReplacePart[a, Transpose[{Range[4, 6]}] -> {1, 2, 3}]
Out[3]= {1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 7, 8, 9, 10}
rm -rf
  • 88,781
  • 21
  • 293
  • 472
5

After reading the answers, this is one way more to do it (maybe too simple):

a[[4 ;; 6]] = ConstantArray[{1, 2, 3}, 3];

Thinking in it, you can do your own simple Set:

Attributes[simpleSet] = {HoldFirst};
simpleSet[lhs_, rhs_] := 
  With[{newrhs = ConstantArray[rhs, Length[lhs]]}, lhs = newrhs];

And use it as Set:

simpleSet[a[[4 ;; 6]], {1, 2, 3}];
{{1, 2, 3}, {1, 2, 3}, {1, 2, 3}}

And it returns the same as Set returns: what it is assigned.

a
{1, 2, 3, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, 7, 8, 9, 10}
FJRA
  • 3,972
  • 22
  • 31