Skip to content

Commit

Permalink
Day 25 final, good form
Browse files Browse the repository at this point in the history
  • Loading branch information
zebalu committed Dec 27, 2023
1 parent 4f68f96 commit 2b4d0f3
Showing 1 changed file with 142 additions and 81 deletions.
223 changes: 142 additions & 81 deletions aoc2023/src/main/java/io/github/zebalu/aoc2023/days/Day25.java
Original file line number Diff line number Diff line change
@@ -1,98 +1,24 @@
package io.github.zebalu.aoc2023.days;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.nio.file.*;
import java.util.*;

public class Day25 {
public static void main(String[] args) {
String input = readInput();
Map<String, Set<String>> graph = new HashMap<>();
input.lines().forEach(l->processLine(l, graph));
Map<Set<String>, Integer> edgeFrequency = new HashMap<Set<String>, Integer>();
List<String> vertexes = new ArrayList<>(graph.keySet());
for(int i=0; i<vertexes.size(); ++i) {
String start = vertexes.get(i);
for(int j=i+1; j<vertexes.size(); ++j) {
String target = vertexes.get(j);
markEdges(start, target, graph, edgeFrequency);
}
}
edgeFrequency.entrySet().stream().sorted(Comparator.<Entry<Set<String>, Integer>>comparingInt(e->e.getValue()).reversed()).limit(3).forEach(e->{
cutEdge(graph, e.getKey());
});
int part1Size = findConnectedSize(graph.keySet().iterator().next(), graph);
int part2Size = graph.size()-part1Size;
System.out.println(part1Size*part2Size);
System.out.println("Marry Christmas!");
}

private static void markEdges(String start, String target, Map<String, Set<String>> graph,
Map<Set<String>, Integer> edgeFrequency) {
Queue<Step> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.add(new Step(start, List.of()));
visited.add(start);
while (!queue.isEmpty()) {
Step curr = queue.poll();
if(target.equals(curr.vertex)) {
curr.edges.forEach(e->{
int v = edgeFrequency.getOrDefault(e, 0);
edgeFrequency.put(e, v+1);
});
return;
}
graph.get(curr.vertex).stream().filter(n->!visited.contains(n)).forEach(n->{
List<Set<String>> nextEdges = new ArrayList<>(curr.edges());
nextEdges.add(new HashSet<>(Set.of(curr.vertex, n)));
Step nextStep = new Step(n, nextEdges);
queue.add(nextStep);
visited.add(n);
});
}
}

private static void cutEdge(Map<String, Set<String>> graph, Set<String> edge) {
var it = edge.iterator();
String a = it.next();
String b = it.next();
graph.get(a).remove(b);
graph.get(b).remove(a);
}

private static int findConnectedSize(String start, Map<String, Set<String>> graph) {
Queue<String> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.add(start);
visited.add(start);
while (!queue.isEmpty()) {
String curr = queue.poll();
graph.get(curr).stream().filter(n->!visited.contains(n)).forEach(n->{
queue.add(n);
visited.add(n);
});
}
return visited.size();
}

record Step(String vertex, List<Set<String>> edges) {

var partitioning = partitionGraphWithMaxCut(graph, 3);

System.out.println(partitioning.part1.size()*partitioning.part2.size());
System.out.println("Marry Christmas!");
}

private static String readInput() {
try {
return Files.readString(Path.of("day25.txt").toAbsolutePath());
} catch (IOException e) {
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
Expand All @@ -110,4 +36,139 @@ private static void markConnectionn(Map<String, Set<String>> graph, String from,
graph.computeIfAbsent(from, (k)->new HashSet<>()).add(to);
graph.computeIfAbsent(to, (k)->new HashSet<>()).add(from);
}

private static Set<Set<String>> findShortestPath(Map<String, Set<String>> graph, String from, String to, Set<Set<String>> forbiddenEdges) {
record PathStep(String at, List<String> history) {}
Set<String> visited = new HashSet<>();
Queue<PathStep> queue = new LinkedList<>();
visited.add(from);
queue.add(new PathStep(from, List.of(from)));
while (!queue.isEmpty()) {
var current = queue.poll();
for (String next : graph.get(current.at).stream()
.filter(n -> !visited.contains(n) && !forbiddenEdges.contains(Set.of(current.at, n))).toList()) {
List<String> history = new ArrayList<>(current.history);
history.add(next);
queue.add(new PathStep(next, history));
visited.add(next);
if (next.equals(to)) {
return constructPath(history);
}
}
}
return new HashSet<>();
}

private static SequencedSet<Set<String>> constructPath(List<String> pathElements) {
SequencedSet<Set<String>> path = new LinkedHashSet<Set<String>>();
for(int i=0; i<pathElements.size()-1; ++i) {
path.add(Set.of(pathElements.get(i), pathElements.get(i+1)));
}
return path;
}

private record GraphPartition(Map<String, Set<String>> part1, Map<String, Set<String>> part2, Set<Set<String>> cuttedEdges) {

}

/**
* Partitions the graph into 2 separate partitions bay cutting the shortest path between 2 vertices maxCut times.
* This can only work if the partitioning requires less cut than the minimum connectivity of the vertices.
* @param graph the graph to cut.
* @param maxCut the times we remove shortest pathes from the graph
* @return the partitioning with the 2 parts and the bridges
*/
private static GraphPartition partitionGraphWithMaxCut(Map<String, Set<String>> graph, int maxCut) {
int minimumConnectivity = graph.values().stream().mapToInt(s->s.size()).min().orElseThrow();
if(minimumConnectivity <= maxCut) {
throw new IllegalArgumentException("This algorithm won't work, as the graph has too low connectivity number");
}
List<String> vertices = new ArrayList<String>(graph.keySet());
for(int i=0; i<vertices.size()-1; ++i) {
String from = vertices.get(i);
for(int j=i+1; j<vertices.size(); ++j) {
String to = vertices.get(j);
Set<Set<String>> forbiddenEdges = new HashSet<>();
for(int k=0; k<maxCut; ++k) {
Set<Set<String>> path = findShortestPath(graph, from, to, forbiddenEdges);
if(path.isEmpty()) {
throw new IllegalStateException("Too early not to find a path from: "+from+" to: "+to+" in the "+k+". step");
}
forbiddenEdges.addAll(path);
}
Set<Set<String>> path = findShortestPath(graph, from, to, forbiddenEdges);
if(path.isEmpty()) {
// the edges to cut are in the forbidden set
return createPartitioning(graph, from, to, forbiddenEdges);
} else {
// from and to are in the same subgraph (or at least they have more then maxCut shortest pathes between them)
}
}
}
throw new IllegalStateException("Could not partition the graph in "+maxCut+" steps");
}

/**
* Creates partitions of the two vertices, where the bridge vertices are hidden between the candidate edges.
* @param graph the graph to partition
* @param from a vertex in one of the partitions
* @param to a vertex in the other partition
* @param candidateEdges some of these edges are the bridges
* @return the partitioning with the 2 parts and the bridge edges
*/
private static GraphPartition createPartitioning(Map<String, Set<String>> graph, String from, String to, Set<Set<String>> candidateEdges) {
Set<Set<String>> bridges = new HashSet<>();

for(var edge: candidateEdges) {
// forbid the use of all candidate edges, except the current one
var forbiddenEdges = new HashSet<>(candidateEdges);
forbiddenEdges.remove(edge);
var path = findShortestPath(graph, from, to, forbiddenEdges);
if(!path.isEmpty()) {
// this is a bridge between the 2 parts
bridges.add(edge);
}
}

Map<String, Set<String>> part1 = new HashMap<>();
Map<String, Set<String>> part2 = new HashMap<>();

Set<String> part1Vertices = findConnectedVertices(from, graph, bridges);

for(String id: graph.keySet()) {
Set<String> connections = graph.get(id);
for(String v2: connections) {
if(!bridges.contains(Set.of(id, v2))) {
if(part1Vertices.contains(id)) {
markConnectionn(part1, id, v2);
} else {
markConnectionn(part2, id, v2);
}
}
}
}
return new GraphPartition(part1, part2, bridges);
}

/**
* Finds all reachable vertices, but does not use the forbidden edges.
* @param start the start vertex to run from
* @param graph the graph to work with
* @param forbiddenEdges edges which can not be used
* @return The partitioning with the 2 separate parts and the bridges
*/
private static Set<String> findConnectedVertices(String start, Map<String, Set<String>> graph, Set<Set<String>> forbiddenEdges) {
Queue<String> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.add(start);
visited.add(start);
while (!queue.isEmpty()) {
String curr = queue.poll();
graph.get(curr).stream().filter(n->!visited.contains(n) && ! forbiddenEdges.contains(Set.of(curr, n))).forEach(n->{
queue.add(n);
visited.add(n);
});
}
return visited;
}
}

0 comments on commit 2b4d0f3

Please sign in to comment.