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:
parent
1d45f14615
commit
380a6a352c
3 changed files with 240 additions and 1 deletions
232
scratch/deepmind/part_two/graph-coloring.ts
Normal file
232
scratch/deepmind/part_two/graph-coloring.ts
Normal 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`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@
|
||||||
** DONE Balanced Binary Tree
|
** DONE Balanced Binary Tree
|
||||||
** DONE Binary Search Tree Checker
|
** DONE Binary Search Tree Checker
|
||||||
** DONE 2nd Largest Item in a Binary Search Tree
|
** DONE 2nd Largest Item in a Binary Search Tree
|
||||||
** TODO Graph Coloring
|
** DONE Graph Coloring
|
||||||
** TODO MeshMessage
|
** TODO MeshMessage
|
||||||
** TODO Find Repeat, Space Edition BEAST MODE
|
** TODO Find Repeat, Space Edition BEAST MODE
|
||||||
* Dynamic programming and recursion
|
* Dynamic programming and recursion
|
||||||
|
|
7
scratch/deepmind/part_two/tsconfig.json
Normal file
7
scratch/deepmind/part_two/tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es6", "dom"]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue