LOG = True	# whether or not to print intermediate solutions

def seq_assignment(nodes, adj):
    """Sequential color assignment.

    Starting with one color, for the graph represented by 'nodes' and 'adj':
       * go through all nodes, and check if a used color can be assigned;
       * if this is not possible, assign it a new color.
    Returns the solution found and the number of colors used.
       """
    K = 0       # number of colors used
    color = [None for i in nodes]       # solution vector
    for i in nodes:
        # determine colors currently assigned to nodes adjacent to i:
        adj_colors = set([color[j] for j in adj[i] if color[j] != None])
        if LOG:
            print "adj_colors[%d]:\t%s" % (i, adj_colors),
        for k in range(K):
            if k not in adj_colors:
                color[i] = k
                break
        else:
            color[i] = K
            K += 1
        if LOG:
            print "--> color[%d]: %s" % (i, color[i])
    return color, K


def largest_first(nodes, adj):
    """Sequencially assign colors, starting with nodes with largest degree.

    Firstly sort nodes by decreasing degree, then call sequential
    assignment, and return the solution it determined.
    """
    # sort nodes by decreasing degree (i.e., decreasing len(adj[i]))
    tmp = []    # to hold a list of pairs (degree,i)
    for i in nodes:
        degree = len(adj[i])
        tmp.append((degree,i))
    tmp.sort()          # sort by degree
    tmp.reverse()       # for decreasing degree
    sorted_nodes = [i for degree,i in tmp]      # extract the nodes from the pairs
    return seq_assignment(sorted_nodes, adj)    # sequential assignment on ordered nodes

    # # more efficient (geek) version:
    # nnodes = reversed(sorted([(len(adj[i]),i) for i in nodes]))
    # return seq_assignment([i for _,i in nnodes], adj)


def dsatur(nodes, adj):
    """Dsatur algorithm (Brelaz, 1979).
    
    Dynamically choose the vertex to color next, selecting one that is
    adjacent to the largest number of distinctly colored vertices.
    Returns the solution found and the number of colors used.
    """
    unc_adj = [set(adj[i]) for i in nodes]      # currently uncolored adjacent nodes
    adj_colors = [set([]) for i in nodes]       # set of adjacent colors, for each vertex
    color = [None for i in nodes]       # solution vector

    K = 0
    U = set(nodes)      # yet uncolored vertices
    while U:
        # choose vertex with maximum saturation degree
        max_colors = -1
        for i in U:
            n = len(adj_colors[i])
            if n > max_colors:
                max_colors = n
                max_uncolored = -1
            # break ties: get index of node with maximal degree on uncolored nodes
            if n == max_colors:
                adj_uncolored = len(unc_adj[i])
                if adj_uncolored > max_uncolored:
                    u_star = i
                    max_uncolored = adj_uncolored
        if LOG:
            print "u*:", u_star,
            print "\tadj_colors[%d]:\t%s" % (u_star, adj_colors[u_star]),

        # find a color for node 'u_star'
        for k in range(K):
            if k not in adj_colors[u_star]:
                k_star = k
                break
        else:   # must use a new color
            k_star = K
            K += 1
        color[u_star] = k_star
        for i in unc_adj[u_star]:
            unc_adj[i].remove(u_star)
            adj_colors[i].add(k_star)

        U.remove(u_star)

        if LOG:
            print "--> color[%d]:%s" % (u_star, color[u_star])
    return color, K


