Automatic Operations on Lists
& Pure Functions

Classnotes for CPSC 215

Christian Jacob — February 6 - 8, 2002.

Mapping Functions on Lists

Motivation

The automatic mapping of functions on lists will not solve all our problems. For example, let us look at Variables which delivers the variables in a polynomial.

Variables[x + y]

If we apply this function to a list, we get a flat list of all variables in the original list.

Variables[{x + y, x + z, y}]

But what are the variables of the single elements of the list? An iteration over the elements is somewhat awkward:

Table[
      Variables[{x + y, x + z, y}[[
          i]]], {i, 3}]

In order to get this functionality for any list, we would have to write a function as follows:

extractVariables[lst_List] := 
      Table[Variables[lst[[i]]], {i, 
          Length[lst]}]

extractVariables[{x + y, x + z, y}]

extractVariables[{(x^2) + 2 + z, a + b + (y^3), z - k, t}]

The Map Function

However, Mathematica offers a much more convenient way of mapping functions of one argument onto lists, namely using Map.

Map[Variables, {x + y, x + z, y}]

Because Map appears often in programs, there is also a short notation:

Variables /@ {x + y, x + z, y}

Examples: Mapping onto Lists

Here are a few more examples with Map:

(1) Here we calculate the length of each sublist:

Map[Length, {{a, b, c}, {d, e}, {f, g, h, i}, {z}}]

(2) Here we reverse the ordering of the elements of each sublist:

Reverse /@ {{a, b, c}, {d, e}, {f, g, h, i}, {z}}

(3) This is a different notation for the same functionality, with a different set of lists:

Map[Reverse, Table[Range[n], {n, 2, 5}]]

Compare this to applying Reverse to the whole list of sublists:

Reverse[Table[Range[n], {n, 2, 5}]]

(4) This maps a function g onto each sublist:

g /@ {{a, b}, {c, d}}

(5) Map also works on general expressions:

Map[g, f[a, g[b], c]]

Here an example with a polynomial, where we use the short notation (/@) for Map:

g /@ (c + 2  x + (x^2))

Map with Level Specifications

Map can take a level specification as a third argument.

This applies the mappings up to level 1, and acts like the default Map command:

Map[f, {{a, b, c}, {d, {e}}}, 1]

Here we map up to level 2:

Map[f, {{a, b, c}, {d, {e}}}, 2]

... and mapping up to level 3:

Map[f, {{a, b, c}, {d, {e}}}, 3]

In case we are not sure how deep an expression is, we can use ∞ (Infinity) to specify the highest level possible:

Map[f, {{a, b, c}, {d, {e}}}, ∞]

Mapping on Specific Levels

We may also specify to apply the mapping only on a specific level:

Map[f, {{a, b, c}, {d, {e}}}, {2}]

... or on selected levels:

Map[f, {{a, b, c}, {d, {e}}}, {2, 3}]

What will happen for the two special cases
	(i) mapping on level 0 and
	(ii) mapping on a level larger than the expression level?

Map[f, {{a, b, c}, {d, {e}}}, {0}]

Map[f, {{a, b, c}, {d, {e}}}, {∞}]

Mapping on General Expressions

The Map function is not restricted to lists only. It will work on almost any expression the way one expects it to work.

Here is an example with a polynomial:

h /@ (1 + 2 x + (x^2))

This is, of course, a special case where the head of the expression is Plus.

(1 + 2 x + (x^2)) // FullForm

So, for the Map function this is not different from the following:

h /@ g[1, 2  x, (x^2)]

Or we could map onto any other expression that is valid in Mathematica:

h /@ g[a, k[3], t, g[b, b]]

Note that the mapping is performed on the arguments, that is, the expressions on level 1. The outer head remains the same.

Variants of the Map Function

There are two variants of the Map function: MapAll, MapAt, and MapIndexed.

Actually, there is also a fourth version, MapThread, which we will discuss later on.

MapAll

MapAll maps a function to every subexpression, including the head.

MapAll[h, {{a, b}, {c, d}}]

Here is the notational shortcut:

h //@ {{a, b}, {c, d}}

This is in fact equivalent to calling the Map function with a level specification range:

Map[h, {{a, b}, {c, d}}, {0, ∞}]

Options[MapAll]

MapAll[h, α[β[a, b], γ[c, d]], 
      Heads -> True]

