Maximum Subarray in Haskell

2018-12-29
haskelldynamic programming

Dynamic programming is an algorithmic technique that trades space for improved runtime performance and takes advantage of overlapping subproblems. Memoization is a common strategy within Dynamic programming; store the results of expensive computations and look them up when the same input occurs again. Most literature on algorithms provide an imperative programming solution instead of a declarative one. I want to work through some dynamic programming problems with Haskell.

The maximum subarray problem is an easy place to start. Given a list of numbers, what is the subarray (contiguous) that sums to the largest value. If the array only includes positive values, then it is the entire subarray. If it includes negative values, then the answer is most likely a subarray. Kadane’s algorithm provides a solution in O(n) time. Here is the imperative form:

max_so_far = 0
max_ending_here = 0

For each element in the array a:
    max_ending_here = max_ending_here + a[i]
    if (max_ending_here < 0)
        max_ending_here = 0
    if (max_so_far < max_ending_here)
        max_so_far = max_ending_here

return max_so_far

The strategy is to store the max result of any subarray in max_so_far and when the loop ends, it contains the correct answer. max_ending_here contains the result from some index up to the current index. The beginning index of the subarray is reset if max_ending_here is less than zero, and max_so_far is updated only when it is less than max_ending_here.

The translation to Haskell is pretty simple. Instead of two mutable values, we create an internal function that takes the updated values of maxSoFar and maxEndingHere and call it recursively over the values of the list and when the list is empty, we return the maxSoFar value.

I decided to improve the function and return the start and end indexes of the max subarray.

You can see that the amount of values we need to pass to the function is increasing. This makes it harder to read and keep track of the values. In the future, for more complex algorithms we might consider some monadic tools like Reader, Writer and State to reduce the complexity. We will test maxSubarray below and take a look at some of the results.