From 6856482338f089c22748e1d691e08e9ef5cb2364 Mon Sep 17 00:00:00 2001 From: oabrivard Date: Mon, 8 Jan 2024 21:46:35 +0100 Subject: [PATCH] Completed Day 25 puzzle with exemple from geeksforgeeks.org for Karger's mincut algorithm --- day25/day25.go | 243 ++++++++++++++++++++++++++++++++++++++++++++ day25/day25_test.go | 78 ++++++++++++++ 2 files changed, 321 insertions(+) diff --git a/day25/day25.go b/day25/day25.go index b5e3fa2..6161788 100644 --- a/day25/day25.go +++ b/day25/day25.go @@ -2,6 +2,8 @@ package day25 import ( "fmt" + "log" + "math/rand" "strings" "gitea.paas.celticinfo.fr/oabrivard/aoc2023/utils" @@ -159,6 +161,7 @@ func findEdgesToDelete(graph Graph, edges []Edge) (bool, *Edge, *Edge, *Edge) { return false, nil, nil, nil } +// Too basic. This brute force approach does not work with a large graph func Part1(lines []string) int { graph := buildGraph(lines) printGraph(graph) @@ -188,3 +191,243 @@ func Part1(lines []string) int { return len(result[0]) * len(result[1]) } + +// Try to solve it using https://en.wikipedia.org/wiki/Karger%27s_algorithm +func randomStringKey[T any](dic map[string]T) string { + i := rand.Int() % len(dic) + + for key := range dic { + i-- + if i < 0 { + return key + } + } + + return "" +} + +func swapConverged(graph *Graph, target, converged, vertex string) { + connected := (*graph)[target] + + val, found := connected[converged] + if !found { + log.Fatal("Should never happen") + } + delete(connected, converged) + connected[vertex] = val +} + +func kargerMinCut(graph Graph) (*Graph, int) { + count := 0 + + for len(graph) > 2 { + // randomly select a vertex and one of its connected vertex (called 'converged') to be merged + vertex := randomStringKey(graph) + converged := randomStringKey(graph[vertex]) + + // Remove link between vertex and converged vertex + delete(graph[converged], vertex) + delete(graph[vertex], converged) + count++ + + // Merge converged vertex into vertex + for k, v := range graph[converged] { + graph[vertex][k] = v + } + + // swap converged vertex with vertex in all adjacency list vertices that shared an edge with converged vertex + for k := range graph[converged] { + verticesToLink := graph[k] + verticesToLink[vertex] = true + delete(verticesToLink, converged) + //swapConverged(&graph, k, converged, vertex) + } + + // remove converged vertex from graph (adjency list) + delete(graph, converged) + count = len(graph[vertex]) + } + + return &graph, count +} + +func duplicateGraph(graph *Graph) *Graph { + result := Graph{} + + for k1, v1 := range *graph { + result[k1] = map[string]bool{} + for k2, v2 := range v1 { + result[k1][k2] = v2 + } + } + + return &result +} + +// buggy since the adjency list is implemented with a map of map. Shoud be a map of slices to work +func Part1WithKerger(lines []string) int { + srcGraph := buildGraph(lines) + printGraph(srcGraph) + edges := buildEdgeList(srcGraph) + fmt.Println(edges) + + minCut := 0 + var graphParts *Graph + + for minCut != 3 { + graph := duplicateGraph(&srcGraph) + + graphParts, minCut = kargerMinCut(*graph) + } + + if len(*graphParts) != 2 { + log.Fatal("Should always be 2") + } + + result := 1 + for _, v := range *graphParts { + result *= len(v) + } + + return result +} + +type GraphHolder struct { + v, e int + edges []Edge + graph Graph +} + +type Subset struct { + parent string + rank int +} + +// see https://ondrej-kvasnovsky-2.gitbook.io/algorithms/finding/union-find-algorithms +// or https://jojozhuang.github.io/algorithm/algorithm-union-find/ +func find(subsets map[string]*Subset, e string) string { + // find root and make root as parent of e + // (path compression) + if subsets[e].parent != e { + subsets[e].parent = find(subsets, subsets[e].parent) + } + return subsets[e].parent +} + +func Union(subsets map[string]*Subset, x, y string) { + xroot := find(subsets, x) + yroot := find(subsets, y) + + // Attach smaller rank tree under root of high + // rank tree (Union by Rank) + if subsets[xroot].rank < subsets[yroot].rank { + subsets[xroot].parent = yroot + } else { + if subsets[xroot].rank > subsets[yroot].rank { + subsets[yroot].parent = xroot + } else { + // If ranks are same, then make one as root and + // increment its rank by one + subsets[yroot].parent = xroot + subsets[xroot].rank++ + } + } +} + +// Adapted from https://www.geeksforgeeks.org/introduction-and-implementation-of-kargers-algorithm-for-minimum-cut/?ref=lbp +func kargerMinCutUF(graphHolder GraphHolder) (int, *map[string]*Subset) { + // Get data of given graph + V := graphHolder.v + E := graphHolder.e + edges := graphHolder.edges + + // Allocate memory for creating V subsets. + subsets := map[string]*Subset{} + + // Create V subsets with single elements + for k := range graphHolder.graph { + subsets[k] = &Subset{k, 0} + } + + // Initially there are V vertices in + // contracted graph + vertices := V + + // Keep contracting vertices until there are + // 2 vertices. + for vertices > 2 { + // Pick a random edge + i := rand.Int() % E + + // Find vertices (or sets) of two corners + // of current edge + subset1 := find(subsets, edges[i].left) + subset2 := find(subsets, edges[i].right) + + // If two corners belong to same subset, + // then no point considering this edge + if subset1 == subset2 { + continue + } else { + // Else contract the edge (or combine the + // corners of edge into one vertex) + vertices-- + Union(subsets, subset1, subset2) + } + } + + // Now we have two vertices (or subsets) left in + // the contracted graph, so count the edges between + // two components and return the count. + cutedges := 0 + for i := 0; i < E; i++ { + subset1 := find(subsets, edges[i].left) + subset2 := find(subsets, edges[i].right) + if subset1 != subset2 { + cutedges++ + } + } + + return cutedges, &subsets +} + +func Part1WithKergerUF(lines []string) int { + srcGraph := buildGraph(lines) + printGraph(srcGraph) + edges := buildEdgeList(srcGraph) + fmt.Println(edges) + + gh := GraphHolder{len(srcGraph), len(edges), edges, srcGraph} + + minCut := 1000000 + for i := 0; i < 1000; i++ { + cut, subsets := kargerMinCutUF(gh) + + if cut < minCut { + minCut = cut + } + + if minCut == 3 { + counts := map[string]int{} + + for _, v := range *subsets { + currCount, found := counts[v.parent] + if !found { + counts[v.parent] = 1 + } else { + counts[v.parent] = currCount + 1 + } + } + + result := 1 + for _, count := range counts { + result *= count + } + + return result + + } + } + + return minCut +} diff --git a/day25/day25_test.go b/day25/day25_test.go index 5d2174a..deadbae 100644 --- a/day25/day25_test.go +++ b/day25/day25_test.go @@ -39,3 +39,81 @@ func TestPart1WithInput(t *testing.T) { t.Fatalf("expected 2402, got %d", result) } } + +func TestPart1WithKerger(t *testing.T) { + /* + lines := []string{ + "jqt: rhn xhk nvd", + "rsh: frs pzl lsr", + "xhk: hfx", + "cmg: qnr nvd lhk bvb", + "rhn: xhk bvb hfx", + "bvb: xhk hfx", + "pzl: lsr hfx nvd", + "qnr: nvd", + "ntq: jqt hfx bvb xhk", + "nvd: lhk", + "lsr: lhk", + "rzs: qnr cmg lsr rsh", + "frs: qnr lhk lsr", + } + */ + lines := []string{ + "1: 2 3 4", + "2: 1 3 5", + "3: 1 2 4 5", + "4: 1 3", + "5: 2 3", + } + + result := Part1WithKerger(lines) + + if result != 54 { + t.Fatalf("expected 54, got %d", result) + } +} + +func TestPart1WithKergerUF(t *testing.T) { + + lines := []string{ + "jqt: rhn xhk nvd", + "rsh: frs pzl lsr", + "xhk: hfx", + "cmg: qnr nvd lhk bvb", + "rhn: xhk bvb hfx", + "bvb: xhk hfx", + "pzl: lsr hfx nvd", + "qnr: nvd", + "ntq: jqt hfx bvb xhk", + "nvd: lhk", + "lsr: lhk", + "rzs: qnr cmg lsr rsh", + "frs: qnr lhk lsr", + } + + /* + lines := []string{ + "1: 2 3 4", + "2: 1 3 5", + "3: 1 2 4 5", + "4: 1 3", + "5: 2 3", + } + */ + + result := Part1WithKergerUF(lines) + + if result != 54 { + t.Fatalf("expected 54, got %d", result) + } +} + +func TestPart1WithKergerUFWithInput(t *testing.T) { + lines := utils.ReadLines("input.txt") + + result := Part1WithKergerUF(lines) + + if result != 569904 { + t.Fatalf("expected 569904, got %d", result) + } +}