4
$\begingroup$

I've been trying to write a simple function that turns an expression into a nested list. So for example, a+2b+3c becomes {a,{2,b},{3,c}}. This is what I wrote:

toList[x_] := Module[{}, 
                x = List@@x; 
                For[i=1, i <= Length[x]; i++, 
                  If[x[[i]] != List@@x[[i]], x[[i]] = toList[x[[i]]]]
                ];
                x
              ]

However, I keep getting errors like "Tag Plus in a+2b+3c is protected" and "a+2b+3c in the part assignment is not symbol." How can I fix what's going wrong?

$\endgroup$
11
$\begingroup$

The following does what you want:

Apply[List, a + 2 b + 3 c, All]

This simply applies List to all parts of the expression in one go.

Alternatively:

Replace[a + 2 b + 3 c, _[args___] :> {args}, All]
(* {a, {2, b}, {3, c}} *)

The idea here is to replace the head of any expression with List. Note that Replace[…,…,All] is not equivalent to ReplaceAll[…,…] (i.e. …/.…). The former replaces from the inside out, while the latter from the outside in (and ignores parts that have already been touched)

Your attempt

As to why your attempt doesn't work:

  • Function arguments are not variables (as in their value can't be modified). This is the reason for the "Tag … is protected" errors. To fix this, copy the value to a local variable
  • For does not localize the iteration variable, so i is shared across recursive calls. To fix this, localize i explicitly.
  • Equal/Unequal (==/!=) is for value equality, and doesn't evaluate in many cases (e.g. a==1 is not False). To do structural comparisons, use SameQ/UnsameQ (===/=!=).

Changing only what's necessary in your code yields the following working version:

toList[y_] := Module[
  {x = y, i},
  x = List @@ x;
  For[i = 1, i <= Length[x], i++,
   If[x[[i]] =!= List @@ x[[i]],
    x[[i]] = toList[x[[i]]]
    ]
   ];
  x
  ]

toList[a + 2 b + 3 c]
(* {a, 2 b, 3 c} *)

Further simplifications could be:

  • Replace the For loop with Do (which does localize the variable)

    toList[y_] := Module[
      {x = y},
      x = List @@ x;
      Do[
      If[x[[i]] =!= List @@ x[[i]],
        x[[i]] = toList[x[[i]]]
        ],
      {i, Length@x}
      ];
      x
      ]
    
  • Replace the For/Do loop with Map (the If now needs to return something in both cases, as the output is used):

    toList[y_] := Module[
      {x = y},
      x = List @@ x;
      x = Map[
        If[# =!= List @@ #,
          toList[#],
          #
          ] &,
        x
        ];
      x
      ]
    
  • Remove the unnecessary local variable x:

    toList[y_] := Map[
      If[# =!= List @@ #,
        toList[#],
        #
        ] &,
      List @@ y
      ]
    
  • Move the termination condition for the recursion into the function definition itself using Condition (/;):

    toList[y_] /; y =!= List @@ y := Map[
      toList[#] &,
      List @@ y
      ]
    toList[y_] := y
    
  • Use shorthands for Map (/@):

    toList[y_] /; y =!= List @@ y := toList /@ List @@ y
    toList[y_] := y
    
  • Remove the termination condition completely (it's not needed, as Map/Apply don't do anything on expressions that have "zero length" (i.e. atoms)

    toList[y_] := toList /@ List @@ y
    
$\endgroup$
  • $\begingroup$ Yes, it is a matter of replacement of heads. $\endgroup$ – Αλέξανδρος Ζεγγ Sep 2 '18 at 14:10
  • $\begingroup$ Wow, thanks! That is a really detailed explanation $\endgroup$ – Paradox Sep 2 '18 at 14:22
3
$\begingroup$
expr = a + 2 b + 3 (4 E^x + 3 x);

ReplaceRepeated[expr, f_[args___] /; (TrueQ[f =!= List]) :> List[args]]

(* {a, {2, b}, {3, {{4, {E, x}}, {3, x}}}} *)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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