def recursive_largest_fit(nodes, adj):
    """Recursive largest fit algorithm (Leighton, 1979).

    Color vertices one color class at a time, in a greedy way.
    Returns the solution found and the number of colors used.
    """
    K = 0               # current color class
    V = set(nodes)      # yet uncolored vertices
    color = [None for i in nodes]       # solution vector
    unc_adj = [set(adj[i]) for i in nodes]      # currently uncolored adjacencies

    while V:
        
        # phase 1: color vertex with max number of connections to uncolored vertices
        max_edges = -1
        for i in V:
            n = len(unc_adj[i])
            if n > max_edges:
                max_edges = n
                u_star = i

        V.remove(u_star)
        color[u_star] = K
        for i in unc_adj[u_star]:
            unc_adj[i].remove(u_star)
        U = set(unc_adj[u_star]) # adj.vertices are uncolorable with current color
        V -= unc_adj[u_star]     # remove them from V
        if LOG:
            print "phase 1, u* =", u_star, "\tU =", U, "\tV =", V

        # phase 2: check for other vertices that can have the same color (K)
        while V:
            # determine colorable vertex with maximum uncolorable adjacencies:
            max_edges = -1
            for i in V: 
                v_adj = unc_adj[i] & U  # uncolorable, adjacent vertices 
                n = len(v_adj)
                if n > max_edges:
                    max_edges = n
                    u_star = i
            V.remove(u_star)
            color[u_star] = K
            for i in unc_adj[u_star]:
                unc_adj[i].remove(u_star)
            
            # remove from V all adjacencies not colorable with K
            not_colored = unc_adj[u_star] & V
            V -= not_colored    # remove uncolored adjacencies from V
            U |= not_colored    # add them to U
            if LOG:
                print "phase 2, u* =", u_star, "\tU =", U, "\tV =", V

        V = U   # update list of yet uncolored vertices
        K += 1  # switch to next color class

    return color, K



def check(nodes,adj,color):
    """Auxiliary function, for checking if a coloring is valid."""
    for i in nodes:
        for j in adj[i]:
            if color[i] != None and color[i] == color[j]:
                txt = "nodes %d,%d are connected and have the same color (%d)" % (i,j,color[i])
                raise ValueError, txt



if __name__ == "__main__":
    """Simple heuristics for Graph Coloring: sample usage."""

    import random
    # rndseed = 1	# uncomment for having always the same behavior
    # random.seed(rndseed)

    print "*** graph coloring problem ***"
    print

    #
    # uncomment this for a simple test
    #
     
    print "instance randomly created"
    from graphtools import rnd_adj, adjacent
    instance = "randomly created"
    nodes,adj = rnd_adj_fast(10,.5)
     
    print "sequential assignment"
    color, K = seq_assignment(nodes, adj)
    print "solution: z =", K
    print color
    print
    print "largest fit"
    color, K = largest_first(nodes, adj)
    print "solution: z =", K
    print color
    print
    print "dsatur"
    color, K = dsatur(nodes, adj)
    print "solution: z =", K
    print color
    print
    print "recursive largest fit"
    color, K = recursive_largest_fit(nodes, adj)
    print "solution: z =", K
    print color
    print
    exit(0)


    # #
    # # uncomment this for a thorough test
    # #
    #  
    # from time import clock
    # from graphtools import read_graph, shuffle
    # import sys
    #  
    # DIR = "INSTANCES/GCP/"        # location of the instance files
    # INST = ["myciel3.col","DSJC250.1.col","latin_square_10.col"] # instances to test
    # TENT = 10   # tentatives with each heuristics
    #  
    # print "%-20s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % \
    #       ("Instance", "|V|", "K_seq", "t", "K_lf", "t", "K_dsat", "t", "K_rlf", "t")
    # for inst in INST:
    #     instance = DIR+inst
    #  
    #     nodes,adj = read_graph(instance)
    #  
    #     for i in range(TENT):
    #         K1, t1, K2, t2, K3, t3, K4, t4 = -1,0, -1,0, -1,0, -1,0
    #  
    #         cpu = clock()
    #         color, K1 = seq_assignment(nodes, adj)
    #         t1 = clock() - cpu
    #         check(nodes,adj,color)
    #  
    #         cpu = clock()
    #         color, K2 = largest_first(nodes, adj)
    #         t2 = clock() - cpu
    #         check(nodes,adj,color)
    #  
    #         cpu = clock()
    #         color, K3 = dsatur(nodes, adj)
    #         t3 = clock() - cpu
    #         check(nodes,adj,color)
    #  
    #         cpu = clock()
    #         color, K4 = recursive_largest_fit(nodes, adj)
    #         t4 = clock() - cpu
    #         check(nodes,adj,color)
    #  
    #         print "%-20s\t%d\t%d\t%.2f\t%d\t%.2f\t%d\t%.2f\t%d\t%.2f" % \
    #               (inst, len(nodes), K1, t1, K2, t2, K3, t3, K4,t4)
    #         sys.stdout.flush()
    #  
    #         # randomize graph
    #         adj = shuffle(nodes,adj)
    #     print
