Completed Day 25 puzzle with exemple from geeksforgeeks.org for Karger's mincut algorithm

main
oabrivard 2 years ago
parent 11d1b155d3
commit 6856482338

@ -2,6 +2,8 @@ package day25
import ( import (
"fmt" "fmt"
"log"
"math/rand"
"strings" "strings"
"gitea.paas.celticinfo.fr/oabrivard/aoc2023/utils" "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 return false, nil, nil, nil
} }
// Too basic. This brute force approach does not work with a large graph
func Part1(lines []string) int { func Part1(lines []string) int {
graph := buildGraph(lines) graph := buildGraph(lines)
printGraph(graph) printGraph(graph)
@ -188,3 +191,243 @@ func Part1(lines []string) int {
return len(result[0]) * len(result[1]) 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
}

@ -39,3 +39,81 @@ func TestPart1WithInput(t *testing.T) {
t.Fatalf("expected 2402, got %d", result) 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)
}
}

Loading…
Cancel
Save