Chapter 3. Data Structures

Well I live with snakes and lizards And other things that go bump in the night

Higher mathematics is rich in structures and formalisms that take mathematics beyond the realm of numbers. This chapter includes a potpourri of recipes for data structures and algorithms that arise in linear algebra, tensor calculus, set theory, graph theory, and computer science. For the most part, lists form the foundation for these structures. Mathematica gains a lot of mileage by representing sets, vectors, matrices, and tensors using lists because all the generic list operations are available for their manipulation. Of course, a list, a set, and a tensor are very distinct entities from a mathematical point of view, but this distinction is handled by special-purpose functions rather than special-purpose data structures.

This introduction reviews the most common operations associated with list structures but is not an exhaustive reference. These operations will be used frequently throughout this book, so you should have some basic familiarity.

The foundation of most data structures in Mathematica is the list. It is difficult to do much advanced work with Mathematica unless you are fluent in its functions for list processing. To this end, the initial recipes revolve around basic list processing. A list in Mathematica is constructed using the function List[elem1,elem2,...,elemN] or, more commonly, with curly brackets {elem1,elem2,...,elemN}. There is no restriction on the nature of these elements. They could be mixtures of numbers, strings, functions, other lists, or anything else Mathematica can represent (like graphic or sound data).

The first thing you need to know about lists is how to generate them. Table is the workhorse function for doing this. It has several variations that are most easily explained by example.

List Functions

In addition to Table, Mathematica has several special-purpose list constructors: Range, Array, ConstantArray, DiagonalMatrix, and IdentityMatrix. These functions are less general than Table but are clearer and simpler to use when applicable. For example, consider IdentityMatrix and its Table equivalent.

List Functions

Sometimes using a special-purpose list constructor is more verbose. Consider these equivalent ways of generating an array of ten 1s. Here, 1& is the function that always returns 1.

In[9]:=  Array[1&, 10] == ConstantArray[1, 10]
Out[9]=  True

Once you have one or more lists, you can compose new lists using functions like Append, Prepend, Insert, Join, and Riffle.

