12
$\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$ Commented 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$ Commented Feb 15, 2020 at 22:18

4 Answers 4

11
$\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

Note

In the examples, I only used Hold to prevent 2+3 from being evaluated to 5, and to prevent the two matrices being added together in the leftmost expr column. You don't need to wrap your expression with Hold before passing it into terms. terms is defined to already wrap everything in Hold before doing anything else. This also serves to show that if you are using Hold to prevent evaluation of expr before passing it into terms, then ReleaseHold will clear all instances of Hold, whether placed there by you or terms itself.

$\endgroup$
16
$\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$ Commented Feb 16, 2020 at 2:21
  • $\begingroup$ @mwalth, you are welcome. And welcome to mma.se. $\endgroup$ Commented Feb 16, 2020 at 2:28
  • $\begingroup$ As always: cool answer from you! $\endgroup$ Commented 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$ Commented Jun 19, 2020 at 13:19
  • 1
    $\begingroup$ @yarchik, you can use terms[Evaluate@u] ? $\endgroup$ Commented 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$
2
  • $\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$ Commented Feb 16, 2020 at 2:33
  • $\begingroup$ This has a worse problem than not returning a singleton list! (a+b)^2 /. Plus -> List will become {a,b}^2 which results in {a^2,b^2}. $\endgroup$ Commented Apr 2, 2025 at 5:04
2
$\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$ Commented Feb 16, 2020 at 2:23
  • 1
    $\begingroup$ You can use StringSplit[term,{"+","-"}] this should work $\endgroup$ Commented Feb 16, 2020 at 8:23
  • $\begingroup$ Very good, thank you! $\endgroup$ Commented Feb 16, 2020 at 13:17

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.