Here are some notes about topics we covered in lecture 10.
For more details about exceptions, see the Essential C# textbook or the C# reference pages. Dynamic programming is covered in ch. 15 of Cormen, Introduction to Algorithms.
The throw
statement throws an exception,
which can be any object belonging to the System.Exception
class or any of its subclasses. The
exception will pass up the call stack, aborting the execution of any
methods in progress until it is caught with a try...catch
block at some point. If the
exception is not caught, the program will terminate.
The try
statement attempts to execute a block of
code. It may have a set of catch
clauses and/or a
finally
clause.
A catch
clause catches all exceptions of a certain
type. For example:
static void Main() { StreamReader reader; try { reader = new StreamReader("numbers"); } catch (FileNotFoundException e) { WriteLine("can't find input file: " + e.FileName); return; } …
The code above will catch an exception of class
FileNotFoundException
,
or of any subclass of it. When an exception is caught, the
catch
block (called an exception handler)
executes. The catch block may itself rethrow the given exception, or
even a different exception. If the catch block does not throw an
exception, execution resumes below the try
statement.
A finally
clause will always execute, even if an
exception is thrown inside the body of the try
statement. For example:
StreamReader reader = …; StreamWriter writer = …; try { while (reader.ReadLine() is string s) writer.WriteLine(transform(s)); } finally { reader.Close(); writer.Close(); }
In this close, reader
and writer
will be
closed even if an exception occurs within the try
body
(for example, within the transform
method). Note that a
finally
clause does not itself catch an exception, which
will continue to pass up the call stack.
Certain classes in the C# library implement the interface
IDisposable
, which has a single method:
void Dispose ();
This method frees any external resources associated with an object. You should call it when you are finished using an object.
Fort example, the StreamReader
and StreamWriter
classes implement IDisposable
. The Dispose() method in
these classes performs the same task as the Close() method: it closes
a file or other stream. It is especially important to call Close() or
Dispose() when writing to a file; if you do not, some output may not
be written. So to be sure that the file is closed in any case, you
can write
StreamWriter w = new StreamWriter("output"); try { ... write to w ... } finally { w.Dispose(); }
This pattern is so common that C# includes a statement called using
that can be used for the same task. using
takes a object
that implements IDisposable
and executes a block of
code, disposing the object when the block exits:
StreamWriter w = new StreamWriter("output"); using (w) { ... write to w ... }
Alternatively, you can bind a new variable inside a using
statement:
using (StreamWriter w = new StreamWriter("output")) { ... write to w ... }
In class we talked about dynamic programming problems with a 2-dimensional structure. Here are the programs we saw:
// 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]; int from, to; for (from = s.Length - 1 ; from >= 0 ; --from) { len[from, from] = 1; for (to = from + 1 ; to < s.Length ; ++to) { len[from, to] = s[from] == s[to] ? 2 + len[from + 1, to - 1] : Max(len[from, to - 1], len[from + 1, to]); } } // 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 = ""; from = 0; to = s.Length - 1; while (to >= from) { if (to == from) { a += s[from]; break; } if (s[from] == s[to]) { a = a + s[from]; b = s[to] + b; ++from; --to; } else if (len[from, to - 1] > len[from + 1, to]) --to; else ++from; } return a + b; }
// Compute the length of the longest common subsequence of s and t. static int lcs_len(string s, string t) { // len[i, j] is the length of the longest common subsequence of // s[0 .. (i - 1)] and t[0 .. (j - 1)]. int[,] len = new int[s.Length + 1, t.Length + 1]; for (int i = 1 ; i <= s.Length ; ++i) for (int j = 1 ; j <= t.Length ; ++j) len[i, j] = s[i - 1] == t[j - 1] ? 1 + len[i - 1, j - 1] : Max(len[i, j - 1], len[i - 1, j]); return len[s.Length, t.Length]; }
// 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) { // b[i, j] is true if some subset of a[0 .. (i - 1)] adds up to j. bool[,] b = new bool[a.Length + 1, s + 1]; b[0, 0] = true; // we can make 0 from the empty set for (int i = 1 ; i <= a.Length ; ++i) for (int j = 0; j <= s ; ++j) b[i, j] = b[i - 1, j] || // we can make j without a[i - 1] a[i - 1] <= j && b[i - 1, j - a[i - 1]]; // we can make j by adding a[i - 1] if (!b[a.Length, s]) return null; // sum is not possible // Now construct the integers in the set. List<int> result = new List<int>(); int k = s; for (int i = a.Length ; i > 0 ; --i) { if (!b[i - 1, k]) { result.Add(a[i - 1]); k -= a[i - 1]; } } return result.ToArray(); }