10
$\begingroup$

I'm looking for a nice way to split an expression into a list of the terms (i.e. the addends).

The desired behavior in:

terms[a^2 + a^-2 + c]  
(* Out: {a^2, a^-2, c} *)
terms[a*b*c]
(* Out: {a*b*c} *)

I've tried a few solutions. MonomialList doesn't work if the expression is not a polynomial. I've also tried

Level[a + b + c, 1]
(* Out: {a, b, c} *)

But the problem is it breaks down for monomials.

Level[a, 1]
(* Out: {} *)
Level[a*b*c, 1]
(* Out: {a, b, c} *)

The desired output would be {a} and {a b c} respectively.

Thank you in advance!

$\endgroup$
2
  • $\begingroup$ terms[a*b*c] but terms here are a and b and c. Unless you are using your own definition of what a term is. So you can use List @@ expr to obtain the terms. expr = a^2 + a^-2 + c; List @@ expr gives {1/a^2, a^2, c} and expr = a*b*c; List @@ expr gives {a, b, c} etc.. $\endgroup$
    – Nasser
    Feb 15, 2020 at 22:12
  • $\begingroup$ @Nasser Thank you for your reply. To clarify, I am using "term" to mean addend. I am finding that the ` List @@ expr ` similarly does not handle monomials correctly. Namely expr = a; List @@ expr returns just a rather than {a}. $\endgroup$
    – mwalth
    Feb 15, 2020 at 22:18

4 Answers 4

5
$\begingroup$

Applying new Heads

Mathematica has a built-in function for applying a specific head in an expression. Both of these are (almost) equivalent:

Apply[List, expr]
List@@expr

Baby Steps and Single Use Cases

Apply replaces a head(s) in an expression with the specified head. The default levelspec is {0}, meaning it only applies to the top level (the Head of expr itself) when you don't specify anything else.

List applied to 2 expressions

When using the curried form (List@@expr), expr is evaluated before applying your specified head. Parentheses help protect you from this.

(Note that this first one is evaluated as (List@@a)+b+c and a has no head to replace.)

Examples of the curried form of Apply

Alternatively, to be certain what you're applying your new head to, you could Defer evaluation, but then you need use the curried form of MapApply (@@@) to Apply List to the expression inside Defer, not to Defer itself.

MapApply is needed when you Defer evaluation

Writing A Function To Do This

To do this right, we need something stronger than Defer. We'll need Hold and ReleaseHold.

Extracting Only Addition Terms

It's easy to use Apply to replace the top level head with List. It's still relatively easy to selectively replace Plus with List when it's the level 0 head. Handling the monomials (when the head is not Plus) is a larger challenge.

  • If the head is Plus, you want it replaced with List
  • If the head is not Plus, you want to wrap List around the expression

Without building If into the function, it's tricky to satisfy both conditions at the same time. Most attempts to satisfy one condition gave one too many or one too few instances of List in the other case.

It took quite a bit of debugging, but the solution is to wrap everything in List, then if Plus is the unevaluated head, replace it with Sequence. This always applies List, and if we have Plus, we get rid of it and just pass its sequence of parameters into the outer list, without creating a second list. Because we need wrap Hold around everything, we now need Extract to get at the buried Plus.

Summary

  1. Use HoldAll to ensure everything comes into the function without modification
  2. Wrap everything in Hold to prevent modification while working with the expression
  3. Reach past Hold and Extract the original head
  4. If that head is Plus, replace it with Sequence
  5. Take this head and Apply it back inside the held expression
  6. ReleaseHold
  7. Put everything inside List
    • Sequence will absorb into List and disappear, any other head will remain

You need both these lines to make it work.

SetAttributes[terms, HoldAll]
terms[expr_] := {ReleaseHold[Apply[Replace[Extract[Hold[expr], {1, 0}], Plus -> Sequence], Hold[expr], {1}]]}

Examples

The FullForm column shows that there aren't any spare Hold, Defer, or Unevaluated floating around.

Examples

$\endgroup$
13
$\begingroup$
ClearAll[terms]
SetAttributes[terms, HoldAll]
terms[Plus[a__]] := {a}
terms[a_?AtomQ] := {a}
terms[a_] := a

Examples:

ClearAll[a, b, c, d]

terms[a]

{a}

terms[a + b + c]

{a, b, c}

terms[a b c]

a b c

terms[a^2 + a^-2 + c]

{a^2, 1/a^2, c}

terms[a b c + 3 Sin[c + d] + Log@d]

{a b c, 3 Sin[c + d], Log[d]}

$\endgroup$
6
  • 2
    $\begingroup$ This works perfectly! I appreciate you putting in the effort. $\endgroup$
    – mwalth
    Feb 16, 2020 at 2:21
  • $\begingroup$ @mwalth, you are welcome. And welcome to mma.se. $\endgroup$
    – kglr
    Feb 16, 2020 at 2:28
  • $\begingroup$ As always: cool answer from you! $\endgroup$
    – mgamer
    Feb 17, 2020 at 9:52
  • $\begingroup$ But when I write u=a+b and terms[u] your function does not work. Do you know how to generalize? $\endgroup$
    – yarchik
    Jun 19, 2020 at 13:19
  • 1
    $\begingroup$ @yarchik, you can use terms[Evaluate@u] ? $\endgroup$
    – kglr
    Jun 19, 2020 at 20:44
5
$\begingroup$

For the examples you mentioned, the following simple replacement works:

a^2 + a^-2 + c /. Plus -> List

(* Out: {1/a^2, a^2, c} *)

Note that the ordering is not retained, but then you probably shouldn't depend on the order of monomials anyway because MMA might rewrite your expression on its own to conform to its "canonical" format (e.g. evaluating b - a returns -a + b).

Similarly,

a*b*c /. Plus -> List     (* Out: a*b*c *)
a /. Plus -> List         (* Out: a     *)
$\endgroup$
1
  • $\begingroup$ Thank you for your response! This seems to work well for expressions with multiple expressions, but I'm finding that abc /. Plus->List returns abc whereas I would hope for it to return the singleton list {abc}. Do you know how to modify so that it returns a list regardless of the number of terms in the input? $\endgroup$
    – mwalth
    Feb 16, 2020 at 2:33
1
$\begingroup$

No sure whether I got your point, but you can do something like (o.k. broth-force attack ;-) ):

    makeList[term_] := 
 ToExpression /@ StringSplit[ToString[term, InputForm], "+"
   ]

then

temp = a^2 + a^-2 + c

and

makeList@temp

yields:

{1/a^2, a^2, c}
$\endgroup$
3
  • 1
    $\begingroup$ Thank you for the response. This was a route I thought about going also. I realize I didn't specify this in the question, but I also need it to split terms that have a minus "-" in addition to a plus "+". Do you know if this is possible with your method? $\endgroup$
    – mwalth
    Feb 16, 2020 at 2:23
  • 1
    $\begingroup$ You can use StringSplit[term,{"+","-"}] this should work $\endgroup$
    – mgamer
    Feb 16, 2020 at 8:23
  • $\begingroup$ Very good, thank you! $\endgroup$
    – mwalth
    Feb 16, 2020 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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