α[β[a, b], γ[c, d]] // TreeForm

MapAt

MapAt applies a mapping to elements at specific positions.

Here we apply the function g to the expression at position {2} in the list:

MapAt[g, {{a, b}, {c, d}}, 2]

Here is an alternative way of entering this matrix and visualizing the result:

MapAt[g,(({{a, b}, {c, d}})),2]//ColumnForm

Here we specify the first argument of the second subexpression:

MapAt[g,(({{a, b}, {c, d}})),{2, 1}]//ColumnForm

We can also specify several positions where to map to:

MapAt[g,(({{a, b}, {c, d}})),{{1, 2}, {2, 1}}]//ColumnForm

Note that one has to put brackets around part specifications:

MapAt[g, {a, b, c, d}, {{1}, {3}}]

This, however, does not work:

MapAt[g, {a, b, c, d}, {1, 3}]

MapIndexed

MapIndexed applies a function to the elements of an expression, giving the part specification of each element as the second argument.

MapIndexed[g, {a, b, c}]

MapIndexed[g, {{a, b}, {c, d}}, {2}] // MatrixForm

EXAMPLE 1:

With this we can write a function that attaches the position to each subexpression:

positionLabel[expr_, pos_List] := 
      (Subscript[expr, Apply[Sequence, pos]])

Here we get each matrix element associated with its position (= part specification):

MapIndexed[positionLabel, {{a, b}, {c, d}}, {2}] // 
      MatrixForm

This also works for expressions that have a more complicated structure:

MapIndexed[positionLabel, {h[a], b, g[k[t[c]], p]}, {0, ∞}] // 
      MatrixForm

But now we can actually identify the position of each element easily:

Extract[{h[a], b, t[k[3], l]}, {{3, 1}, {3, 1, 1}}]

EXAMPLE 2:

Here is another example where the indices turn out to be useful:

(aa = Table[Range[5], {5}]) // MatrixForm

Now let's rotate each row to the left according to its index. That is, we rotate the first row by one shift, the second row by two shifts, etc. With the MapIndexed command we can do this in a straight-forward way:

MapIndexed[RotateLeft, aa] // MatrixForm

Clear[aa]

SIDE NOTE: We can do the same with the columns if we turn the whole matrix by 90°, treat all columns as rows, and put the matrix back to its original orientation.

The operation for turning a matrix by 90° is called "transposition".

(abc=(({{a, aa, aaa}, {b, bb, bbb}, {c, cc, ccc}})))//MatrixForm

We use the Transpose command to rotate the whole matrix:

abc // Transpose // MatrixForm

