266 lines
7.2 KiB
Python
266 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import random
|
|
|
|
|
|
class Node(object):
|
|
|
|
def __init__(self, index):
|
|
self.index = index
|
|
self.neighbors = set()
|
|
self.color = None
|
|
|
|
def __str__(self):
|
|
ns = len(self.neighbors)
|
|
return "N({}, {}, color={})".format(self.index, ns, self.color)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
def is_feasible(self, color):
|
|
""" Returns True if color can be assigned without causing
|
|
a violation with a neighbor. """
|
|
assert(color is not None)
|
|
for nb in self.neighbors:
|
|
if nb.color == color:
|
|
return False
|
|
return True
|
|
|
|
def get_color_count(self, color):
|
|
""" Returns how many neighbors with that color exist. """
|
|
return sum([1 for n in self.neighbors if n.color == color])
|
|
|
|
def neighbors_by_color(self, max_color):
|
|
neighbors = {c: [] for c in range(0, max_color)}
|
|
for nb in self.neighbors:
|
|
neighbors[nb.color].append(nb)
|
|
return neighbors
|
|
|
|
def get_neighbors_by_color(self, color):
|
|
return [nb for nb in self.neighbors if nb.color == color]
|
|
|
|
|
|
def parse(input_data):
|
|
# parse the input
|
|
lines = input_data.split('\n')
|
|
node_count, edge_count = map(int, lines[0].split())
|
|
nodes = [Node(i) for i in range(node_count)]
|
|
|
|
for i in range(1, edge_count + 1):
|
|
n_1, n_2 = map(int, lines[i].split())
|
|
nodes[n_1].neighbors.add(nodes[n_2])
|
|
nodes[n_2].neighbors.add(nodes[n_1])
|
|
return nodes
|
|
|
|
|
|
def greedy_old(nodes, color):
|
|
|
|
def branch(nodes):
|
|
if not nodes:
|
|
return nodes
|
|
|
|
min_node = min(nodes, key=lambda n: len(n.colors))
|
|
nodes.remove(min_node)
|
|
|
|
min_node.color = min_node.colors.pop()
|
|
for nb in min_node.neighbors:
|
|
nb.colors.discard(min_node.color)
|
|
return nodes
|
|
|
|
def prune(nodes, color):
|
|
node = None
|
|
for n in nodes:
|
|
if not n.colors:
|
|
node = n
|
|
break
|
|
|
|
while node:
|
|
assert(node.color is None)
|
|
node.color = color
|
|
next_node = None
|
|
next_nodes = []
|
|
for n in nodes:
|
|
if n is node:
|
|
continue
|
|
|
|
if n not in node.neighbors:
|
|
n.colors.add(color)
|
|
|
|
if next_node is None and not n.colors:
|
|
next_node = n
|
|
|
|
next_nodes.append(n)
|
|
color += 1
|
|
nodes = next_nodes
|
|
node = next_node
|
|
return nodes, color
|
|
|
|
while nodes:
|
|
nodes, color = prune(nodes, color)
|
|
nodes = branch(nodes)
|
|
return nodes
|
|
|
|
|
|
def kemp_chain(node, color_a, color_b):
|
|
assert(node.color == color_a)
|
|
visited = set()
|
|
to_invert = set([node])
|
|
while to_invert:
|
|
n = to_invert.pop()
|
|
visited.add(n)
|
|
|
|
if n.color == color_a:
|
|
n.color = color_b
|
|
for nb in n.neighbors:
|
|
if nb.color == color_b and not nb in visited:
|
|
to_invert.add(nb)
|
|
elif n.color == color_b:
|
|
n.color = color_a
|
|
for nb in n.neighbors:
|
|
if nb.color == color_a and not nb in visited:
|
|
to_invert.add(nb)
|
|
|
|
|
|
def maximize_color(nodes, color):
|
|
for n in nodes:
|
|
assert(n.color is None)
|
|
|
|
colored_nodes = []
|
|
uncolored_nodes = []
|
|
|
|
colored_nodes_max = []
|
|
uncolored_nodes_max = []
|
|
|
|
for i in range(250):
|
|
random.shuffle(nodes)
|
|
|
|
for n in nodes:
|
|
if n.color is None and n.is_feasible(color):
|
|
n.color = color
|
|
colored_nodes.append(n)
|
|
elif n.color is None:
|
|
uncolored_nodes.append(n)
|
|
|
|
if len(colored_nodes) > len(colored_nodes_max):
|
|
colored_nodes_max = colored_nodes.copy()
|
|
uncolored_nodes_max = uncolored_nodes.copy()
|
|
|
|
for n in nodes:
|
|
n.color = None
|
|
|
|
colored_nodes.clear()
|
|
uncolored_nodes.clear()
|
|
|
|
for n in colored_nodes_max:
|
|
n.color = color
|
|
|
|
return uncolored_nodes_max
|
|
|
|
|
|
def eliminate_color_from_node(node, color_to_eliminate):
|
|
possible_colors = [c for c in range(0, color_to_eliminate)]
|
|
possible_colors.sort(key=lambda c: len(node.get_neighbors_by_color(c)))
|
|
|
|
for color_a in possible_colors:
|
|
|
|
count_a = node.get_color_count(color_a)
|
|
neighbors_with_color_a = node.get_neighbors_by_color(color_a)
|
|
|
|
while neighbors_with_color_a:
|
|
nb = neighbors_with_color_a.pop()
|
|
for color_b in possible_colors:
|
|
if color_a == color_b:
|
|
continue
|
|
|
|
kemp_chain(nb, color_a, color_b)
|
|
count_a_new = node.get_color_count(color_a)
|
|
|
|
if count_a_new >= count_a:
|
|
kemp_chain(nb, color_b, color_a)
|
|
else:
|
|
count_a = count_a_new
|
|
neighbors_with_color_a = node.get_neighbors_by_color(color_a)
|
|
break
|
|
|
|
count_a_new = node.get_color_count(color_a)
|
|
assert(count_a == count_a_new)
|
|
if count_a == 0:
|
|
node.color = color_a
|
|
return
|
|
raise ValueError("Wasn't able to eliminate color.")
|
|
|
|
|
|
def eliminate_color(nodes, color_to_eliminate):
|
|
nodes_with_color = [n for n in nodes if n.color == color_to_eliminate]
|
|
for node in nodes_with_color:
|
|
eliminate_color_from_node(node, color_to_eliminate)
|
|
return nodes
|
|
|
|
|
|
def shuffle(nodes, max_color):
|
|
colors = list(range(0, max_color))
|
|
random.shuffle(nodes)
|
|
for node in nodes:
|
|
color = random.choice(colors)
|
|
kemp_chain(node, node.color, color)
|
|
return nodes
|
|
|
|
|
|
def greedy(nodes):
|
|
color = 0
|
|
max_color = {50: 6, 70: 17, 100: 16, 250: 78, 500: 16, 1000: 100}[len(nodes)]
|
|
|
|
uncolored_nodes = nodes
|
|
while uncolored_nodes:
|
|
uncolored_nodes = maximize_color(uncolored_nodes, color)
|
|
color += 1
|
|
|
|
while color >= max_color:
|
|
try:
|
|
eliminate_color(nodes, color)
|
|
color -= 1
|
|
except ValueError:
|
|
# print("Could not eliminate {}. Shuffle and try again.".format(color))
|
|
shuffle(nodes, color)
|
|
|
|
return nodes
|
|
|
|
|
|
def solve_it(input_data):
|
|
color = 0
|
|
nodes = parse(input_data)
|
|
nodes.sort(key=lambda n: len(n.neighbors), reverse=True)
|
|
|
|
# greedy_old(nodes, color)
|
|
greedy(nodes)
|
|
|
|
return to_output(nodes, input_data)
|
|
|
|
|
|
def to_output(nodes, input_data):
|
|
nodes.sort(key=lambda n: n.index)
|
|
test_nodes = parse(input_data)
|
|
colors = set()
|
|
|
|
assert(len(nodes) == len(test_nodes))
|
|
for i in range(len(test_nodes)):
|
|
node = nodes[i]
|
|
test_node = test_nodes[i]
|
|
assert(test_node.index == node.index)
|
|
# This works even if we got rid of the neighbors in the algorithm.
|
|
for neighbor in test_node.neighbors:
|
|
neighbor = nodes[neighbor.index]
|
|
assert(node.color != neighbor.color)
|
|
colors.add(node.color)
|
|
obj = str(len(colors))
|
|
opt = str(0)
|
|
colors = " ".join([str(n.color) for n in nodes])
|
|
return "{} {}\n{}".format(obj, opt, colors)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
file_location = "coloring/data/gc_50_3"
|
|
with open(file_location, 'r') as input_data_file:
|
|
input_data = input_data_file.read()
|
|
print(solve_it(input_data))
|