Solve InterviewCake's graph-coloring problem

Write a function that colors the nodes of a graph such that no two neighbors
share a color.
This commit is contained in:
William Carroll 2020-03-19 12:31:24 +00:00
parent 1d45f14615
commit 380a6a352c
3 changed files with 240 additions and 1 deletions

View file

@ -0,0 +1,232 @@
type Color = string;
interface GraphNode {
label: string;
neighbors: Set<GraphNode>;
color: string;
}
class GraphNode {
constructor(label: string) {
this.label = label;
this.neighbors = new Set();
this.color = null;
}
}
interface Queue<A> {
xs: Array<A>;
}
class Queue<A> {
constructor() {
this.xs = [];
}
isEmpty(): boolean {
return this.xs.length === 0;
}
enqueue(x: A): void {
this.xs.push(x);
}
dequeue(): A {
return this.xs.shift();
}
}
type Graph = Array<GraphNode>;
// Return a set of all of the colors from the neighbor nodes of `node`.
function neighborColors(node: GraphNode): Set<Color> {
const result: Set<Color> = new Set();
for (const x of node.neighbors) {
if (typeof x.color === 'string') {
result.add(x.color);
}
}
return result;
}
// Returns the set difference between sets `xs`, and `ys`.
function setDifference<A>(xs: Set<A>, ys: Set<A>): Set<A> {
const result: Set<A> = new Set();
for (const x of xs) {
if (!ys.has(x)) {
result.add(x);
}
}
return result;
}
// Returns an element from the set, `xs`.
// Throwns an error if `xs` is an empty set.
function choose<A>(xs: Set<A>): A {
if (xs.size === 0) {
throw new Error('Cannot choose an element from an empty set.');
} else {
return xs.values().next().value;
}
}
// Returns true if `node` is present in `node.neighbors`.
function isCyclic(node: GraphNode): boolean {
for (const x of node.neighbors) {
if (x === node) {
return true;
}
}
}
function colorGraph(graph: Graph, colors: Array<Color>): void {
const allColors = new Set(colors);
for (const node of graph) {
if (isCyclic(node)) {
throw new Error('InterviewCake would like me to invalidate this');
}
if (typeof node.color !== 'string') {
node.color = choose(setDifference(allColors, neighborColors(node)));
}
}
}
// Tests
const colors = ['red', 'green', 'blue', 'orange', 'yellow', 'white'];
let graph = [];
{
const nodeA = new GraphNode('A');
const nodeB = new GraphNode('B');
const nodeC = new GraphNode('C');
const nodeD = new GraphNode('D');
nodeA.neighbors.add(nodeB);
nodeB.neighbors.add(nodeA);
nodeB.neighbors.add(nodeC);
nodeC.neighbors.add(nodeB);
nodeC.neighbors.add(nodeD);
nodeD.neighbors.add(nodeC);
graph = [nodeA, nodeB, nodeC, nodeD];
}
colorGraph(graph, colors);
assertEqual(validateGraphColoring(graph), true, 'line graph');
{
const nodeA = new GraphNode('A');
const nodeB = new GraphNode('B');
const nodeC = new GraphNode('C');
const nodeD = new GraphNode('D');
nodeA.neighbors.add(nodeB);
nodeB.neighbors.add(nodeA);
nodeC.neighbors.add(nodeD);
nodeD.neighbors.add(nodeC);
graph = [nodeA, nodeB, nodeC, nodeD];
}
colorGraph(graph, colors);
assertEqual(validateGraphColoring(graph), true, 'separate graph');
{
const nodeA = new GraphNode('A');
const nodeB = new GraphNode('B');
const nodeC = new GraphNode('C');
nodeA.neighbors.add(nodeB);
nodeA.neighbors.add(nodeC);
nodeB.neighbors.add(nodeA);
nodeB.neighbors.add(nodeC);
nodeC.neighbors.add(nodeA);
nodeC.neighbors.add(nodeB);
graph = [nodeA, nodeB, nodeC];
}
colorGraph(graph, colors);
assertEqual(validateGraphColoring(graph), true, 'triangle graph');
{
const nodeA = new GraphNode('A');
const nodeB = new GraphNode('B');
const nodeC = new GraphNode('C');
const nodeD = new GraphNode('D');
const nodeE = new GraphNode('E');
nodeA.neighbors.add(nodeB);
nodeA.neighbors.add(nodeC);
nodeB.neighbors.add(nodeA);
nodeB.neighbors.add(nodeC);
nodeB.neighbors.add(nodeD);
nodeB.neighbors.add(nodeE);
nodeC.neighbors.add(nodeA);
nodeC.neighbors.add(nodeB);
nodeC.neighbors.add(nodeD);
nodeC.neighbors.add(nodeE);
nodeD.neighbors.add(nodeB);
nodeD.neighbors.add(nodeC);
nodeD.neighbors.add(nodeE);
nodeE.neighbors.add(nodeB);
nodeE.neighbors.add(nodeC);
nodeE.neighbors.add(nodeD);
graph = [nodeA, nodeB, nodeC, nodeD, nodeE];
}
colorGraph(graph, colors);
assertEqual(validateGraphColoring(graph), true, 'envelope graph');
{
const nodeA = new GraphNode('A');
nodeA.neighbors.add(nodeA);
graph = [nodeA];
}
assertThrows(() => {
colorGraph(graph, colors);
}, 'loop graph');
function validateGraphColoring(graph) {
const maxDegree = Math.max(...graph.map(node => node.neighbors.size));
const colorsUsed = new Set();
graph.forEach(node => {
colorsUsed.add(node.color);
});
if (colorsUsed.has(null)) {
return false;
}
if (colorsUsed.size > maxDegree + 1) {
return false;
}
let badEdges = 0;
graph.forEach(node => {
node.neighbors.forEach(neighbor => {
if (neighbor.color === node.color) {
badEdges += 1;
}
});
});
if (badEdges > 0) {
return false;
}
return true;
}
function assertEqual(a, b, desc) {
if (a === b) {
console.log(`${desc} ... PASS`);
} else {
console.log(`${desc} ... FAIL: ${a} != ${b}`);
}
}
function assertThrows(func, desc) {
try {
func();
console.log(`${desc} ... FAIL`);
} catch (e) {
console.log(`${desc} ... PASS`);
}
}

View file

@ -24,7 +24,7 @@
** DONE Balanced Binary Tree
** DONE Binary Search Tree Checker
** DONE 2nd Largest Item in a Binary Search Tree
** TODO Graph Coloring
** DONE Graph Coloring
** TODO MeshMessage
** TODO Find Repeat, Space Edition BEAST MODE
* Dynamic programming and recursion

View file

@ -0,0 +1,7 @@
{
"compilerOptions": {
"downlevelIteration": true,
"target": "es5",
"lib": ["es6", "dom"]
}
}