MapIndexed[RotateLeft, abc // Transpose] // Transpose // 
      MatrixForm

So, column one is shifted once, column two is rotated twice, and column three is back to its original arrangement after three shifts.

Compare this to the original matrix:

abc // MatrixForm

Pure Functions = Functions without Names

Motivation

When we discussed the Select command we defined an auxiliary function similar to this one

myTest[x_] = ((x^2) > 10) && ((x^2) < 50);

to recognize the desired list elements.

Select[Range[100], myTest]

Listing all the squares from (1^2) to (8^2) we see why these are the selected elements:

Range[8]^2

Often we need a selection function only once, so there is no point in giving it a name.

Pure Functions with Function

Motivation

Having to come up with a name for a function, such as a selection function, which we only use once, can be avoided with the help of pure functions.

In many cases, pure functions offer also a more convenient notation.

Function[x, expression using the variable x]

The first argument of Function contains the name of the auxiliary (local) variable x, the second argument is the expression in x to be evaluated.

Here are a few examples:

Function[x, (x^2)]

Function[y, (1/y)]

Function[n, (Sqrt[n])]

Function[{x, y}, x - y]

The pure function for the selection problem above can be rewritten as follows:

Function[x, (x > 10) && (x < 50)]

Such objects are applied in the usual way, just like any other function (whether a built-in Mathematica function or a user-defined function):

Function[x, (x^2)][3]

Function[y, (1/y)][4]

Function[n, (Sqrt[n])][16]

Function[{x, y}, x - y][5, 3]

For our selection example, the argument 9 is substituted for x and the function body evaluated.

Function[x, (x > 10) && (x < 50)][9]

Calculations with Pure Functions: Map

Pure functions can easily be mapped on lists

Function[x, (x > 10) && (x < 50)]  /@  {1, 20, 100, 
        30}

Function[x, (1/(Sqrt[x]))] /@ Range[20]

Function[x, Range[x]/Length[Range[x]]] /@ Range[5]

Function[x, Sin[x]/Cos[x]] /@ Range[1, 5,  .1]

ListPlot[%, PlotJoined -> True, PlotRange -> All];

Function[x, Sin[x]/Cos[x]] /@ Range[1, 20,  .1];

ListPlot[%, PlotJoined -> True, PlotRange -> All];

Selection with Pure Functions

We can use pure functions in functions like Select.

Select[Table[i^2, {i, 10}], 
      Function[x, (x > 10) && (x < 50)]]

Select[Range[100], Function[x, (x^2) < 1000]]

Coming back to our selection example, using the following definition:

myTest[x_] = ((x^2) > 10) && ((x^2) < 50);

Here is the selection example from above, now not using the explicit function definition of myTest, but specifying the selection function on the spot, by using the right-hand side of the function definition:

Select[Range[100], 
      Function[x, ((x^2) > 10) && ((x^2) < 50)]]

But we still have to give a name to the local variable (= x).

Pure Functions with # and &

Getting Rid of the Variable Name ...

We do not need an explicit name for the symbol x in the pure function either. We can replace it with # and write only the function definitions.

\(\(Function[#\^2]\)[3]\)

\(\(Function[1\/#]\)[4]\)

\(\(Function[\@#]\)[16]\)

Function[#1 - #2][5, 3]

\(Select[Range[100], 
      Function[\((#\^2 > 10)\) && \((#\^2 < 50)\)]]\)

Even shorter: Getting Rid of the Function construct ...

Because pure functions appears often, there is an even shorter way to write them down: leave out the Function and delimitate the pure function with a &.

\(\(#\^2 &\)[3]\)

\(\(1\/# &\)[4]\)

\(\(\@# &\)[16]\)

#1 - #2 &[5, 3]

\(Select[Range[100], \((#\^2 > 10)\) && \((#\^2 < 50)\) &]\)

This shortcut notation enables us to write applications of functions in different ways:

Sqrt[a]

or

(Sqrt[a])

can be rewritten as

Sqrt[#] &[a]

or

\(a // \@# &\)

Well, this looks more complicated than it should be.

But let us have a look at a mapping example, where it makes more sense to use pure function notation.

\(Map[2  #\^2 + 3  # + 1 &, {x, y, z}] // TableForm\)

Or we can rewrite this in a more "cryptic" way:

\(\(\((2  #\^2 + 3  # + 1)\) &\) /@ {x, y, z}\)

Here is another example, for calculating the partial derivatives of the expression

expr = ((x - y)/(Sqrt[(x^2) + (y^2) + (z^2)]));

with respect to x, y, z, i.e. the gradient.

We would calculate the partial derivatives explicitly by saying

{D[expr, x], D[expr, y], D[expr, z]}

A much shorter version to do this, however, is like that:

D[expr, #] &  /@  {x, y, z}

Pure Functions with Several Arguments

In a pure function one can specify several arguments:

Function[{x, y, z}, (Sqrt[(x^2) + (y^2) + (z^2)])]

%[a, b, c]

Without giving the arguments names, we can refer to argument number 1, 2, 3, etc. by #1, #2, #3, etc.

Function[(Sqrt[#(1^2) + #(2^2) + #(3^2)])][a, b, c]

Function[(Sqrt[#(1^2) + #(2^2) + #(3^2)])][1, 1, 1]

And we can also use the shortcut notation:

(Sqrt[#(1^2) + #(2^2) + #(3^2)]) &[x, y, z]

Pure Functions in Definitions

Of course we can also write definitions with pure functions.

geometricMean = (((#1 #2 #3))^((1/3))) &;

This assigns the symbol geometricMean with the functionality of the expression defined above. Therefore, we can use geometricMean like any other defined or built-in function in Mathematica:

geometricMean[a, b, c]

The alternative definition of the form

geometricMean1[x_, y_, z_] := (((x y z))^((1/3)))

geometricMean1[a, b, c]

is a definition for the pattern geometricMean1[x_,y_,z_]. In contrast, the pure function

geometricMean = (((#1 #2 #3))^((1/3))) &;

can be interpreted as a definition for the head geometricMean.