24

Given a symbol t and an expression expr, how can I determine whether or not the symbol t appears somewhere in expr?

The best solution I have up with so far is:

Block[{t,s},(expr/.t->s)=!=expr]

which will return True if t is in expr, and False otherwise.

But this feels a bit like a hack because it's not really using /. because it's the right tool, but rather because /. happens to need to search through expr in order to do its unrelated task. This results in having to search through expr at least three times (I think?): once for the /., and twice for each side of the =!=, when clearly its possible to find t in only one search.

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Ian Hincks
  • 1,859
  • 13
  • 21
  • 10
    -1? Sure the answer is obvious once you know it, but I spent a very long time looking for an answer, and even longer trying to code my own answer. It's not like FreeQ is a terrible descriptive function name, and it's not listed in the 'Patterns' Guide, or the 'Testing Expressions' guide, or a bunch of other guides that might seem relevant to a newish user. – Ian Hincks Aug 23 '12 at 17:42
  • 3
    It's in "Testing and Searching List Elements" at least, but I have to agree that the omission of FreeQ[] in the "Testing Expressions" is a most unfortunate omission. – J. M.'s missing motivation Aug 23 '12 at 17:49
  • 1
    @J.M. i filed FreeQ as a suggestion to be added to "Testing Expressions" –  Aug 23 '12 at 17:51
  • 2
    Also even if it is obvious and easy to find, at least the question makes sense, is well-written, and doesn't need mind-reading skills to answer. This is more than can be said for lots of others that people upvote – acl Aug 23 '12 at 18:44
  • 4
    Don't forget FreeQ takes a levelspec optional argument. Can be useful if your quarry is hiding deep inside. Also there is Internal`DependsOnQ[expr,var] which attempts (operative word, that) to determine if expr has a functional dependence on x. Can be useful for tasks where detecting literal symbol presence is not quite what is wanted. Example: ``In[1]:= Internal`DependsOnQ[f[x], x]

    Out[1]= True``

    – Daniel Lichtblau Aug 23 '12 at 19:08

2 Answers2

23

Try FreeQ

FreeQ[x^2, t]
(*True*)
FreeQ[x^2, x]
(*False*)
11

ruebenko provides the built-in function, but supposing FreeQ was not provided we can find another way. You are on the right track to use ReplaceAll, but it would be much better to give a result as soon as the search pattern is found.

freeQ[expr_, pat_] :=
 Catch[expr /. pat :> RuleCondition@Throw[False, "freeQ"]; True, "freeQ"]

RuleCondition (1)(2)(3) is needed in the case that expr is Hold[ ] or the Throw will never evaluate.

Creating a massive expression (~1GB) and doing Timings (each in a separate session to circumvent caching) shows that this is reasonably fast.

big = Expand[(1 + x + y)^200 (2 - q)^150];

! FreeQ[big, q] // Timing

{1.03, True}

! freeQ[big, q] // Timing

{1.311, True}

Block[{q, s}, (big /. q -> s) =!= big] // Timing

{6.739, True}


Another approach that is cleaner, but perhaps less didactic, that will also return as soon as the pattern is found uses Cases:

freeQ2[expr_, pat_] :=
   {} === Cases[expr, pat, {0, -1}, 1, Heads -> True]

! freeQ2[big, q] // Timing

{0.983, True}

An important difference is that ReplaceAll (short form /.) will scan from the outside in while Cases, like most Mathematica functions, will scan inside out. See:

And more examples:

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • @Mr.Wizard If I am interested in testing for more than one variable I defined freeQ[a_, b_] := FreeQ[a, b] /; Length[b] <= 1; freeQ[a_, b_] := And @@ Map[FreeQ[a, #] &, b] /; Length[b] > 1 Is this the most efficient way to proceed? Why isn't it the default behaviour? – chris Aug 23 '12 at 18:57
  • @chris I'd suggest trying Alternatives: FreeQ[expr, x | y | z] Or do you mean something else? – Mr.Wizard Aug 23 '12 at 19:00
  • I understand your answer as I should use freeQ[a_, b_List] := FreeQ[a, Or@@b]/; Length[b] > 1. (I was after FreeQ taking lists) – chris Aug 23 '12 at 19:07
  • @chris no, I mean: freeQ[a_, b_List] := FreeQ[a, Alternatives @@ b] -- see Alternatives. – Mr.Wizard Aug 23 '12 at 19:09
  • Thanks. I guess I have to wrap my head around Trott-Strzebonski, now. :) – rcollyer Aug 23 '12 at 19:20
  • Thanks for the reference for RuleCondition! – Silvia Aug 24 '12 at 06:00