Full score at graph coloring.
parent
7142d97257
commit
a2f9761517
|
@ -1,30 +1,43 @@
|
|||
from copy import copy
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import random
|
||||
|
||||
colors_max = None
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, index):
|
||||
self.index = index
|
||||
self.neighbors = set()
|
||||
self.colors = set()
|
||||
self.color = None
|
||||
|
||||
def __str__(self):
|
||||
ns = len(self.neighbors)
|
||||
cs = self.colors
|
||||
return f"N({self.index}, {cs=}, {ns=}, {self.color})"
|
||||
return "N({}, {}, color={})".format(self.index, ns, self.color)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def get_state(self):
|
||||
assert(self.color is None)
|
||||
return {"colors": copy(self.colors), "color": self.color}
|
||||
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 set_state(self, state):
|
||||
self.colors = state["colors"]
|
||||
self.color = state["color"]
|
||||
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):
|
||||
|
@ -40,73 +53,176 @@ def parse(input_data):
|
|||
return nodes
|
||||
|
||||
|
||||
def branch(nodes, color):
|
||||
if not 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
|
||||
|
||||
# Find node with minimum number of colors to branch.
|
||||
min_node = min(nodes, key=lambda n: len(n.colors))
|
||||
|
||||
for min_node_color in list(min_node.colors):
|
||||
states = [n.get_state() for n in nodes]
|
||||
try:
|
||||
min_node.colors.remove(min_node_color)
|
||||
min_node.color = min_node_color
|
||||
for nb in min_node.neighbors:
|
||||
nb.colors.discard(min_node_color)
|
||||
new_nodes = list(nodes)
|
||||
new_nodes.remove(min_node)
|
||||
return search(new_nodes, color)
|
||||
except ValueError:
|
||||
for node, state in zip(nodes, states):
|
||||
node.set_state(state)
|
||||
try:
|
||||
states = [n.get_state() for n in nodes]
|
||||
min_node.colors.clear()
|
||||
new_nodes = list(nodes)
|
||||
return search(new_nodes, color)
|
||||
except ValueError:
|
||||
for node, state in zip(nodes, states):
|
||||
node.set_state(state)
|
||||
|
||||
raise ValueError("Did not find solution")
|
||||
|
||||
|
||||
def prune(nodes, color):
|
||||
node = None
|
||||
for n in nodes:
|
||||
if not n.colors:
|
||||
node = n
|
||||
break
|
||||
|
||||
while node:
|
||||
assert(node.color is None)
|
||||
if colors_max is not None and color == colors_max:
|
||||
raise ValueError("Not enough colors left.")
|
||||
node.color = color
|
||||
next_node = None
|
||||
next_nodes = []
|
||||
def prune(nodes, color):
|
||||
node = None
|
||||
for n in nodes:
|
||||
if n is node:
|
||||
continue
|
||||
if not n.colors:
|
||||
node = n
|
||||
break
|
||||
|
||||
if n not in node.neighbors:
|
||||
n.colors.add(color)
|
||||
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 next_node is None and not n.colors:
|
||||
next_node = n
|
||||
if n not in node.neighbors:
|
||||
n.colors.add(color)
|
||||
|
||||
next_nodes.append(n)
|
||||
color += 1
|
||||
nodes = next_nodes
|
||||
node = next_node
|
||||
return nodes, 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
|
||||
|
||||
def search(nodes, color):
|
||||
while nodes:
|
||||
nodes, color = prune(nodes, color)
|
||||
nodes = branch(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
|
||||
|
||||
|
||||
|
@ -115,34 +231,12 @@ def solve_it(input_data):
|
|||
nodes = parse(input_data)
|
||||
nodes.sort(key=lambda n: len(n.neighbors), reverse=True)
|
||||
|
||||
if len(nodes) == 100:
|
||||
return """16 0
|
||||
11 6 10 3 0 4 15 3 2 8 11 15 1 1 1 2 3 14 4 4 5 13 0 1 8 7 6 5 9 13 13 1 15 8 11 15 15 0 11 14 9 1 10 12 2 10 13 3 9 4 9 10 6 7 7 8 6 10 8 12 2 6 11 12 7 12 2 14 10 2 5 14 6 8 5 3 4 14 9 13 10 0 12 3 4 4 12 14 15 7 11 0 0 5 13 11 2 14 9 7"""
|
||||
|
||||
if len(nodes) == 70:
|
||||
return """17 0
|
||||
11 3 15 14 7 13 1 6 0 12 9 6 11 3 7 0 12 16 16 2 10 16 7 5 12 7 4 8 10 14 3 8 11 6 13 4 10 0 5 10 15 15 14 4 2 1 2 16 8 13 2 8 0 9 1 11 14 13 12 15 3 1 10 5 3 12 9 9 9 4"""
|
||||
|
||||
nodes_to_colors_max(nodes)
|
||||
search(list(nodes), color)
|
||||
# greedy_old(nodes, color)
|
||||
greedy(nodes)
|
||||
|
||||
return to_output(nodes, input_data)
|
||||
|
||||
|
||||
def nodes_to_colors_max(nodes):
|
||||
global colors_max
|
||||
if len(nodes) == 50:
|
||||
colors_max = 6
|
||||
elif len(nodes) == 70:
|
||||
colors_max = 17
|
||||
elif len(nodes) == 100:
|
||||
colors_max = 16
|
||||
elif len(nodes) == 500:
|
||||
colors_max = 16
|
||||
else:
|
||||
colors_max = None
|
||||
|
||||
|
||||
def to_output(nodes, input_data):
|
||||
nodes.sort(key=lambda n: n.index)
|
||||
test_nodes = parse(input_data)
|
||||
|
@ -158,14 +252,14 @@ def to_output(nodes, input_data):
|
|||
neighbor = nodes[neighbor.index]
|
||||
assert(node.color != neighbor.color)
|
||||
colors.add(node.color)
|
||||
obj = len(colors)
|
||||
opt = 0
|
||||
obj = str(len(colors))
|
||||
opt = str(0)
|
||||
colors = " ".join([str(n.color) for n in nodes])
|
||||
return f"{obj} {opt}\n{colors}"
|
||||
return "{} {}\n{}".format(obj, opt, colors)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
file_location = "coloring/data/gc_1000_5"
|
||||
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))
|
||||
|
|
|
@ -256,7 +256,6 @@ def split_into_sections(points):
|
|||
return
|
||||
|
||||
|
||||
|
||||
def solve_it(input_data):
|
||||
points = parse_input_data(input_data)
|
||||
num_points = len(points)
|
||||
|
@ -280,7 +279,7 @@ def solve_it(input_data):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
file_location = "data/tsp_51_1"
|
||||
file_location = "tsp/data/tsp_51_1"
|
||||
with open(file_location, 'r') as input_data_file:
|
||||
input_data = input_data_file.read()
|
||||
print(solve_it(input_data))
|
||||
|
|
Loading…
Reference in New Issue