In[10]:=  list1 = Range[10]
Out[10]=  {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

In[11]:=  list2 = list1 ^ 2
Out[11]=  {1, 4, 9, 16, 25, 36, 49, 64, 81, 100}

In[12]:=  (*Add elements to the end.*)
          Append[list1, 11]
Out[12]=  {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}

In[13]:=  (*Add elements to the front.*)
          Prepend[list1, 0]
Out[13]=  {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

In[14]:=  (*Insert elements at specific positions.*)
          Insert[list1, 3.5, 4]
Out[14]=  {1, 2, 3, 3.5, 4, 5, 6, 7, 8, 9, 10}

In[15]:=  (*Negative offsets to insert from the end*)
          Insert[list1, 3.5, -4]
Out[15]=  {1, 2, 3, 4, 5, 6, 7, 3.5, 8, 9, 10}

In[16]:=  (*You can insert at multiple positions {{i1},{i2},...,{iN}}.*)
          Insert[list1, 0, List /@ Range[2, Length[list1]]]
Out[16]=  {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10}

In[17]:=  (*Join one or more lists.*)
          Join[list1, list2]
Out[17]=  {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100}

In[18]:=  (*Riffle is a function specifically designed to interleave elements.*)
          Riffle[list1, 0]
Out[18]=  {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10}

The flip side of building lists is taking them apart. Here you will use operations like Part, First, Last, Rest, Most, Take, Drop, Select, and Cases.

List Functions

See Chapter 5 for more information on patterns.

You rearrange and restructure lists using functions such as Reverse, RotateLeft, RotateRight, Flatten, Partition, Transpose, and Sort

In[28]:=  Reverse[list1]
Out[28]=  {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}

In[29]:=  RotateLeft[list1]
Out[29]=  {2, 3, 4, 5, 6, 7, 8, 9, 10, 1}

In[30]:=  RotateRight[list1]
Out[30]=  {10, 1, 2, 3, 4, 5, 6, 7, 8, 9}

Partition and Flatten are very versatile functions for creating and removing structure. Flatten can be thought of as the inverse of Partition. Here, repeated partitioning using Nest converts a list to a binary tree.

List Functions

Flatten can also take a level that tells it to flatten only up to that level.

List Functions

Many of these functions have advanced features, so you should refer to the Mathematica documentation for each to understand their full capabilities. I will use these functions frequently throughout this book without further explanation, so if you are not already familiar with them, you should take some time to experiment on your own.

A vector is also represented by a list, but Mathematica has a special representation called a SparseArray that can conserve space when a vector contains many zero entries (see 3.8 Using Sparse Arrays to Conserve Memory). Matrices and tensors are naturally represented as nested lists; these likewise can use SparseArrays.

Vector math is supported by the fact that most mathematical operations have the attribute Listable, meaning that the operations automatically thread over lists.

In[43]:=  (*Multiplication and subtraction of a vector by a scalar*)
          3 * list1 - 3
Out[43]=  {0, 3, 6, 9, 12, 15, 18, 21, 24, 27}

In[44]:=  (*Listable is the relevant property.*)
          Intersection[Flatten[Attributes[{Times, Plus, Minus, Divide, Power}]]]
Out[44]=  {Flat, Listable, NumericFunction,
           OneIdentity, Orderless, Protected, ReadProtected}

Same-sized vectors and matrices can also be added, multiplied, and so on, in an element-by-element fashion.

In[45]:=  Range[10] ^ Range[10, 1, -1]
Out[45]=  {1, 512, 6561, 16384, 15625, 7776, 2401, 512, 81, 10}

Vector-specific operations are also supported. Some of the more advanced operations are in a package called VectorAnalysis‘, including CrossProduct, Norm, Div, Grad, Curl, and about three dozen others. Use ?VectorAnalysis'* after loading the package to see the full list.

In[47]:=  u = {-1,0.5,1}; v = {1,-0.5,1};

In[48]:=  u.v
Out[48]=  -0.25

In[49]:=  Norm[u]
Out[49]=  1.5

In[50]:=  Orthogonalize[{u, v}]
Out[50]=  {{-0.666667, 0.333333, 0.666667}, {0.596285, -0.298142, 0.745356}}

In[51]:=  Projection[u,v]
Out[51]=  {-0.111111, 0.0555556, -0.111111}

CrossProduct is not built in, so you must load a special package.

In[52]:=  Needs["VectorAnalysis" "]

In[53]:=  CrossProduct[u,v]
Out[53]=  {1.,2.,0.}

If you need to sort lists containing objects more complicated than scalars, you will need to be comfortable with expressing the order relation function. Here are some examples.

In[80]:=  data = {
             {"21 Mar 2007 14:34:30", 10.1, 12.7, 13.3},
             {"21 Jun 2005 10:19:30", 10.3, 11.7, 11.7},
             {"21 Aug 2006 15:34:01", 11.7, 16.8, 8.6},
             {"21 Aug 2006 09:34:00", 11.9, 16.5, 8.6}
            };
          (*Sort the data by the time entry,
          which must be converted to an absolute time to be properly ordered.*)
          Sort[data,
            Less[AbsoluteTime[{#1[[1]], {"Day", "MonthNameShort", "Year", "Time"}}],
              AbsoluteTime[{#2[[1]],
                {"Day", "MonthNameShort", "Year", "Time"}}]] &] // TableForm
Out[81]//TableForm=
             21 Jun 2005 10:19:30 10.3 11.7 11.7
             21 Aug 2006 09:34:00 11.9 16.5 8.6
             21 Aug 2006 15:34:01 11.7 16.8 8.6
             21 Mar 2007 14:34:30 10.1 12.7 13.3

For practical sorting, you will never need to look beyond Sort, because it is both fast and flexible. However, if you are interested in sorting from an algorithmic perspective, Mathematica also has a package called Combinatorica`, which contains some sorting routines that use specific algorithms (SelectionSort, HeapSort).

In[82]:=  Needs["Combinatorica`"]

In[83]:=  SelectionSort[list, Less]
Out[83]=  {-73, -50, -45, -43, -20, 2, 42, 50, 66, 84}

Of course, there is probably no practical reason to use SelectionSort since its asymptotic behavior is O(n^2), whereas Sort uses a O(n log n) algorithm. You can count the number of comparisons each sort makes using a custom comparison function. The framed number is the comparison count.

Discussion

Heap sort is O(n log n), but the Combinatorica` implementation is somewhat crippled because the ordering operation cannot be customized.

In[88]:=  HeapSort[list]
Out[88]=  {-73, -50, -45, -43, -20, 2, 42, 50, 66, 84}

If you are keen to do this experiment with HeapSort, you can easily make a customizable version, since the source code is available.

Discussion

It is unfortunate that we have to hack HeapSort to give it customizable ordering function. When you develop your own general-purpose functions, it pays to consider facilities that allow you and other users to customize the details while leaving the essential algorithm intact. This is the essence of what is called generic programming. Chapter 2 has several recipes that demonstrate how to create more generic functions.

One application of sorting is performing efficient search. The Combinatorica` package provides the function BinarySearch, which requires a sorted list. BinarySearch returns the index of the first occurrence of a search key, if found. If the key is not found, it returns index + 1/2, indicating that the key belongs between index and index + 1 if it were to be inserted.

Discussion

3.3 Determining Order Without Sorting discusses how to determine sorted order without rearranging the elements of the list.

A good overview of various sorting algorithms can be found at http://bit.ly/2bRckv .

3.2 Sorting Lists discusses sorting.

OrderedQ tests if a list is ordered, and Order compares two expressions, returning –1 (Less), 0 (Equal), or 1 (Greater).

A generalized permutation matrix has the same zero entries as the corresponding permutation matrix, but the nonzero entries can have values other than 1.

Discussion

You can easily enumerate all n! permutation matrices of size n.

In[143]:=  allPermutationMatrices[n_] :=
            permutationMatrix[#] & /@ Permutations[Range[n]]
Discussion

It is also easy to detect if a matrix is a row permutation of another matrix: simply remove each row from ml that matches m2 and see if you are left with no rows. Of course, you must also check that the matrices are the same size. A check for column permutation is just a check for row permutations on the transpose of each matrix.

In[145]:=  isRowPermutation[m1_, m2_] :=
            Length[m1] == Length[m2] && Fold[DeleteCases[#1, #2] &, m1, m2] {}
           isMatrixPermutation[m1_, m2_] := isRowPermutation[ml, m2] ||
             isRowPermutation[Transpose[ml], Transpose[m2]]

You can verify this on some test cases.

In[147]:=  (*Obviously a matrix is a permutation of itself.*)
           isMatrixPermutation[m1, m1]
Out[147]=  True

In[148]:=  (*Test a row permutation.*)
           isMatrixPermutation[m1, p1m1]
Out[148]=  True

In[149]:=  (*Test a column permutation.*)
           isMatrixPermutation[m1, m1p1]
Out[149]=  True

In[150]:=  (*A matrix and its tranpose are not permutations unless symmetric.*)
           isMatrixPermutation [m1, Transpose [m1]]
Out[150]=  False

You may be thinking that matrix permutations via linear algebra will only apply to matrices of numbers, but recall that Mathematica is a symbolic language and, thus, not limited to numerical manipulation. Here we do a Dot product on a matrix of graphics!

Discussion

This chess demo lacks some aesthetics (the squares move along with the rooks), but it does illustrate the generality of the permutation matrix.

3.5 Constructing Matrices of Specific Structure showed how to use SparseArray and Band to create tridiagonal matrices.

A level specification (or levelspec) is the key for surgically manipulating lists that contain many levels. Most of Mathematica’s functions that deal with lists have variations that take levelspecs. Position is one such function. Consider a list of integers that has values nested up to eight levels.

Solution

If you use Position to search for 1, you get a list of all positions that have the value 1.You can verify this using Extract.

In[191]:=  Position [deep, 1]
Out[191]=  {{1}, {2, 5, 1}, {2, 5, 3}, {2, 6, 1}, {2, 6, 2, 1, 1, 1, 1},
            {2, 6, 2, 1, 1, 2}, {2, 6, 2, 1, 2, 1}, {2, 6, 2, 2}, {2, 7}, {2, 8}, {2, 9}}

In[192]:=  Extract [deep, Position [deep,1]]
Out[192]=  {l,l,l, 1, 1, 1, 1, 1, 1, 1, 1}

Suppose you do not want the Is at every level. This is where levelspecs come in handy.

Use a single positive integer n to search at all levels up to and including n.

In[193]:=  (*Only search up to level two.*)
           Position [deep, 1, 2]
Out[193]=  {{1}, {2, 7}, {2, 8}, {2, 9}}

Enclosing the level {n} in a list restricts search to that level.

In[194]:=  (*Only search at level two.*)
           Position [deep, 1, {2}]
Out[194]=  {{2, 7}, {2, 8}, {2, 9))

The list notation {n,m} restricts search to levels n through m, inclusively.

In[195]:=  (*Search at levels three through five.*)
           Position [deep, 1, {3, 5}]
Out[195]=  {{2, 5, 1}, {2, 5, 3}, {2, 6, 1}, {2, 6, 2, 2}}

Negative level specification of the form -n looks for objects that themselves have depth n.

In[196]:=  Position [deep, 1, -1]
Out[196]=  {{1}, {2, 5, 1}, {2, 5, 3}, {2, 6, 1}, {2, 6, 2, 1, 1, 1, 1},
            {2, 6, 2, 1, 1, 2}, {2, 6, 2, 1, 2, 1}, {2, 6, 2, 2}, {2, 7}, {2, 8}, {2, 9}}

In[197]:=  (*See the discussion for why this is empty and must be empty.*)
           Position [deep, 1, -2]
Out[197]=  {}

We used Position to get a feel for level specifications because it is easy to judge, based on the length of each position sublist, the depth of each item found. However, you may be surprised that the last example was empty. It is easy to mistakenly think that negative level specification means searching from the bottom of the tree up because this seems analogous to the way negative indices work in functions like Part. This is not the case. A negative level specification means only looking for items with specified depth after dropping the minus sign. Any scalar (like 1) has depth 1, including complex numbers.

In[198]:=  {Depth[1], Depth[3.7], Depth["foo"], Depth[1 + 7 I]}
Out[198]=  {1, 1, 1, 1}

From this, it follows that a scalar will never be found by using a negative depth value less than -1.

Another important function for illustrating the use of level specifications is Level. Its function is to retrieve all objects at the specified level(s).

In[199]:=  Level [deep, {2}]
Out[199]=  {2, 3, 4, 5, {1, 6, 1, 7}, {1, {{{{1, 8}, 1}, {1}}, 1}}, 1, 1, 1, 9, 10, 11}

Objects at level {2} may have a variety of depths.

In[200]:=  Depth /@ Level [deep, {2}]
Out[200]=  {1, 1, 1, 1, 2, 6, 1, 1, 1, 1, 1, l}

Objects at level {-2} will only have a single depth by definition.

In[201]:=  Level [deep, {-2}]
Out[201]=  {{1, 6, 1, 7}, {1, 8}, {1}}

In[202]:=  Depth /@ Level [deep, {-2}]
Out[202]=  {2, 2, 2}

A picture helps reinforce this. Note that each tree has two levels.

Discussion

Note the difference between {-2}, meaning exactly depth 2, and -2, meaning depth 2 or more.

In[204]:=  Depth /@ Level [deep, -2]
Out[204]=  {2, 2, 3, 2, 4,5,6, 7}

Once you have mastered level specifications, the functions Apply, Cases, Delete, DeleteCases, Extract, FreeQ, Level, Map, Maplndexed, MemberQ, Position, Replace, and Scan take on more power and precision because they each have versions that use levelspecs.

Here are some examples in which we extract, delete, and modify the contents of a deeply nested expression. This time we use an algebraic expression.

Discussion

Chapter 2, has recipes that deal with the specifics of Apply, Map, Maplndexed, and Scan.

Chapter 4, has recipes that deal with the specifics of Cases, DeleteCases, FreeQ, MemberQ, and Replace

You can use Mathematica’s ability to represent arbitrarily large integers as a means of implementing bit vectors. Using Mathematica’s UpValue convention (see Chapter 2, DownValues and UpValues) you can make bit vectors adopt the familiar interface used by lists. When you create custom data structures like this, you can give them an output format that hides the details of their internal representation.

In[215]:=  (*Make a bit vector from a list of bit value.*)
           makeBitVector[bits_List] :=
            bitvec[FromDigits[Reverse[bits], 2], Length[bits]]
           (*Make a bit vector of a specified
            length. Values are initialized to 0.*)
           makeBitVector[1_: 32] := bitvec[0, 1]
           (*Set bit at index to 0 or 1.*)
           setBit [bitvec[n_, 1_], index_Integer, 1] :=
            Module[ {n2 = BitSet[n, index -1]}, bitvec[n2, Max[1, BitLength[n2]]]]
           setBit [bitvec[n_, 1_], index_Integer, o] :=
            bitvec[BitClear [n, index - 1], 1]
           SetAttributes[setBitOf, HoldFirst]
           setBitOf [name_symbol, index_Integer, bit_ /; bit === 0 || bit === 1] :=
            name = setBit [name, index, bit]
           (*Get the first bit value.*)
           bitvec /: First [bitvec[n_, _]] := BitGet[n, 0]
           (*Get the rest of the bits after the first as a new bit vector.*)
           bitvec /: Rest [bitvec [n_, 1_]] := bitvec [Floor [n / 2],1 - 1]
           (*Get bit at index.*)
           bitvec /: Part [bitvec[n_, _], index_Integer] := BitGet [n, index - 1]
           (*Get the length of the bit vector.*)
           bitvec /: Length [bitvec [n_, 1_]] := 1
            bitvec /: BitLength [bitvec [n_, 1_]] := 1
           (*Perform bitwise AND of two vectors.*)
           bitvec /: BitOr[bitvec[ n1_, 11_], bitvec[ n2 _, 12_]]:=
            bitvec [BitOr[n1, n2], Max [11,12]]
           (*Perform bitwise OR of two vectors.*)
           bitvec /: BitOr [bitvec [n1_, 11_], bitvec [n2_, 12_]]:=
            bitvec[BitAnd [n1, n2], Max [11, 12] ]
           (*Return the complement (NOT) of a bit vector.*)
           bitvec /: BitNot [bitvec[ n _, 1_]] :=
            bitvec[BitAnd [BitNot [n], 2 ^ 1 - 1], 1]
           (*Create a format to print bit vectors in an abbreviated fashion.*)
           Format [bitvec[ n _, 1_]] :=
            "bitvec"["<"<> Tostring[BitGet[n,o]] <> " ••• " <>
              ToString [BitGet [n, 1 - 1]] <> ">", 1]

Here are some examples of usage.

In[229]:=  bv = makeBitVector[{1, 0, 0, 0, 1}]
Out[229]=  bitvec[<1...1>, 5]

In[230]:=  bv[[2]]
Out[230]=  0

In[231]:=  bv = setBit[bv, 2, 1]
Out[231]=  bitvec[<1...1>, 5]

In[232]:=  bv[[2]]
Out[232]=  1

In[233]:=  bv = setBit[bv, 500, 1]
Out[233]=  bitvec[ <1...1>, 500]

In[234]:=  bv2 = Rest[bv]
Out[234]=  bitvec[<1...1>, 499]

In[235]:=  bv3 = BitNot[makeBitVector[{1, 0, 0, 0, 1}]]
Out[235]=  bitvec[<0...0>, 5]

In[236]:=  bv3 [[1]]
Out[236]=  0

The simplest tree is the binary tree, and the simplest model of a binary tree in Mathematica is a list consisting of the left branch, node value, and right branch.

In[238]:=  (*MakeTree constructs either an empty
            tree or a tree with only a root element.*)
           makeTree[] := {}
           makeTree[value_] := {{}, value, {}}
           (*Functions for extracting the parts of a node*)
           getTreeValue[tree_] := tree [[2]]
           getTreeLeft[tree_] := tree[[l]]
           getTreeRight[tree_] := tree[[3]]
           (*We insert elements into a tree using < ordering relation.*)
           insertTree[{}, value _] := {{}, value, {}}
           insertTree[tree_, value_] := If[value < getTreeValue[tree],
             {insertTree [getTreeLeft[tree], value],
              getTreeValue[tree], getTreeRight[tree]},
             {getTreeLeft[tree], getTreeValue[tree],
              insertTree[getTreeRight[tree], value]}]
           (*Given the above primitives, it is easy to define
            some common algorithms.*)
           listToTree[list_List] := Fold[insertTree[#1, #2] &, makeTree[], list]
           (*A preorder traversal is also known as depth-first.*)
           preorder[tree_] := Reap[preorder2[tree]] [[2, 1]]
           preorder2[{}] := {}
           preorder2[tree_] := Module[{}, Sow[getTreeValue[tree]];
                                     preorder2[getTreeLeft[tree]];
                                 preorder2[getTreeRight[tree]]]
        postorder [tree_] := Reap [postorder2 [tree]][[2, 1]]
        postorder2[{}] := {}
        postorder2[tree_] := Module[{},
          postorder2[getTreeLeft[tree]];
          postorder2[getTreeRight[tree]];
              Sow[getTreeValue[tree]];
        (*An inorder traversal returns the values in sorted order.*)
        inorder[tree_] := Reap[inorder2[tree]][[2, 1]]
        inorder2[{}] := {}
        inorder2[tree_] := Module[{},
          inorder2[getTreeLeft[tree]];
          Sow[getTreeValue[tree]];
          inorder2[getTreeRight[tree]]]
         (*A level order traversal is also known as breadth first.*)
         level order[tree_] := Reap[levelorder2[{tree}]][[2, 1]]
         (*Breadth first is commonly implemented in terms of
          a queue that keeps track of unprocessed levels. I model
          the queue as a list.*)
         levelorder2[{}] := {} (*Stop on empty queue.*)
         levelorder2[{{}}] := {} (*Stop on queue with empty tree.*)
         levelorder2[queue_] := Module[{front = First [queue],
            queue2 = Rest[queue], (*Pop front of queue.*),
            left, right},
           Sow[getTreeValue[front]]; (*Visit node. *)
           left = getTreeLeft[front];
           right = getTreeRight[front];
           queue2 = If[Length[left] == 0, queue2, Append[queue2, left]];
           (*Append left if not empty.*)
           queue2 = If[Length[right] == 0, queue2, Append[queue2, right]];
           (*Append right if not empty.*)
           levelorder2[queue2]]

In[259]:=  nodes = RandomInteger[{1, 100}, 18]
Out[259]=  {62, 97, 36, 82, 76, 84, 58, 32, 79, 16, 89, 15, 45, 72, 90, 32, 12, 9}
Solution

A red-black tree is a popular balanced tree algorithm used as the foundation for associative data structures. To implement a red-black tree in Mathematica, you create a representation of the tree and functions for creating, reading, updating, and deleting (CRUD). This implementation will use a head rbTree containing a tree and an ordering relation. The tree is modeled as either an empty list or a quadruple consisting of a color (red or black), a left subtree, an element, and a right subtree. By default, we use the function Less as the ordering relation. Storing the ordering relation as part of the tree allows for trees of varying element content.

In[285]:=  (*Make an empty tree with default ordering.*)
           makeRBTree[] := rbTree[ {}, Less]
           (*Make an empty tree with a custom ordering.*)
           makeRBTree[ordering_] := rbTree[{}, ordering]
           (*Make a tree with given root and ordering.*)
           makeRBTree[{color_, left_, elem_, right_), ordering_] :=
            rbTree[{ color, left, elem, right}, ordering]

Before we can do much with these trees, we need a method for inserting new elements while keeping the tree well ordered and balanced. For this, we create a toplevel insert function implemented in terms of several low-level functions that maintain all the constraints necessary for a red-black tree.

In[288]:=  insertRBTree[rbTree[struct_, ordering_], elem_] :=
            makeRBTree[makeBlack[insertRBTree2[struct, elem, ordering]], ordering]

In[289]:=  (*This implementation method does ordered
            insertion and balancing of the tree representation.
             Note: empty subtrees {} are considered implicitly black.*)
           insertRBTree2[{}, elem_, _] := {red, {}, elem, {}}
           insertRBTree2[{color_, left_, elem2_, right_}, elem1_, ordering_] :=
             Which[ordering[elem1, elem2],
             balance[color, insertRBTree2[left, elem1, ordering], elem2, right],
                   ordering[elem2, elem1],
             balance[color, left, elem2, insertRBTree2[right, elem1, ordering]],
                    True, {color, left, elem2, right}]
In[291]:=  (*This is a helper that turns a node to black.*)
           makeBlack[{color_, left_, elem_, right_}] := {black, left, elem, right}
In[292]:=  (*Balancing is handled by a transformation function that
             matches all red-black constraint violations
             and transforms them into balanced versions.*)
           balance[black, {red, {red, left1_, elem1_, right1_}, elem2_, right2_},
             elem3_, right3_]:=
            {red, {black, left1, elem1, right1}, elem2, {black, right2, elem3, right3}}
           balance[black, {red, left1_, elem1_, {red, left2_, elem2_, right1_}},
             elem3_, right2_] :=
            {red, {black, left1, elem1, left2}, elem2, 
             {black, right1, elem3, right2}}
           balance[black, left1_, elem1_,
              {red, {red, left2_, elem2_, right1_}, elem3_, right2_}]:=
             {red, {black, left1, elem1, left2}, elem2,
              {black, right1, elem3, right2}}
           balance[black, left1_, elem1_,
              {red, left2_, elem2_, {red, left3_, elem3_, right1_}}] :=
             {red, {black, left1, elem1, left2}, elem2, {black, left3, elem3, right1}}
           balance[color_, left1_, elem1_, right1_] :=
             {color, left1, elem1, right1}

List-to-tree and tree-to-list conversions are very convenient operations for interfacing with the rest of Mathematica.

Solution

There are several ways to approach a problem like this. One reasonable answer is to implement associative lookup outside of Mathematica using a language like C++ and then use MathLink to access this functionality. Here we will take the approach of implementing a red-black tree directly in Mathematica.

A red-black tree implemented in C may typically be hundreds of lines of code, yet we achieve an implementation in Mathematica with less than a hundred, including comments. How is this possible? The main idea is to exploit pattern matching as much as possible. Note particularly the function balance. This function directly implements the most tricky part of a red-black-tree implementation in a traditionally procedural language by stating the balancing rules in a form that is very close to the way the algorithm requirements might specify them. Let’s take one of the versions as an example.

balance[black, {red, {red, left1_, elem1_, right1_}, elem2_, right2_}, elem3
_, right3_] :=
 {red, {black, left1, elem1, right1}, elem2, {black, right2, elem3, right3}}

The above says: "If you find a black node (elem3) with a red left child (elem2) that also has a red left child (elem1), then convert to a red node with two black children (elem1 and elem3, in that order). This is a case where the code speaks more clearly and precisely than any English translation. With a slight bit of editing, the code itself translates into a graphical view of before and after. I can‘t think of another general programming language where you can code and visualize an algorithm with so little added effort!

Discussion

A solution to associative lookup that is more in the spirit of Mathematica can be found in 3.13 Exploiting Mathematica’s Built-In Associative Lookup.

This recipe was inspired by the book Purely Functional Data Structures by Chris Okasaki (Cambridge University Press), in which Haskell is used to demonstrate that data structures can be written under the constraints of a pure functional programming language.

Wikipedia provides a good basic explanation of and references to more sources for red-black trees ( http://bit.ly/3WEqrT ).

Harness the same mechanism Mathematica uses to locate the downvalues of a symbol to create the dictionary.

Here I outline the basic idea for the solution and defer the actual implementation to the discussion. The idea is simply to exploit something that Mathematica must already do well: look up a symbol’s downvalues. It must do this well because it is central to Mathematica programming. Imagine you want to create a table of values associating some U.S. zip codes with towns. A reasonable way to proceed is as follows:

In[311]:=  zipcode[11771] = {"Oyster Bay", "Upper Brookville",
               "East Norwhich", "Cove Neck", "Centere Island"};
           zipcode[11772] = {"Patchogue", "North Patchogue", "East Patchogue"};
           (*And so on...*)
           zipcode [11779] = {"Ronkonkoma", "Lake Ronkonkoma"};

Now, when your program needs to do a lookup, it can simply call the "function" zipcode.

Solution

This is so obvious that few regular Mathematica programmers would even think twice about doing this. However, this use case is static. Most associative data structures are dynamic. This is not a problem because you can also remove downvalues.

In[315]:=  zipcode[11779] =.

Now there is no longer an association to 11779. Mathematica indicates this by returning the expression in unevaluated form.

In[316]:=  zipcode[11779]
Out[316]=  zipcode[11779]

But this is still not enough. An associated data structure should also tell you all the keys and all the values it knows. Again, Mathematica comes through.

Solution

So all the building blocks are present in the core of Mathematica to create a dynamic dictionary-like data structure. All that is needed is the creation of some code to neatly tie these pieces together into a general utility.

The first function we need is a way to construct a dictionary. In the solution, we use a symbol that makes sense for the problem at hand, but in a generic implementation what symbol is used is not significant so long as it is unique. Luckily, Mathematica has the function Unique to deliver the goods. We initialize the dictionary by creating a downvalue that maps any value to the empty list. The symbol is wrapped up in the head Dictionary and returned to the caller.

In[318]:=  makeDictionary[] :=
                Module [{name},
                     name = Unique ["dict"];
                     Evaluate [name] [k_] := {};
                     Dictionary [name]
                ]

You will also want a way to get rid of dictionaries and all their content. Remove does the trick.

In[319]:=  destroyDictionary[Dictionary[name_,_]] :=
            If[ValueQ[name[_] ], Remove[name]; True, False]

Although we said that there is no need to know the symbol used internally, there is no harm in providing a function to retrieve it. Further, our implementation will use this function so that it is easier to change the internal representation in the future.

In[320]:=  dictName[Dictionary[name_,_]] := name

The most important function, dictStore, allows the association of a value with a key. We assume, as in the solution, that more than one value may be needed for a given key, so we store values in a list and prepend new values as they are added.

In[321]:=  dictstore[dict_Dictionary, key_, value_] :=
            Module[{d = dictName[dict]}, d[key] = Prepend[d[key],value]]

The function dictReplaee is like dictStore, except it guarantees value is unique. That is, there are no duplicates of value, although there might be other values for the key.

In[322]:=  dictReplace [dict_Dictionary, key _, value] :=
            Module[{d = dictName[dict]}, d[key] = d[key]∪(value}]

In contrast, the function dictRemove ensures that there are no instances of value associated with the key (although, again, there might be other values for the key).

In[323]:=  dictRemove[dict_Dictionary, key-, value] :=
            Module[{d =dictName [dict]}, d[key] = Complement [d [key], {value}]]

If you want all values removed, then use dict Clear.

In[324]:=  dictClear[Dictionary[name_, _]] :=
            If[ValueQ[name[_]], Clear[name]; Evaluate[name][k_] := {}; True, False]

Maintaining the dictionary is all well and good, but you also need to be able to retrieve values. The function dictLookup is the easiest to implement because it gets Mathematica to do all the work by simply asking for the downvalue in the usual way.

In[325]:=  dictLookup[Dictionary[name_, _], key] := name[key]

Sometimes you might not care what the value is but rather if the key exists at all. Here I use ValueQ, which returns true if the evaluation of an expression returns something different than the expression itself (hence, indicating there is a value). In this implementation, I don‘t care that the value may be the empty list {} because dictHasKeyQ is only intended to tell the caller that the key is present.

In[326]:=  dictHasKeyQ[Dictionary[name_, _], key] := ValueQ [name [key]]

This function tells you that the key is present but has no values.

In[327]:=  dictKeyEmptyQ[Dictionary[name_,_],key]:= name[key] === {}

In some applications, you may want to know the set of all keys; dictKeys provides that. It works by using DownValues, as shown in the solution, but transforms the results to extract only the keys. Most is used to exclude the special downvalue name[k_], which was created within makeDictionary. The use of HoldPattern follows from the format that DownValues uses, as seen in the solution section. Here, Evaluate is used because DownValues has the attribute HoldAll.

Discussion

Another useful capability is to get a list of all key value pairs; dictKeyValuePairs does that.

Discussion

Before I exercise this functionality, a few general points need to be made.

You may be curious about the pattern Dictionary[name_, _] since the representation of the dictionary, per makeDictionary, is clearly just Dictionary[name]. As you probably already know (see Chapter 4 if necessary), _ matches a sequence of zero or more expressions. Using this pattern will future proof the functions against changes in the implementation. For example, I may want to enhance Dictionary to take options that control aspects of its behavior (for example, whether duplicate values are allowed for a key or whether a key can have multiple values all together). Keep this in mind when creating your own data structures.

A collection of functions like this really begs to be organized more formally as a Mathematica package. In fact, you can download such a package, with the source code, at this book’s website, http://oreilly.com/catalog/9780596520991/. I cover packages in 18.4 Packaging Your Mathematica Solutions into Libraries for Others to Use.

Here is how I might code the zip codes example from the solution if I needed the full set of create, read, update, and delete capabilities that Dictionary provides.

In[330]:=  zipcodes = makeDictionary[];
           dictStore[zipcodes, 11771,#] & /@ {"Oyster Bay",
              "Upper Brookville", "East Norwhich", "Cove Neck", "Centere Island"};
           dictStore [zipcodes, 11772,#] &/@
              {"Patchogue", "North Patchogue", "East Norwhich"};
           dictStore [zipcodes, 11779,#] &/@ {"Ronkonkoma", "Lake Ronkonkoma"};

In[334]:=  dictLookup[zipcodes, 11771]
Out[334]=  {Centere Island, Cove Neck, East Norwhich, Upper Brookville, Oyster Bay}

In[335]:=  dictLookup[zipcodes, 99999]
Out[335]=  {}

Ask if a key is present.

In[336]:=  dictHasKeyQ[zipcodes, 11779]
Out[336]=  True

Get all the zip codes stored.

In[337]:=  dictKeys[zipcodes]
Out[337]=  {11771, 11772, 11779}

In 3.12 Implementing Ordered Associative Lookup Using a Red-Black Tree quite a bit more coding is required to get a similar level of functionality. This recipe is relatively easy because it leverages one of Mathematica’s strengths. This is an important lesson when working with Mathematica (or any language). Always look for solutions that play to the language’s strengths rather than using hack solutions designed for other programming environments. To be fair, the red-black-tree implementation has features that would be more difficult to support in this recipe. Specifically, we could control the ordering of keys with red-black tree, but here keys are ordered according to Mathematica’s conventions (which are conveniently in line with the expectations one would have for a dictionary).

Graphs can also be constructed from combinations of existing graphs by using GraphUnion, GraphIntersection, GraphDifference, GraphProduct, and GraphJoin. In the examples given here, I always use two graphs, but the operations are generalized to multiple graphs.

GraphUnion always creates a disjoint graph resulting from the combination of the graphs in the union.

Discussion

GraphJoin performs a union and then links up all the vertices from the corresponding graphs.

Discussion

GraphIntersection works only on graphs with the same number of vertices and produces a graph where the input graphs have edges in common.

Discussion

GraphDifference creates a graph with all the edges that are in the first graph but not in the second.

Discussion

GraphProduct creates a graph by injecting copies of the first graph into the second at each vertex of the second and then connecting the vertices of the injected graphs.

Unlike a numerical product, this operation is not commutative, as demonstrated in Out[354] on Discussion.

Discussion

Another way to construct graphs is from alternate representations, such as adjacency matrices and adjacency lists. Out[355] on Discussion shows a graph constructed from an adjacency matrix obtained from GraphData. Normal is used to convert SparseMatrix, since Combinatorica does not recognize sparse-matrix representations.

Discussion

Combinatorica also supports directed graphs and graphs with weighted edges. Using SetEdgeWeights alone gives random real weights in the range [0,1]. SetEdgeWeights also accepts Weighting Function and WeightRange options. You can also explicitly specify the weights in a list, which will be assigned to the edges in the same order as returned by the function Edges.

Discussion

See Chapter 7 and Chapter 8 in Computational Discrete Mathematics: Combinatorics and Graph Theory with Mathematica by Sriram Pemmaraju and Steven Skiena.