Skip to content

Commit c416ac0

Browse files
author
Chris Pilcher
authored
Merge pull request kodecocodes#146 from mrael2/master
Set Cover (Unweighted)
2 parents 9f9b7fb + 28415fb commit c416ac0

File tree

7 files changed

+190
-0
lines changed

7 files changed

+190
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Set Cover (Unweighted)
2+
3+
If you have a group of sets, this algorithm finds a subset of those sets within that group whose union will cover an initial set that you're trying to match. The initial set is also known as the universe.
4+
5+
For example, suppose you have a universe of `{1, 5, 7}` and you want to find the sets which cover the universe within the following group of sets:
6+
7+
> {8, 4, 2}
8+
> {3, 1}
9+
> {7, 6, 5, 4}
10+
> {2}
11+
> {1, 2, 3}
12+
13+
You can see that the sets `{3, 1} {7, 6, 5, 4}` when unioned together will cover the universe of `{1, 5, 7}`. Yes, there may be additional elements in the sets returned by the algorithm, but every element in the universe is represented in the cover itself.
14+
15+
There may be cases where no cover exists. For example, if your universe is `{7, 9}`, there is no combination of sets within the group above that will yield a cover.
16+
17+
## The algorithm
18+
19+
The Greedy Set Cover algorithm (unweighted) is provided here. It's known as greedy because it uses the largest intersecting set from the group of sets first before examining other sets in the group. This is part of the reason why the cover may have additional elements which are not part of the universe.
20+
21+
The function (named `cover`) is provided as an extension of the Swift type `Set`. The function takes a single parameter, which is an array of sets. This array represents the group, and the set itself represents the universe.
22+
23+
One of the first things done in `cover` is to make a copy of the universe in `remainingSet`. Then, the algorithm enters a `while` loop in which a call to `largestIntersectingSet` is made. The value returned from `largestIntersectingSet` is the set which has the most elements in common with the remaining universe identified by `remainingSet`. If all sets have nothing in common, `largestIntersectingSet` returns `nil`.
24+
25+
If the result from `largestIntersectingSet` is not nil, that result is subtracted from `remainingSet` (reducing its size), and the loop continues until `remainingSet` has zero length (meaning a cover has been found) or until `largestIntersectingSet` returns `nil`.
26+
27+
If there is no cover within the group of sets, `cover` returns `nil`.
28+
29+
## See also
30+
31+
[Set cover problem on Wikipedia](https://en.wikipedia.org/wiki/Set_cover_problem)
32+
33+
*Written for Swift Algorithm Club by [Michael C. Rael](https://github.com/mrael2)*
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
let universe1 = Set(1...7)
2+
let array1 = randomArrayOfSets(covering: universe1)
3+
let cover1 = universe1.cover(within: array1)
4+
5+
let universe2 = Set(1...10)
6+
let array2: Array<Set<Int>> = [[1,2,3,4,5,6,7], [8,9]]
7+
let cover2 = universe2.cover(within: array2)
8+
9+
let universe3 = Set(["tall", "heavy"])
10+
let array3: Array<Set<String>> = [["tall", "light"], ["short", "heavy"], ["tall", "heavy", "young"]]
11+
let cover3 = universe3.cover(within: array3)
12+
13+
let universe4 = Set(["tall", "heavy", "green"])
14+
let cover4 = universe4.cover(within: array3)
15+
16+
let universe5: Set<Int> = [16, 32, 64]
17+
let array5: Array<Set<Int>> = [[16,17,18], [16,32,128], [1,2,3], [32,64,128]]
18+
let cover5 = universe5.cover(within: array5)
19+
20+
let universe6: Set<Int> = [24, 89, 132, 90, 22]
21+
let array6 = randomArrayOfSets(covering: universe6)
22+
let cover6 = universe6.cover(within: array6)
23+
24+
let universe7: Set<String> = ["fast", "cheap", "good"]
25+
let array7 = randomArrayOfSets(covering: universe7, minArraySizeFactor: 20.0, maxSetSizeFactor: 0.7)
26+
let cover7 = universe7.cover(within: array7)
27+
28+
let emptySet = Set<Int>()
29+
let coverTest1 = emptySet.cover(within: array1)
30+
let coverTest2 = universe1.cover(within: Array<Set<Int>>())
31+
let coverTest3 = emptySet.cover(within: Array<Set<Int>>())
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Foundation
2+
3+
public func randomArrayOfSets<T>(covering universe: Set<T>,
4+
minArraySizeFactor: Double = 0.8,
5+
maxSetSizeFactor: Double = 0.6) -> Array<Set<T>> {
6+
var result = [Set<T>]()
7+
var ongoingUnion = Set<T>()
8+
9+
let minArraySize = Int(Double(universe.count) * minArraySizeFactor)
10+
var maxSetSize = Int(Double(universe.count) * maxSetSizeFactor)
11+
if maxSetSize > universe.count {
12+
maxSetSize = universe.count
13+
}
14+
15+
while true {
16+
var generatedSet = Set<T>()
17+
let targetSetSize = Int(arc4random_uniform(UInt32(maxSetSize)) + 1)
18+
19+
while true {
20+
let randomUniverseIndex = Int(arc4random_uniform(UInt32(universe.count)))
21+
for (setIndex, value) in universe.enumerate() {
22+
if setIndex == randomUniverseIndex {
23+
generatedSet.insert(value)
24+
break
25+
}
26+
}
27+
28+
if generatedSet.count == targetSetSize {
29+
result.append(generatedSet)
30+
ongoingUnion = ongoingUnion.union(generatedSet)
31+
break
32+
}
33+
}
34+
35+
if result.count >= minArraySize {
36+
if ongoingUnion == universe {
37+
break
38+
}
39+
}
40+
}
41+
42+
return result
43+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
public extension Set {
2+
func cover(within array: Array<Set<Element>>) -> Array<Set<Element>>? {
3+
var result: [Set<Element>]? = [Set<Element>]()
4+
var remainingSet = self
5+
6+
func largestIntersectingSet() -> Set<Element>? {
7+
var largestIntersectionLength = 0
8+
var largestSet: Set<Element>?
9+
10+
for set in array {
11+
let intersectionLength = remainingSet.intersect(set).count
12+
if intersectionLength > largestIntersectionLength {
13+
largestIntersectionLength = intersectionLength
14+
largestSet = set
15+
}
16+
}
17+
18+
return largestSet
19+
}
20+
21+
while remainingSet.count != 0 {
22+
guard let largestSet = largestIntersectingSet() else { break }
23+
result!.append(largestSet)
24+
remainingSet = remainingSet.subtract(largestSet)
25+
}
26+
27+
if remainingSet.count != 0 || self.count == 0 {
28+
result = nil
29+
}
30+
31+
return result
32+
}
33+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='osx'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

Set Cover (Unweighted)/SetCover.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
extension Set {
2+
func cover(within array: Array<Set<Element>>) -> Array<Set<Element>>? {
3+
var result: [Set<Element>]? = [Set<Element>]()
4+
var remainingSet = self
5+
6+
func largestIntersectingSet() -> Set<Element>? {
7+
var largestIntersectionLength = 0
8+
var largestSet: Set<Element>?
9+
10+
for set in array {
11+
let intersectionLength = remainingSet.intersect(set).count
12+
if intersectionLength > largestIntersectionLength {
13+
largestIntersectionLength = intersectionLength
14+
largestSet = set
15+
}
16+
}
17+
18+
return largestSet
19+
}
20+
21+
while remainingSet.count != 0 {
22+
guard let largestSet = largestIntersectingSet() else { break }
23+
result!.append(largestSet)
24+
remainingSet = remainingSet.subtract(largestSet)
25+
}
26+
27+
if remainingSet.count != 0 || self.count == 0 {
28+
result = nil
29+
}
30+
31+
return result
32+
}
33+
}

swift-algorithm-club.xcworkspace/contents.xcworkspacedata

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)