Array Processing Design Recipes

There are multiple ways to process arrays. In a language like Haskell, where recursion is the main control mechanism, there are two ways that you should be familar with.

### Linear, item by item, processing.

• Scan over the elements from lowest to highest.
• Write a pair of functions.
1. A wrapper function that computes the bounds of the array, and then calls the worker with the bounds as arguments.
2. A worker function that recurses over the the two bounds.
• The base case is when the lower bound becomes larger than the upper bound.
• A step case where the lower bound is less than or equal to the upper bound. This case consists of at least two actions
1. Perform some action based upon the lower bound
2. Make a recursive call which increments the lower bound but which leaves the upper bound unchanged.
• The wrapper function usually has 2 or three actions.
1. Compute the bounds of the array (and possibly other initialization actions).
2. Calls the workewr function
3. Performs some clean up actions

##### Example linear scan. Printing each element of an array.

```printEach :: Show a => Array a -> IO ()       -- write a contract
printEach a =                                 -- define the wrapper
do { (low,high) <- boundsArr a              -- compute the bounds
; worker (low,high) a                    -- call the worker
; return ()                              -- clean up
}

worker (low,high) a | low > high = return ()  -- base case
worker (low,high) a =                         -- step case has 2 actions
do { item <- readArr a low                  -- 1) an action based upon low
; print item
; worker (low+1,high) a                  -- 2) make a recursive call
}
```

### Index pre-computation, and scan over index list.

• The design recipe has two parts
1. Index calculation, where we compute a list of indexes.
2. Index scanning, where we scan over the list performing an action for each index.
• Each part can correspond to either a separate function, or separate actions. This is the designers choice.
• The two parts are then sequenced by using a "do" command.
• Note that index calculation can often be wriiten with a simple list creating operation, like a sequence or comprehension.
• Scanning is just a simple recursive function over the structure of lists
1. Like many such list functions it has two cases
2. When the index list is empty
3. When the index list has the shape (x:xs). In this case perform an action and make a recursive call.
##### An example of index precalculation

```revIndexes :: (Int,Int) -> [(Int,Int)]
revIndexes (low,high) = zip countup countdown          -- Part 1, index precalculation
where n = high - low + 1
half = n `div` 2
countup = [low..half]
countdown = [high, high -1 .. low]

rev5 :: Array a -> IO ()
rev5 a =
do { (low,high) <- boundsArr a
; let scan :: [(Int,Int)] -> IO ()               -- Part 2, defining scanning
scan [] = return ()                        -- base case of scan
scan ((i,j):xs) =                          -- step with 2 actions
do { swap a (i,j); scan xs }
; scan (revIndexes (low,high))                   -- calling the scan step
}
```
Back to the Daily Record.