Week 9: Notes

Our topics this week included:

delegates

A delegate is a value that represents a function or method. It is similar to to a function object in Python, or a function pointer in languages such as C.

The delegate keyword declares a new delegate type. For example:

  delegate bool IntCondition(int i);

With this declaration, an IntCondition is a type of delegate that takes an integer argument and returns a boolean. We can now declare a variable of type IntCondition, and use it to refer to a method of corresponding type:

  static bool isOdd(int i) {
    return (i % 2 == 1);
  }

  static void Main() {
    IntCondition c = isOdd;
    

We can invoke the delegate using function call syntax:

    WriteLine(c(4));    //  writes False

In the example above, the delegate c refers to a static method odd. A delegate may also refer to an instance method, in which case it actually references a particular object on which the method will be invoked. For example:

  class Interval {
    public int low, high;
    public Interval(int low, int high) { this.low = low; this.high = high; }
  
    public bool contains(int i) {
      return (low <= i && i <= high);
    }
  }

  static void Main() {
    IntCondition c = new Interval(1, 5).contains;
    IntCondition d = new Interval(3, 7).contains;
    WriteLine(c(2));  // writes True
    WriteLine(d(2));  // writes False
  }

Here is a method that counts how many elements in an array of integers satisfy an arbitrary condition:

  static int count(int[] a, IntCondition cond) {
    int n = 0;
    foreach (int i in a)
      if (cond(i))
        ++n;
    return n;
  }

We can invoke this method as follows:

  static bool isEven(int i) {
    return (i % 2 == 0);
  }
  
  int[] a = { 3, 4, 5, 6, 7 };
  WriteLine(count(a, isEven));  // writes 2

Delegates may be generic:

  delegate bool Condition<T>(T t);  // maps type T to bool

Here is the count method from above, rewritten to work on an array of any type T. Notice that the method itself must also be generic (indicated by the "<T>" after the method name).

  static int count<T>(T[] a, Condition<T> cond) {
    int n = 0;
    foreach (T val in a)
      if (cond(val))
        ++n;
    return n;
  }

lambda expressions

A lambda expression is an anonymous function that can appear inside another expression.

For example, here is a delegate type for a function from integers to integers:

delegate int IntFun(int i);

And here is a method that applies a given function to every element of an array of integers:

  static int[] map(int[] a, IntFun f) {
    int[] b = new int[a.Length];
    for (int i = 0 ; i < a.Length ; ++i)
      b[i] = f(a[i]);
    return b;
  }

We can define a named method and pass it to map:

  static int plus2(int i) {
    return i + 2;
  }

  static int[] add2(int[] a) {
    return map(a, plus2);
  }

Alternatively, we can invoke map using a lambda expression:

  static int[] add2(int[] a) {
    return map(a, i => i + 2);
  }

Here, i => i + 2 is a lambda expression. It is an anonymous function that takes an integer parameter i and returns the value i + 2.

Like a local function, a lambda expression may refer to parameters or local variables in its containing method. For example, suppose that we want to write a method that adds a given value k to each element in an array. We could write a local function and pass it to map:

  static int[] add_k(int[] a, int k) {
    int f(int i) {
      return i + k;
    }
    return map(a, f);
  }

Or we can use a lambda expression that adds k directly:

  static int[] add_k(int[] a, int k) {
    return map(a, i => i + k);
  }

events

An event is a class member that lets callers register event handlers that will receive notifications. Each event handler is a delegate. When an event is raised (i.e. fires), a notification is sent to each registered event handler. Each notification includes arguments matching the event's delegate type.

Events are useful for implementing the observer pattern, in which one or more observers may want to hear about changes to an object. A common example of this pattern is a model-view architecture, in which the view observes the model and displays the model's data. In such an architecture we want the model to be unaware of the view. Using an event, a view can register to find out when the model has changed, without giving the model specific knowledge of the view class.

Here is an array class including an event that is raised whenever any array element changes:

delegate void Notify(int index, int old, int now);

class WatchableArray {
  int[] a;
  
  public WatchableArray(int n) {
    a = new int[n];
  }
  
  public event Notify changed;
  
  public int this[int i] {
    get => a[i];
    set {
      int prev = a[i];
      a[i] = value;
      changed(i, prev, a[i]);  // fire the event to notify observers
    }
  }
}

Notice that the event declaration includes a delegate type, and that we can raise an event using method call syntax.

Use the += operator to register a handler with an event. For example, we can create an instance of the WatchableArray class and register an event handler:

  void onChange(int index, int old, int now) {
    WriteLine($"a[{index}] changed from {old} to {now}");
  }
  
  public void Main() {
    WatchableArray a = new WatchableArray(5);
    a.changed += onChange;


  }

If some method later calls

  a[3] = 4;

then the above event handler will run, and will print a message such as

  a[3] changed from 0 to 4

events with no handlers

Be warned: if you attempt to raise an event that has no registered handlers, you will get a NullPointerException. In my opinion this is a weakness in the C# event system: if would be nicer if raising such an event did nothing. However, this is how it works. So in the example above, instead of

changed(i, prev, a[i]);  // fire the event to notify observers

actually we should write

if (changed != null)
    changed(i, prev, a[i]);  // fire the event to notify observers

which will guard against this condition.

2-dimensional dynamic programming problems

Last week we began our study of dynamic programming. The dynamic programming problems we saw last week had a 1-dimensional structure. That means that each of those problems could be solved by writing a recursive function with 1 argument. The naive recursive solution was exponentially inefficient, since it solved the same subproblems over and over again. We found that we could solve these problems much more efficiently by filling in a 1-dimensional array in a certain order. This array recorded the solution to each subproblem, so that it was immediately available to use in solving other, larger, subproblems.

For example, last week we studied the rod-cutting problem. The naive recursive solution looked like this:

  // Return the best price for cutting a rod of length n, given a table
  // with the prices for pieces from lengths 1 .. n.
  static int profit(int n, int[] prices) {
      int best = 0;
      for (int i = 1 ; i <= n ; ++i)
          best = Max(best, prices[i] + profit(n - i, prices));
      return best;
  }

Notice that profit() is a function of a single argument "int n" (ignoring the constant array prices[], which could just be stored as a field outside the function). To compute the result more efficiently, we used a bottom-up approach that filled in a 1-dimensional array called best[], where best[i] held the best possible profit for a rod of size i, i.e. the value that would be returned by profit(i, prices).

This week we will study slightly more challenging dynamic programming problems that have a more general 2-dimensional structure. For these problems, a naive recursive solution will be a function with 2 arguments. Once again, this naive solution will run in exponential time. We can solve these problems more efficiently using bottom-up dynamic programming in which we fill in a 2-dimensional array or array-like data structure. We will need to be careful about the order in which we fill in the array elements. The solution to any subproblem instance will depend on the solutions to smaller instances, so we need to fill in the array in some order that guarantees that the dependent solutions will already be available when we compute any subproblem solution.

Some examples will make these ideas clearer. Let's consider the problem of finding the longest palindromic subsequence of a given string. For example, consider the string "watermelon stew". The string "wteretw" is a longest palindromic subsequence:

W A T E R M E L O N   S T E W
W   T E R   E           T   W

That's because this subsequence is a palindrome (i.e. it reads the same forwards and backwards), and there is no longer palindromic subsequence in the string. Note that the longest palindromic subsequence is not necessarily unique: "wtemetw" is another possibility.

We can solve this problem using two-dimensional dynamic programming. Suppose that the input string s has length N, with character indices from 0 to (N - 1). Let L(i, j) be the length of the longest palindromic subsequence of s[i .. j].

We must first find a recursive formulation of the problem that will allow us to compute L(i, j) recursively for any i and j. Our base case is straightforward: if i = j, then clearly L(i, j) = 1, since any 1-character string is a palindrome. In fact it will be useful to have an additional base case that is even smaller: if j < i, then s[i .. j] is empty, so L(i, j) = 0.

Now suppose that i < j, which will be the recursive case. We may consider two possibilities:

Since we have found a recursive pattern, we may now write a recursive function to compute L(i, j):

    // Compute the length of the longest palindromic subsequence of s[i .. j].
    static int len(string s, int i, int j) {
        if (j < i) return 0;
        if (i == j) return 1;

        return s[i] == s[j] ? len(s, i + 1, j - 1) + 2
                            : Max(len(s, i, j - 1), len(s, i + 1, j));
    }

It works:

 string s = "watermelon stew";
 WriteLine(lpsLength(s, 0, s.Length  1));   // writes 7

However this function will run in exponential time.

To solve this problem more efficiently, we will use bottom-up dynamic programming to fill in a 2-dimensional array len[], where len[i, j] = L(i, j). As mentioned above, we need to be careful about the order in which we fill in the array. Specifically, when we compute len[i, j] we must already know len[i + 1, j] and len[i, j – 1] and len[i + 1, j – 1]. An illustration will help here. Supposed that s = "india". Here is a table showing the values of len[i, j]:

Notice the following:

Now, we certainly cannot fill in this rows in this table from top to bottom, because then as we computed each row we would not already have the values below it. One possible order for filling in the rows is bottom to top, left to right:

When we fill in this order, as we encounter each cell (i, j) we will already know the value of its neighbors below, to the left, and diagonally below and to the left. And so we will be able to compute len[i, j].

Now, there is really no need for us to iterate over all the cells below the diagonal in the table above, since they all have value 0 and will be initialized to that value when we create our array. So instead we can fill in the table from bottom to top, proceeding rightward from the diagonal position in each row:

We now have a strategy, so let's write code to fill in the array:

    // Compute the length of the longest palindromic subsequence of s.
    static int longestPalindromic(string s) {
        // len[i, j] is the length of the longest palindromic subsequence of
        // s[i .. j].
        int[,] len = new int[s.Length, s.Length];
        int i, j;
        for (i = s.Length - 1 ; i >= 0 ; --i) {
            len[i, i] = 1;
            for (j = i + 1 ; j < s.Length ; ++j)
                len[i, j] =
                    s[i] == s[j] ? len[i + 1, j - 1] + 2
                                 : Max(len[i, j - 1], len[i + 1, j]);
          }
        return len[0, s.Length - 1];
    }

Our function will run in O(N2), where N = s.Length. This is a huge improvement over the exponential version.

Of course, we may want to know not only the length of the longest palindromic subsequence, but also the subsequence itself! So we'd like to extend our code to return that string. Here is one possible approach: after we have filled in the table above we can use the values in it to reconstruct the string we want. To do that, we start at the upper right, i.e. the value len[0, s.Length – 1]. We can reconstruct a path through the table that explains how that value was derived, and that path will reveal the string itself. At any cell (i, j), if s[i] == s[j] then we know that s[i] and s[j] are included in the string we want, and we can record those characters and proceed to (i + 1, j – 1). Otherwise, we proceed either to (i, j – 1) or to (i + 1, j), choosing the cell with the larger value. Here is an extended version of the function above that can reconstruct the string in this way:

  // Compute the longest palindromic subsequence of s.
  static string longestPalindromic(string s) {
      // len[i, j] is the length of the longest palindromic subsequence of
      // s[i .. j].
      int[,] len = new int[s.Length, s.Length];
    
      … same code as above for filling in the table … 
    
      // Now len[0, s.Length - 1] is the length of the longest palindromic
      // subsequence.  We now want the subsequence itself.  We need to build
      // the sequence from the outside inward, so we use two strings a and b
      // and will return (a + b).
    
      string a = "", b = "";
      i = 0;
      j = s.Length - 1;
      while (j >= i) {
          if (j == i) {
              a += s[i];
              break;
          }
          if (s[i] == s[j]) {
              a = a + s[i];
              b = s[j] + b;
              ++i;
              --j;
          } else if (len[i, j - 1] > len[i + 1, j])
              --j;
            else ++i;
      }
    
    return a + b;
  }

Study the function to understand how it works.

This code for constructing the longest palindromic subsequence may seem like a chore, and so you may be wondering if there is an easier way. Actually there is. When we build the table, instead of storing the length of the longest palindromic subsequence of s[i .. j] in each table cell, we can store the longest palindromic subsequence itself:

    // Compute the longest palindromic subsequence of s.
    static string longestPalindromic(string s) {
        // p[i, j] is the longest palindromic subsequence of s[i .. j].
        string[,] p = new string[s.Length, s.Length];
        int i, j;
        
        for (i = s.Length - 1 ; i >= 0 ; --i) {
            p[i, i] = s[i].ToString();
            for (j = i + 1 ; j < s.Length ; ++j) 
                if (s[i] == s[j])
                    p[i, j] = s[i] + p[i + 1, j - 1] + s[j];
                else {
                    string t = p[i, j - 1], u = p[i + 1, j];
                    p[i, j] = t.Length > u.Length ? t : u;
                }
        }
    
        return p[0, s.Length - 1];
    }

That was easier! This seems like a nicer solution for this problem.

For some other dynamic programming problems, however, it may be easier or more efficient to store intermediate values in the table and the reconstruct the final solution at the end. (Actually we did that for a couple of one-dimensional dynamic programming problems last week.)

The subset sum problem

Let's look at another classic problem that we can solve using two-dimensional dynamic programming: the subset sum problem. Given a set of positive integers and an integer k, we wish to determine whether any subset of the given set has sum k.

As usual, we'd first like to come up with a recursive formulation of the problem. As a clue to how to do that, recall how a couple of weeks ago we wrote a function that generated all subsets of a given set. Here is the general approach we took. Given a set S, we can take any element x from the set, and let S' be the remaining elements in S. If we recursively generate all subsets of S', then each subset is itself a subset of S. In addition, we can add x to any of those subsets to form a subset of S.

So now suppose that we are given a set of positive integers S and an integer k, and we'd like to know whether any subset of S has the sum k. Take any integer x from S, and let S' be the remaining integers in S. If any subset of S' has sum k, then that is a subset of S which has sum k. In addition, if any subset of S' has sum (k – x), then we can add x to that subset to obtain a subset of S which has sum k.

We can use this idea to solve the problem recursively:

// Return true if any subset of a[0 .. (i - 1)] has sum k.
static bool hasSum(int[] a, int i, int k) {
    if (k == 0)
        return true;   // the empty set is a subset, and has sum k
    if (i == 0)
        return false;  // set is empty, cannot have non-zero sum
    return hasSum(a, i - 1, k)             // we can make k without a[i - 1]
        || a[i - 1] <= k &&
           hasSum(a, i - 1, k - a[i - 1]); // we can make k by adding a[i - 1]
}
    
static bool hasSum(int[] a, int k) => hasSum(a, a.Length, k);

As usual, this native recursive solution may take exponential time to run.

The function hasSum above takes two parameters i and k (in addition to the constant array a). That is a sign that we can solve this problem using two-dimensional dynamic programming. We will need a two-dimensional array that holds the boolean value hasSum(a, i, k) for every possible value of i and k. In our bottom-up implementation, we will also call this array hasSum. Specifically, hasSum[i, k] will be true if any subset of a[0 .. (i – 1)] has sum k, just like in the recursive function above.

Once again we must consider the order in which we will fill the array elements. When we compute hasSum[i, k], we may need to know the value of hasSum[i – 1, j] for any value j in the range 0 <= j <= k. That shows that we should fill the array in increasing order of i. It does not matter whether we fill each array column in increasing or decreasing order of k, since the computation of hasSum[i, k] does not depend on any other values in the same column.

After we have filled in the array, if it turns out that there was some subset with sum k, we would like to generate that subset. As in other dynamic programming problems, we can use an iterative loop to reconstruct the solution from the array. At the beginning of each loop iteration, we have values i and k such that hasSum[i, k] is true, so we know that some subset of a[0 .. (i - 1)] has sum k.

Combining these ideas, here is our bottom-up solution:

// Does any subset of the integers in a have sum s?  If so, return the subset;
// otherwise return null.
static int[] subsetSum(int[] a, int s) {
    // hasSum[i, k] is true if some subset of a[0 .. (i - 1)] adds up to k.
    bool[,] hasSum = new bool[a.Length + 1, s + 1];
    hasSum[0, 0] = true;   // we can make 0 from the empty set
    
    for (int i = 1 ; i <= a.Length ; ++i)
        for (int k = 0; k <= s ; ++k)
            hasSum[i, k] = hasSum[i - 1, k]    // we can make k without a[i - 1]
                        || a[i - 1] <= k &&
                           hasSum[i - 1, k - a[i - 1]];  // we can make k by adding a[i - 1]
    
    if (!hasSum[a.Length, s])
        return null;  // sum is not possible
      
    // Now construct the integers in the set.
    List<int> result = new List<int>();
    for (int i = a.Length ; i > 0 ; --i) {
        if (!hasSum[i - 1, s]) {
            result.Add(a[i - 1]); 
            s -= a[i - 1];
        }
    }
    
    return result.ToArray();
}

Once again, you may feel that having to reconstruct the solution at the end is a bother. Is there an easier way? Well, just as in the previous exercise we could store the solutions themselves in the array we are filling in. For example, instead of a two-dimensional array of booleans, we could store a two-dimensional array of List<int>:

List<int>[,] sumSet = new List<int>[a.Length + 1, s + 1];

And then for any i and k, if there is a subset of a[0 .. i - 1] whose sum is k, then sumSet[i, k] could hold a list of integers containing that subset, and could otherwise be null.

However this solution would be relatively inefficient. If we use lists to represent sets, then every time we want to construct a list that contains all the elements in a previous set plus a new integer, we must make a copy of the previous list. If the problem instance was large, all of these list copies could take a significant amount of time and memory.

If, however, we represent sets using linked lists rather than List objects (which are really arrays), then no copying would be necessary, since we can prepend an element to a linked list (while leaving the existing list intact) in O(1). So that would be a reasonable solution, and you may want to try to code that up as an exercise. Of course, even that solution would be less efficient than our solution above, since it is hard to beat an array of booleans if you are trying to save space or time. :) So is it worth using linked lists to avoid the extra loop at the end of the method above? You can make your own judgment about that. :)