Scan vs. Map vs. Apply

These three functions are similar (speaking commonly), and in some applications any of them could be used, yet they have very different special applications.

Rudimentarily:

  • Map wraps (sub)expressions in a given Head, and returns the modified input
  • Apply replaces Heads in (sub)expressions, and returns the modified input
  • Scan "visits" (sub)expressions, evaluates each of them, and returns Null

(For each example below f should be undefined; start with ClearAll[f].)

Map

Most basically Map works like this:

f /@ head[1, 2, 3]      (* shorthand for Map[f, head[1, 2, 3], {1}] *)
head[f[1], f[2], f[3]]

As with each of the functions under discussion it can operate at different levels:

Map[f, {{1, 2}, {3, 4}}, {0}]
Map[f, {{1, 2}, {3, 4}}, {1}]
Map[f, {{1, 2}, {3, 4}}, {2}]
Map[f, {{1, 2}, {3, 4}}, {0, 2}]
f[{{1, 2}, {3, 4}}]

{f[{1, 2}], f[{3, 4}]}

{{f[1], f[2]}, {f[3], f[4]}}

f[{f[{f[1], f[2]}], f[{f[3], f[4]}]}]

Map is an additive process; it inserts additional heads into an expression at the specified levelspec and then evaluates it.

If the expression is held nothing evaluates. (Held meaning the outermost Head has an Attribute such as HoldAll. Hold has this attribute. Do not confuse the Head with the Attribute.)

Print /@ Hold[a[1, 2], b[3, 4]]
Hold[Print[a[1, 2]], Print[b[3, 4]]]

Apply

Apply works like Map, except that instead of adding a Head it replaces an existing one, if a head exists at the specified level. Unlike Map it is frequently used at levelspec {0} with the short form @@, which replaces the outermost head:

f @@ head[1, 2, 3]   (* short form of Apply[f, head[1, 2, 3], {0}] *)
f[1, 2, 3]

A second short form (@@@) exists for levelspec {1}:

f @@@ {{1, 2}, {3, 4}}
{f[1, 2], f[3, 4]}

If a sub-expression is atomic (has no operable Head) Apply does not modify it:

f @@@ head[1, 2, x + y]
head[1, 2, f[x, y]]

Note that 1 and 2 are unmodified, while x + y (which has a FullForm of Plus[x, y]) had its Head replaced.

As with Map, Apply transforms an expression and then evaluates it in entirety; if it is held evaluation does not continue:

Print @@@ Hold[a[1, 2], b[3, 4]]
Hold[Print[1, 2], Print[3, 4]]

One way to "release" this expression for evaluation is to replace Hold with something else, like List:

List @@ Print @@@ Hold[a[1, 2], b[3, 4]]

12

34

{Null, Null}

Note here that the elements 1, 2, and 3, 4 are printed by Print, but since Print[. . .] itself evaluates to Null the output of the line is {Null, Null}.

Scan

Scan is also like Map, except that each individual expression which is wrapped in the specified head is evaluated outside of the main expression, and the main expression is never returned. If we Scan a function without side-effects over an expression we get nothing:

Scan[f, head[1, 2, 3]]

(This could also be written f ~Scan~ head[1, 2, 3].) We do not even get an output line because the output is Null and Mathematica does not print a lone Null output line (by convention). If we use a function with side-effects, such as Print, we get a different result:

Scan[Print, head[1, 2, 3]]

1

2

3

Crucially Scan will carry out its evaluation even when an expression is held because it only looks at the subexpressions:

Print ~Scan~ Hold[a[1, 2], b[3, 4]]

a[1,2]

b[3,4]

Its evaluation can also be interrupted because, unlike Map and Apply which transform the entire expression, then evaluate it, Scan works incrementally:

(Print[#]; If[# > 2, Return[]]) & ~Scan~ {1, 2, 3, 4, 5}

1

2

3

Because Scan does not first duplicate and then modify the entire expression it may be more memory efficient than the use of Map or Apply, but it will still unpack packed arrays therefore if memory efficiency is a priority other methods may be preferred.


Conclusion

I hope these simple examples illustrate that these three functions, while closely related, have unique characteristics that differentiate their use, and each has powerful applications that the others do not.

Recommended reading regarding performance of these and related functions:

  • Two ways of map a function on the list: Which one is faster?
  • Choose between `Apply` and `Map`

Notes on atomic objects

Not all expressions that have the appearance head[arguments] in FullForm are actually standard expressions in that form that can be manipulated with structural tools such as Apply. Instead they have a special internal format and they are merely displayed in the form head[arguments].

From the documentation on Atomic Objects:

All expressions in Mathematica are ultimately made up from a small number of basic or atomic types of objects.

These objects have heads that are symbols that can be thought of as "tagging" their types. The objects contain "raw data", which can usually be accessed only by functions specific to the particular type of object. You can extract the head of the object using Head, but you cannot directly extract any of its other parts.

Standard objects such as strings and integers are atomic, but so are other expressions that may not appear to be:

list = {1, "test", 1/2, 2 + 3 I};
list // FullForm
AtomQ /@ list
List[1, "test", Rational[1, 2], Complex[2, 3]]

{True, True, True, True}

Also atomic are more complex structures such as SparseArray, Graph, BooleanFunction, and (in recent versions) Image.

Atomic objects (or atoms) are handled differently from standard (compound) expression forms.

  • Rational[1, 2] is atomic even though it appears otherwise.
  • Even though 1 is formatted without an obvious head, Head will return Integer.
    (See Is there a summary of answers Head[] can give? for other examples.)

  • Apply does not work in the "normal" way on atoms:

    new @@@ list
    
    {1, "test", 1/2, 2 + 3 I}
    
  • Replace and kin often work on the apparent FullForm of atoms:

    Replace[list, head_[args__] :> {head, args}, {1}]
    
    {1, "test", {Rational, 1, 2}, {Complex, 2, 3}}
    
  • Functions may be overloaded to handle atoms differently; e.g. many functions treat a SparseArray as they would the Normal form expression:

    new @@ SparseArray[{1, 2, 3}]
    
    new[1, 2, 3]      (* not new[{1, 2, 3}] *)
    
  • Handling may appear somewhat irregular even within functions; Part will "extract" (or return) the head of Rationa[1, 2] but not anything else:

    Part[1/2, 0]
    
    Rational
    
    Part[1/2, 1]
    

    Part::partd: Part specification (1/2)[[1]] is longer than depth of object. >>

    (1/2)[[1]]
    

(Somewhat related, and perhaps of interest: Head and everything except Head?)


For a visual, animated, description of basic behavior of such functions, I recommend:

http://reference.wolfram.com/legacy/flash/

(You may want to turn on your computer's sound.)