A while back Peter Norvig posted a wonderful pair of articles about regex golf. The idea behind regex golf is to come up with the shortest possible regular expression that matches one given list of strings, but not the other.
In the first article, Norvig runs a basic algorithm to recreate and improve the results from the comic, and in the second he beefs it up with some improved search heuristics. My favorite part about this topic is that regex golf can be phrased in terms of a problem called set cover. I noticed this when reading the comic, and was delighted to see Norvig use that as the basis of his algorithm.
The set cover problem shows up in other places, too. If you have a database of items labeled by users, and you want to find the smallest set of labels to display that covers every item in the database, you’re doing set cover. I hear there are applications in biochemistry and biology but haven’t seen them myself.
If you know what a set is (just think of the “set” or “hash set” type from your favorite programming language), then set cover has a simple definition.
Definition (The Set Cover Problem): You are given a finite set called a “universe” and sets each of which is a subset of . You choose some of the to ensure that every is in one of your chosen sets, and you want to minimize the number of you picked.
It’s called a “cover” because the sets you pick “cover” every element of . Let’s do a simple. Let and
Then the smallest possible number of sets you can pick is 2, and you can achieve this by picking both or both . The connection to regex golf is that you pick to be the set of strings you want to match, and you pick a set of regexes that match some of the strings in but none of the strings you want to avoid matching (I’ll call them ). If is such a regex, then you can form the set of strings that matches. Then if you find a small set cover with the strings , then you can “or” them together to get a single regex that matches all of but none of .
Set cover is what’s called NP-hard, and one implication is that we shouldn’t hope to find an efficient algorithm that will always give you the shortest regex for every regex golf problem. But despite this, there are approximation algorithms for set cover. What I mean by this is that there is a regex-golf algorithm that outputs a subset of the regexes matching all of , and the number of regexes it outputs is such-and-such close to the minimum possible number. We’ll make “such-and-such” more formal later in the post.
What made me sad was that Norvig didn’t go any deeper than saying, “We can try to approximate set cover, and the greedy algorithm is pretty good.” It’s true, but the ideas are richer than that! Set cover is a simple example to showcase interesting techniques from theoretical computer science. And perhaps ironically, in Norvig’s second post a header promised the article would discuss the theory of set cover, but I didn’t see any of what I think of as theory. Instead he partially analyzes the structure of the regex golf instances he cares about. This is useful, but not really theoretical in any way unless he can say something universal about those instances.
I don’t mean to bash Norvig. His articles were great! And in-depth theory was way beyond scope. So this post is just my opportunity to fill in some theory gaps. We’ll do three things:
- Show formally that set cover is NP-hard.
- Prove the approximation guarantee of the greedy algorithm.
- Show another (very different) approximation algorithm based on linear programming.
Along the way I’ll argue that by knowing (or at least seeing) the details of these proofs, one can get a better sense of what features to look for in the set cover instance you’re trying to solve. We’ll also see how set cover depicts the broader themes of theoretical computer science.
The first thing we should do is show that set cover is NP-hard. Intuitively what this means is that we can take some hard problem and encode instances of inside set cover problems. This idea is called a reduction, because solving problem will “reduce” to solving set cover, and the method we use to encode instance of as set cover problems will have a small amount of overhead. This is one way to say that set cover is “at least as hard as” .
The hard problem we’ll reduce to set cover is called 3-satisfiability (3-SAT). In 3-SAT, the input is a formula whose variables are either true or false, and the formula is expressed as an OR of a bunch of clauses, each of which is an AND of three variables (or their negations). This is called 3-CNF form. A simple example:
The goal of the algorithm is to decide whether there is an assignment to the variables which makes the formula true. 3-SAT is one of the most fundamental problems we believe to be hard and, roughly speaking, by reducing it to set cover we include set cover in a class called NP-complete, and if any one of these problems can be solved efficiently, then they all can (this is the famous P versus NP problem, and an efficient algorithm would imply P equals NP).
So a reduction would consist of the following: you give me a formula in 3-CNF form, and I have to produce (in a way that depends on !) a universe and a choice of subsets in such a way that
has a true assignment of variables if and only if the corresponding set cover problem has a cover using sets.
In other words, I’m going to design a function from 3-SAT instances to set cover instances, such that is satisfiable if and only if has a set cover with sets.
Why do I say it only for sets? Well, if you can always answer this question then I claim you can find the minimum size of a set cover needed by doing a binary search for the smallest value of . So finding the minimum size of a set cover reduces to the problem of telling if theres a set cover of size .
Now let’s do the reduction from 3-SAT to set cover.
If you give me where each is a clause and the variables are denoted , then I will choose as my universe to be the set of all the clauses and indices of the variables (these are all just formal symbols). i.e.
The first part of will ensure I make all the clauses true, and the last part will ensure I don’t pick a variable to be both true and false at the same time.
To show how this works I have to pick my subsets. For each variable , I’ll make two sets, one called and one called . They will both contain in addition to the clauses which they make true when the corresponding literal is true (by literal I just mean the variable or its negation). For example, if uses the literal , then will contain but will not. Finally, I’ll set , the number of variables.
Now to prove this reduction works I have to prove two things: if my starting formula has a satisfying assignment I have to show the set cover problem has a cover of size . Indeed, take the sets for all literals that are set to true in a satisfying assignment. There can be at most true literals since half are true and half are false, so there will be at most sets, and these sets clearly cover all of because every literal has to be satisfied by some literal or else the formula isn’t true.
The reverse direction is similar: if I have a set cover of size , I need to use it to come up with a satisfying truth assignment for the original formula. But indeed, the sets that get chosen can’t include both a and its negation set , because there are of the elements , and each is only in the two . Just by counting if I cover all the indices , I already account for sets! And finally, since I have covered all the clauses, the literals corresponding to the sets I chose give exactly a satisfying assignment.
Whew! So set cover is NP-hard because I encoded this logic problem 3-SAT within its rules. If we think 3-SAT is hard (and we do) then set cover must also be hard. So if we can’t hope to solve it exactly we should try to approximate the best solution.
The greedy approach
The method that Norvig uses in attacking the meta-regex golf problem is the greedy algorithm. The greedy algorithm is exactly what you’d expect: you maintain a list of the subsets you’ve picked so far, and at each step you pick the set that maximizes the number of new elements of that aren’t already covered by the sets in . In python pseudocode:
def greedySetCover(universe, sets): chosenSets = set() leftToCover = universe.copy() unchosenSets = sets covered = lambda s: leftToCover & s while universe != 0: if len(chosenSets) == len(sets): raise Exception("No set cover possible") nextSet = max(unchosenSets, key=lambda s: len(covered(s))) unchosenSets.remove(nextSet) chosenSets.add(nextSet) leftToCover -= nextSet return chosenSets
This is what theory has to say about the greedy algorithm:
Theorem: If it is possible to cover by the sets in , then the greedy algorithm always produces a cover that at worst has size , where is the size of the smallest cover. Moreover, this is asymptotically the best any algorithm can do.
One simple fact we need from calculus is that the following sum is asymptotically the same as :
Proof. [adapted from Wan] Let’s say the greedy algorithm picks sets in that order. We’ll set up a little value system for the elements of . Specifically, the value of each is 1, and in step we evenly distribute this unit value across all newly covered elements of . So for each covered element gets value , and if covers four new elements, each gets a value of 1/4. One can think of this “value” as a price, or energy, or unit mass, or whatever. It’s just an accounting system (albeit a clever one) we use to make some inequalities clear later.
In general call the value of element the value assigned to at the step where it’s first covered. In particular, the number of sets chosen by the greedy algorithm is just . We’re just bunching back together the unit value we distributed for each step of the algorithm.
Now we want to compare the sets chosen by greedy to the optimal choice. Call a smallest set cover . Let’s stare at the following inequality.
It’s true because each counts for a at most once in the left hand side, and in the right hand side the sets in must hit each at least once but may hit some more than once. Also remember the left hand side is equal to .
Now we want to show that the inner sum on the right hand side, , is at most . This will in fact prove the entire theorem: because each set has size at most , the inequality above will turn into
And so , which is the statement of the theorem.
So we want to show that . For each define to be the number of elements in not covered in . Notice that is the number of elements of that are covered for the first time in step . If we call the smallest integer for which , we can count up the differences up to step , we get
The rightmost term is just the cost assigned to the relevant elements at step . Moreover, because covers more new elements than (by definition of the greedy algorithm), the fraction above is at most . The end is near. For brevity I’ll drop the from .
And that proves the claim.
I have three postscripts to this proof:
- This is basically the exact worst-case approximation that the greedy algorithm achieves. In fact, Petr Slavik proved in 1996 that the greedy gives you a set of size exactly in the worst case.
- This is also the best approximation that any set cover algorithm can achieve, provided that P is not NP. This result was basically known in 1994, but it wasn’t until 2013 and the use of some very sophisticated tools that the best possible bound was found with the smallest assumptions.
- In the proof we used that to bound things, but if we knew that our sets (i.e. subsets matched by a regex) had sizes bounded by, say, , the same proof would show that the approximation factor is instead of . However, in order for that to be useful you need to be a constant, or at least to grow more slowly than any polynomial in , since e.g. . In fact, taking a second look at Norvig’s meta regex golf problem, some of his instances had this property! Which means the greedy algorithm gives a much better approximation ratio for certain meta regex golf problems than it does for the worst case general problem. This is one instance where knowing the proof of a theorem helps us understand how to specialize it to our interests.
The linear programming approach
So we just said that you can’t possibly do better than the greedy algorithm for approximating set cover. There must be nothing left to say, job well done, right? Wrong! Our second analysis, based on linear programming, shows that instances with special features can have better approximation results.
In particular, if we’re guaranteed that each element occurs in at most of the sets , then the linear programming approach will give a -approximation, i.e. a cover whose size is at worst larger than OPT by a multiplicative factor of . In the case that is constant, we can beat our earlier greedy algorithm.
The technique is now a classic one in optimization, called LP-relaxation (LP stands for linear programming). The idea is simple. Most optimization problems can be written as integer linear programs, that is there you have variables and you want to maximize (or minimize) a linear function of the subject to some linear constraints. The thing you’re trying to optimize is called the objective. While in general solving integer linear programs is NP-hard, we can relax the “integer” requirement to , or something similar. The resulting linear program, called the relaxed program, can be solved efficiently using the simplex algorithm or another more complicated method.
The output of solving the relaxed program is an assignment of real numbers for the that optimizes the objective function. A key fact is that the solution to the relaxed linear program will be at least as good as the solution to the original integer program, because the optimal solution to the integer program is a valid candidate for the optimal solution to the linear program. Then the idea is that if we use some clever scheme to round the to integers, we can measure how much this degrades the objective and prove that it doesn’t degrade too much when compared to the optimum of the relaxed program, which means it doesn’t degrade too much when compared to the optimum of the integer program as well.
If this sounds wishy washy and vague don’t worry, we’re about to make it super concrete for set cover.
We’ll make a binary variable for each set in the input, and if and only if we include it in our proposed cover. Then the objective function we want to minimize is . If we call our elements , then we need to write down a linear constraint that says each element is hit by at least one set in the proposed cover. These constraints have to depend on the sets , but that’s not a problem. One good constraint for element is
In words, the only way that an will not be covered is if all the sets containing it have their . And we need one of these constraints for each . Putting it together, the integer linear program is
Once we understand this formulation of set cover, the relaxation is trivial. We just replace the last constraint with inequalities.
For a given candidate assignment to the , call the objective value (in this case ). Now we can be more concrete about the guarantees of this relaxation method. Let be the optimal value of the integer program and a corresponding assignment to achieving the optimum. Likewise let be the optimal things for the linear relaxation. We will prove:
Theorem: There is a deterministic algorithm that rounds to integer values so that the objective value , where is the maximum number of sets that any element occurs in. So this gives a -approximation of set cover.
Proof. Let be as described in the theorem, and call to make the indexing notation easier. The rounding algorithm is to set if and zero otherwise.
To prove the theorem we need to show two things hold about this new candidate solution :
- The choice of all for which covers every element.
- The number of sets chosen (i.e. ) is at most times more than .
Since , so if we can prove number 2 we get , which is the theorem.
So let’s prove 1. Fix any and we’ll show that element is covered by some set in the rounded solution. Call the number of times element occurs in the input sets. By definition , so . Recall was the optimal solution to the relaxed linear program, and so it must be the case that the linear constraint for each is satisfied: . We know that there are terms and they sums to at least 1, so not all terms can be smaller than (otherwise they’d sum to something less than 1). In other words, some variable in the sum is at least , and so is set to 1 in the rounded solution, corresponding to a set that contains . This finishes the proof of 1.
Now let’s prove 2. For each , we know that for each , the corresponding variable . In particular . Now we can simply bound the sum.
The second inequality is true because some of the are zero, but we can ignore them when we upper bound and just include all the . This proves part 2 and the theorem.
I’ve got some more postscripts to this proof:
- The proof works equally well when the sets are weighted, i.e. your cost for picking a set is not 1 for every set but depends on some arbitrarily given constants .
- We gave a deterministic algorithm rounding to , but one can get the same result (with high probability) using a randomized algorithm. The idea is to flip a coin with bias roughly times and set if and only if the coin lands heads at least once. The guarantee is no better than what we proved, but for some other problems randomness can help you get approximations where we don’t know of any deterministic algorithms to get the same guarantees. I can’t think of any off the top of my head, but I’m pretty sure they’re out there.
- For step 1 we showed that at least one term in the inequality for would be rounded up to 1, and this guaranteed we covered all the elements. A natural question is: why not also round up at most one term of each of these inequalities? It might be that in the worst case you don’t get a better guarantee, but it would be a quick extra heuristic you could use to post-process a rounded solution.
- Solving linear programs is slow. There are faster methods based on so-called “primal-dual” methods that use information about the dual of the linear program to construct a solution to the problem. Goemans and Williamson have a nice self-contained chapter on their website about this with a ton of applications.
Williamson and Shmoys have a large textbook called The Design of Approximation Algorithms. One problem is that this field is like a big heap of unrelated techniques, so it’s not like the book will build up some neat theoretical foundation that works for every problem. Rather, it’s messy and there are lots of details, but there are definitely diamonds in the rough, such as the problem of (and algorithms for) coloring 3-colorable graphs with “approximately 3” colors, and the infamous unique games conjecture.
I wrote a post a while back giving conditions which, if a problem satisfies those conditions, the greedy algorithm will give a constant-factor approximation. This is much better than the worst case -approximation we saw in this post. Moreover, I also wrote a post about matroids, which is a characterization of problems where the greedy algorithm is actually optimal.
Set cover is one of the main tools that IBM’s AntiVirus software uses to detect viruses. Similarly to the regex golf problem, they find a set of strings that occurs source code in some viruses but not (usually) in good programs. Then they look for a small set of strings that covers all the viruses, and their virus scan just has to search binaries for those strings. Hopefully the size of your set cover is really small compared to the number of viruses you want to protect against. I can’t find a reference that details this, but that is understandable because it is proprietary software.
Until next time!