From 66cc3958b06f378b103dd488fbcfef70c76d1801 Mon Sep 17 00:00:00 2001 From: lol-cubes Date: Sat, 25 Jul 2020 18:33:27 -0400 Subject: [PATCH 1/4] Add implementation of Karger's Algorithm --- graphs/karger.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 graphs/karger.py diff --git a/graphs/karger.py b/graphs/karger.py new file mode 100644 index 000000000000..3540a6ee71e1 --- /dev/null +++ b/graphs/karger.py @@ -0,0 +1,87 @@ +""" +An implementation of Karger's Algorithm for partitioning a graph with a certain probability of finding the minimum edge cut. +""" + +import random +from typing import Dict, List, Set, Tuple + + +# Adjacency list representation of this graph: +# https://en.wikipedia.org/wiki/File:Single_run_of_Karger%E2%80%99s_Mincut_algorithm.svg +TEST_GRAPH = { + '1': ['2', '3', '4', '5'], + '2': ['1', '3', '4', '5'], + '3': ['1', '2', '4', '5', '10'], + '4': ['1', '2', '3', '5', '6'], + '5': ['1', '2', '3', '4', '7'], + '6': ['7', '8', '9', '10', '4'], + '7': ['6', '8', '9', '10', '5'], + '8': ['6', '7', '9', '10'], + '9': ['6', '7', '8', '10'], + '10': ['6', '7', '8', '9', '3'] +} + + +def partition_graph(graph: Dict[ str, List[str] ]) -> Set[ Tuple[str, str] ]: + """ + Partitions a graph using Karger's Algorithm. Implemented from pseudocode found here: + https://en.wikipedia.org/wiki/Karger%27s_algorithm. + NOTE: This function involves random choices, meaning it will not give consistent outputs. + + Args: + graph: A dictionary containing adacency lists for the graph. Nodes must be strings. + + Returns: + The cutset of the cut found by Karger's Algorithm. + + >>> graph = {'0':['1'], '1':['0']} + >>> partition_graph(graph) + {('0', '1')} + """ + # Dict that maps contracted nodes to a list of all the nodes it "contains." + contracted_nodes = {node: {node} for node in graph} + + graph_copy = {node: graph[node][:] for node in graph} + + while len(graph_copy) > 2: + + # Choose a random edge. + u = random.choice(list(graph_copy.keys())) + v = random.choice(graph_copy[u]) + + # Contract edge (u, v) to new node uv + uv = u + v + uv_neighbors = list(set(graph_copy[u] + graph_copy[v])) + uv_neighbors.remove(u) + uv_neighbors.remove(v) + graph_copy[uv] = uv_neighbors + for neighbor in uv_neighbors: + graph_copy[neighbor].append(uv) + + contracted_nodes[uv] = set() + for contracted_node in contracted_nodes[u]: + contracted_nodes[uv].add(contracted_node) + for contracted_node in contracted_nodes[v]: + contracted_nodes[uv].add(contracted_node) + + # Remove nodes u and v. + del graph_copy[u] + del graph_copy[v] + for neighbor in uv_neighbors: + if u in graph_copy[neighbor]: + graph_copy[neighbor].remove(u) + if v in graph_copy[neighbor]: + graph_copy[neighbor].remove(v) + + # Find cutset. + cutset = set() + groups = [contracted_nodes[node] for node in graph_copy] + print(groups) + for node in groups[0]: + for neighbor in graph[node]: + if neighbor in groups[1]: + cutset.add((node, neighbor)) + return cutset + +if __name__ == "__main__": + print(partition_graph(TEST_GRAPH)) From 15c4fb41c5a57ed84162e33ba17eadea8967a0f8 Mon Sep 17 00:00:00 2001 From: lol-cubes Date: Sat, 25 Jul 2020 18:39:07 -0400 Subject: [PATCH 2/4] Remove print statement from karger's algorithm function --- graphs/karger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphs/karger.py b/graphs/karger.py index 3540a6ee71e1..1c83b31b5c01 100644 --- a/graphs/karger.py +++ b/graphs/karger.py @@ -76,7 +76,6 @@ def partition_graph(graph: Dict[ str, List[str] ]) -> Set[ Tuple[str, str] ]: # Find cutset. cutset = set() groups = [contracted_nodes[node] for node in graph_copy] - print(groups) for node in groups[0]: for neighbor in graph[node]: if neighbor in groups[1]: From 3942ba036b0cb4e19aa4bdc025bcc19976d9464e Mon Sep 17 00:00:00 2001 From: lol-cubes Date: Sat, 25 Jul 2020 18:58:49 -0400 Subject: [PATCH 3/4] Fix style issues in graphs/karger.py --- graphs/karger.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/graphs/karger.py b/graphs/karger.py index 1c83b31b5c01..88c5b889daef 100644 --- a/graphs/karger.py +++ b/graphs/karger.py @@ -1,5 +1,5 @@ """ -An implementation of Karger's Algorithm for partitioning a graph with a certain probability of finding the minimum edge cut. +An implementation of Karger's Algorithm for partitioning a graph. """ import random @@ -22,15 +22,18 @@ } -def partition_graph(graph: Dict[ str, List[str] ]) -> Set[ Tuple[str, str] ]: +def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: """ - Partitions a graph using Karger's Algorithm. Implemented from pseudocode found here: + Partitions a graph using Karger's Algorithm. Implemented from + pseudocode found here: https://en.wikipedia.org/wiki/Karger%27s_algorithm. - NOTE: This function involves random choices, meaning it will not give consistent outputs. - + This function involves random choices, meaning it will not give + consistent outputs. + Args: - graph: A dictionary containing adacency lists for the graph. Nodes must be strings. - + graph: A dictionary containing adacency lists for the graph. + Nodes must be strings. + Returns: The cutset of the cut found by Karger's Algorithm. @@ -82,5 +85,6 @@ def partition_graph(graph: Dict[ str, List[str] ]) -> Set[ Tuple[str, str] ]: cutset.add((node, neighbor)) return cutset + if __name__ == "__main__": print(partition_graph(TEST_GRAPH)) From ca647a5fc62e2aadd758ffe64944dbb8f7b36401 Mon Sep 17 00:00:00 2001 From: lol-cubes Date: Sun, 26 Jul 2020 11:17:55 -0400 Subject: [PATCH 4/4] Change for loops to set comprehensions where appropriate in graphs/karger.py --- graphs/karger.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/graphs/karger.py b/graphs/karger.py index 88c5b889daef..d5a27c285fd4 100644 --- a/graphs/karger.py +++ b/graphs/karger.py @@ -61,11 +61,8 @@ def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: for neighbor in uv_neighbors: graph_copy[neighbor].append(uv) - contracted_nodes[uv] = set() - for contracted_node in contracted_nodes[u]: - contracted_nodes[uv].add(contracted_node) - for contracted_node in contracted_nodes[v]: - contracted_nodes[uv].add(contracted_node) + contracted_nodes[uv] = {contracted_node for contracted_node in + contracted_nodes[u].union(contracted_nodes[v])} # Remove nodes u and v. del graph_copy[u] @@ -77,13 +74,9 @@ def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: graph_copy[neighbor].remove(v) # Find cutset. - cutset = set() groups = [contracted_nodes[node] for node in graph_copy] - for node in groups[0]: - for neighbor in graph[node]: - if neighbor in groups[1]: - cutset.add((node, neighbor)) - return cutset + return {(node, neighbor) for node in groups[0] + for neighbor in graph[node] if neighbor in groups[1]} if __name__ == "__main__":