Advertisement
Guest User

Untitled

a guest
Apr 19th, 2019
105
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.22 KB | None | 0 0
  1. """6.009 Lab 7 -- Faster Gift Delivery."""
  2.  
  3.  
  4. from graph import Graph
  5.  
  6. # NO ADDITIONAL IMPORTS ALLOWED!
  7.  
  8. class GraphFactory:
  9.     """Factory methods for creating instances of `Graph`."""
  10.  
  11.     def __init__(self, graph_class):
  12.         """Return a new factory that creates instances of `graph_class`."""
  13.         self.graph_class = graph_class
  14.  
  15.     def from_list(self, adj_list, labels=None):
  16.         """Create and return a new graph instance.
  17.  
  18.        Use a simple adjacency list as source, where the `labels` dictionary
  19.        maps each node name to its label.
  20.  
  21.        Parameters:
  22.            `adj_list`: adjacency list representation of a graph
  23.                        (as a list of lists)
  24.            `labels`: dictionary mapping each node name to its label;
  25.                      by default it's None, which means no label should be set
  26.                      for any of the nodes
  27.  
  28.        Returns:
  29.            new instance of class implementing `Graph`
  30.  
  31.        """
  32.         graph = self.graph_class()
  33.  
  34.         # add each node from the adjacency list with its corresponding label
  35.         for name in range(len(adj_list)):
  36.             if labels is not None and labels[name] is not None:
  37.                 label = labels[name] # will there always be a label if it is not none?
  38.                 graph.add_node(name, label)
  39.             else:
  40.                 graph.add_node(name)
  41.  
  42.         # add each edge by considering all nodes of every node's sublist in adj_list
  43.         for name in range(len(adj_list)):
  44.             for neighbor in adj_list[name]:
  45.                 graph.add_edge(name, neighbor)
  46.  
  47.         return graph
  48.  
  49.     def from_dict(self, adj_dict, labels=None):
  50.         """Create and return a new graph instance.
  51.  
  52.        Use a simple adjacency dictionary as source where the `labels`
  53.        dictionary maps each node name its label.
  54.  
  55.        Parameters:
  56.            `adj_dict`: adjacency dictionary representation of a graph
  57.            `labels`: dictionary mapping each node name to its label;
  58.                      by default it's None, which means no label should be set
  59.                      for any of the nodes
  60.  
  61.        Returns:
  62.            new instance of class implementing `Graph`
  63.  
  64.        """
  65.         graph = self.graph_class()
  66.  
  67.         # add each node from the adjacency dict with its corresponding label
  68.         for name in adj_dict:
  69.             if labels is not None and labels[name] is not None:
  70.                 label = labels[name]  # will there always be a label if it is not none?
  71.                 graph.add_node(name, label)
  72.             else:
  73.                 graph.add_node(name)
  74.         # add each edge by considering all nodes of every node's sublist in adj_dict. ***ISNT THIS AN ISSUE FOR COMPACT GRAPH***
  75.         for name in adj_dict:
  76.             for neighbor in adj_dict[name]:
  77.                 graph.add_edge(name, neighbor)
  78.  
  79.         return graph
  80.  
  81.  
  82. class FastGraph(Graph):
  83.     """Faster implementation of `Graph`.
  84.  
  85.    Has extra optimizations for star and clique patterns.
  86.    """
  87.  
  88.     def __init__(self):
  89.         self.adj_dict = {}
  90.         self.labels = {}
  91.         self.cliques = {} # key = clique size, val = set of tuples of cliques of that size. clique tuples are sorted
  92.         self.nscliques = {} # key = node, val = set of tuples of cliques associated w that node. tuples are internally sorted
  93.         # key = node, val = dict: key = clique size, val = list of nodes in clique associated with node
  94.         self.stars = {} # key = central node, val = outdegree
  95.  
  96.     def query(self, pattern):
  97.  
  98.         def is_clique():
  99.             n = len(pattern)
  100.             for i in range(n):
  101.                 if pattern[i][1] != [j for j in range(n) if j != i]:
  102.                     return False
  103.             return n
  104.         def does_a_clique_match_labels(clique):
  105.             # clique = sorted tuple of nodes
  106.             labelsset = set()
  107.             for v in range(len(clique)):
  108.                 labelsset.add(self.labels[v])
  109.             for i in range(len(pattern)):
  110.                 if pattern[i][0] not in labelsset:
  111.                     return False
  112.             return True
  113.         def query_clique(clique, index, nodes=None, assignments=None, usednodes=None): # get the matching(s) already knowing this clique works
  114.             # clique = sorted tuple of nodes
  115.             flag = False
  116.             if not usednodes:
  117.                 usednodes = set()
  118.                 flag = True
  119.             combos = []  # list of all assignment dicts
  120.  
  121.             if assignments is None:
  122.                 assignments = {}
  123.             if nodes is None:
  124.                 nodes = {}
  125.                 for v in clique:
  126.                     nodes[v] = [i for i in clique if i != v]
  127.  
  128.             if index in assignments:
  129.                 if assignments[index] in nodes:
  130.                     return [assignments]
  131.                 else:
  132.                     return []
  133.  
  134.             label = pattern[index][0]
  135.             for node, neighbors in nodes.items():
  136.                 if node not in usednodes:
  137.                     if self.labels[node] == label or label == '*':
  138.                         assignments[index] = node
  139.                         usednodes.add(node)
  140.                     else:
  141.                         continue
  142.                 else:
  143.                     continue
  144.                 if pattern[index][1]: #should def be true for clique pattern
  145.                     for child in pattern[index][1]:
  146.                         assignmentscopy = assignments.copy()
  147.                         new_adj_dict = {}
  148.                         for node in neighbors:
  149.                             new_adj_dict[node] = [i for i in clique if i != node]  # shrink adj dict to only the nodes we want to consider
  150.                         combos += query_clique(clique, child, new_adj_dict, assignmentscopy, usednodes)
  151.                 else:  # if empty list
  152.                     combos.append(assignments.copy())
  153.                 if flag: usednodes = set()  # reset it for next iteration of for loop at same recursion depth (only if it was first call)
  154.             return combos
  155.  
  156.         def step_2_helper(index, nodes=None, assignments=None, usednodes=None): # for non clique queries
  157.             flag = False
  158.             if not usednodes:
  159.                 usednodes = set()
  160.                 flag = True
  161.             combos = [] # list of all assignment dicts
  162.             if nodes is None:
  163.                 nodes = self.adj_dict
  164.             if assignments is None:
  165.                 assignments = {}
  166.             if index in assignments:
  167.                 if assignments[index] in nodes:
  168.                     return [assignments]
  169.                 else:
  170.                     return []
  171.  
  172.             label = pattern[index][0]
  173.             for node, neighbors in nodes.items():
  174.                 if node not in usednodes:
  175.                     # if pattern is a clique:
  176.                     # nodes = self.cliques all cliques for this particular node
  177.                     if self.labels[node] == label or label == '*':
  178.                         assignments[index] = node
  179.                         usednodes.add(node)
  180.                     else:
  181.                         continue
  182.                 else:
  183.                     continue
  184.                 if pattern[index][1]:
  185.                     for child in pattern[index][1]:
  186.                         assignmentscopy = assignments.copy()
  187.                         new_adj_dict = {}
  188.                         for node in neighbors:
  189.                             new_adj_dict[node] = self.adj_dict[node] # shrink adj dict to only the nodes we want to consider
  190.                         combos += step_2_helper(child, new_adj_dict, assignmentscopy, usednodes)
  191.                 else: # if empty list
  192.                     combos.append(assignments.copy())
  193.                 if flag: usednodes = set() #reset it for next iteration of for loop at same recursion depth (only if it was first call)
  194.             return combos
  195.  
  196.  
  197.         def get_proper_results(results):
  198.             formattedresults = []  # results returned in correct format (list of lists)
  199.  
  200.             are_blanks = True
  201.             while are_blanks:
  202.                 are_blanks = False # until we find a blank
  203.                 new_results = []
  204.                 for d in results:
  205.                     if len(d) < len(pattern): # there is at least 1 blank
  206.                         are_blanks = True
  207.                         for i in range(len(pattern)):
  208.                             if i not in d:
  209.                                 usednodes = set()
  210.                                 for k, v in d.items():
  211.                                     usednodes.add(v)
  212.                                 new_results += step_2_helper(i, assignments=d, usednodes=usednodes) #continue the process from dead end (i)
  213.                                 break
  214.                     else:
  215.                         new_results.append(d)
  216.                 results = new_results
  217.             for i, dictionary in enumerate(results):
  218.                 firsttime = True
  219.                 for k, v in dictionary.items():
  220.                     if firsttime:
  221.                         formattedresults.append([v])
  222.                     else:
  223.                         formattedresults[i].append(v)
  224.                     firsttime = False
  225.             return formattedresults
  226.  
  227.         a = is_clique()
  228.         if a:
  229.             final_clique_results = []
  230.             cliques = self.cliques[a]
  231.             for clique in cliques:
  232.                 if does_a_clique_match_labels(clique):
  233.                     results = query_clique(clique, 0)
  234.                     formatted_results = get_proper_results(results)
  235.                     final_clique_results.append(formatted_results)
  236.         else:
  237.             results = step_2_helper(0)
  238.             return get_proper_results(results)
  239.         #todo check for duplicates in results, results having combos that reuse nodes, intersections fix
  240.         #todo for cloques: get all clique subsets of appropirate size and return the ones that match that pattern labels
  241.  
  242.     def add_node(self, name, label=''):
  243.         """Add a node with name `name` and label `label`."""
  244.         if name in self.adj_dict:
  245.             raise ValueError
  246.         else:
  247.             self.adj_dict[name] = []  # add name to adj dict
  248.             self.labels[name] = label  # add corresponding label
  249.         if 1 not in self.cliques:
  250.             self.cliques[1] = {(name)}
  251.         else:
  252.             self.cliques[1].add((name))
  253.  
  254.     def remove_node(self, name):
  255.         """Remove the node with name `name`."""
  256.         if name not in self.adj_dict:
  257.             raise LookupError
  258.  
  259.         else:
  260.             self.adj_dict.pop(name)  # remove name from adj dict
  261.         # remove node from star if it was part of a star
  262.         if name in self.stars: # if it was central node
  263.             self.stars.pop(name)
  264.         for node in self.adj_dict:
  265.             if name in self.adj_dict[node]: # If this node points to a name
  266.                 self.adj_dict[node].remove(name)
  267.             self.stars[name] -= 1 # should exist and be at least 1-legged
  268.             if self.stars[name] == 0: # no longer a center of a star
  269.                 self.stars.pop(name)
  270.  
  271.         # CLIQUES ANALYSIS
  272.         self.nscliques.pop(name)
  273.  
  274.         # remove/readd-name any cliques that had name
  275.         for node, cliques in self.nscliques:
  276.             for clique in cliques:
  277.                 if name in clique:
  278.                     lclique = list(clique)
  279.                     lclique.remove(name)
  280.                     newclique = tuple(sorted(tuple(lclique)))
  281.                     self.nscliques[node].add(newclique)
  282.  
  283.                     a = len(clique)
  284.  
  285.                     # remove clique from its self.cliques dict at its size
  286.                     self.cliques[a].remove(clique)
  287.                     # add clique minus end to self.cliques dict at size-1
  288.                     if a - 1 in self.cliques:
  289.                         self.cliques[a - 1].add(newclique)
  290.  
  291.  
  292.     def add_edge(self, start, end):
  293.         """Add a edge from `start` to `end`."""
  294.         if start not in self.adj_dict or end not in self.adj_dict:
  295.             raise LookupError
  296.         if end in self.adj_dict[start]:
  297.             raise ValueError
  298.         else:
  299.             self.adj_dict[start].append(end)  # add end node to start node's value list
  300.         if start in self.stars: # if star is about to have one more node
  301.             self.stars[start] += 1
  302.         else: # else add a new 1-legged star for that start node
  303.             self.stars[start] = 1
  304.  
  305.         #CLIQUES ANALYSIS
  306.         # if there is an edge from end to start: now undirected, possibility of cliques
  307.         if start in self.adj_dict[end]:
  308.             # if this end is undirectedly connected to all other edges for any associated clique of start
  309.             if start in self.nscliques:
  310.                 for clique in self.nscliques[start]:
  311.                     if clique in self.nscliques[end]:
  312.                         # add this +1 size new clique
  313.                         a = len(clique)
  314.                         new_clique_l = list(clique).append(end)
  315.                         new_clique = tuple(sorted(new_clique))
  316.                         if a not in self.cliques:
  317.                             self.cliques[a+1] = new_clique
  318.                         else:
  319.                             self.cliques[a+1].add(new_clique)
  320.                         self.nscliques[start].add(new_clique)
  321.                         self.nscliques[end].add(new_clique)
  322.  
  323.             # add size 2 clique start,end
  324.             if 2 not in self.cliques:
  325.                 cliquey = (start, end)
  326.                 clique = tuple(sorted(cliquey))
  327.                 self.cliques[2] = {clique}
  328.                 if start in self.nscliques:
  329.                     self.nscliques[start].add(clique)
  330.                     self.nscliques[end].add(clique)
  331.  
  332.  
  333.  
  334.     def remove_edge(self, start, end):
  335.         """Remove the edge from `start` to `end`."""
  336.         if start not in self.adj_dict or end not in self.adj_dict:
  337.             raise LookupError
  338.         if end not in self.adj_dict[start]:
  339.             raise LookupError
  340.         else:
  341.             self.adj_dict[start].remove(end)
  342.         self.stars[start] -= 1 # should exist and be at least 1-legged
  343.         if self.stars[start] == 0:  # no longer a center of a star
  344.             self.stars.pop(start)
  345.  
  346.         # CLIQUES ANALYSIS
  347.         if start in self.nscliques:
  348.             for clique in self.nscliques[start]:
  349.                 if end in clique:
  350.                     #reomve that clique, add the same one without end in it
  351.                     self.nscliques[start].remove(clique)
  352.                     lclique = list(clique)
  353.                     lclique.remove(end)
  354.                     newclique = tuple(sorted(tuple(lclique)))
  355.                     self.nscliques[start].add(newclique)
  356.  
  357.                     a = len(clique)
  358.  
  359.                     # remove clique from its self.cliques dict at its size
  360.                     self.cliques[a].remove(clique)
  361.                     # add clique minus end to self.cliques dict at size-1
  362.                     if a - 1 in self.cliques:
  363.                         self.cliques[a - 1].add(newclique)
  364.  
  365.         if end in self.nscliques:
  366.             for clique in self.nscliques[end]:
  367.                 if start in clique:
  368.                     #reomve that clique, add the same one without start in it
  369.                     self.nscliques[end].remove(clique)
  370.                     lclique = list(clique)
  371.                     lclique.remove(start)
  372.                     newclique = tuple(sorted(tuple(lclique)))
  373.                     self.nscliques[end].add(newclique)
  374.                     a = len(clique)
  375.  
  376.                     #remove clique from its self.cliques dict at its size
  377.                     self.cliques[a].remove(clique)
  378.                     # add clique minus start to self.cliques dict at size-1
  379.                     if a-1 in self.cliques:
  380.                         self.cliques[a-1].add(newclique)
  381.  
  382. if __name__ == '__main__':
  383.     # factory = GraphFactory(FastGraph)
  384.     # adj = [[1, 3], [2], [0], []]
  385.     # labels = {0: "a", 1: "a", 2: "a", 3: "b"}
  386.     # graph = factory.from_list(adj, labels=labels)
  387.     #
  388.     #
  389.     # # All nodes.
  390.     # pattern = [('*', [1, 2]), ('*', []), ('*', [])]
  391.     # expected = [[0, 1, 3], [0, 3, 1]]
  392.     # a = graph.query(pattern)
  393.     # print(a)
  394.  
  395.     def make_clique_pattern(n, labels=None):
  396.         labels = labels or ["*" for i in range(n)]
  397.         return [(labels[i], [j for j in range(n) if j != i])
  398.                 for i in range(n)]
  399.     p = make_clique_pattern(5)
  400.  
  401.  
  402.     def is_clique(pattern):
  403.         n = len(pattern)
  404.         for i in range(n):
  405.             if pattern[i][1] != [j for j in range(n) if j != i]:
  406.                 return False
  407.         return n
  408.  
  409.  
  410.     def does_a_clique_match_labels(clique, pattern, labels):
  411.         # clique = sorted tuple of nodes
  412.         labelsset = set()
  413.         for v in clique:
  414.             labelsset.add(labels[v])
  415.         for i in range(len(pattern)):
  416.             if pattern[i][0] not in labelsset:
  417.                 return False
  418.         return True
  419.     l = {'a': 'yo', 'b': 'yo', 'c': 'sup'}
  420.     labels = ['hi', 'yo', 'sup']
  421.     clique = ('a', 'b', 'c')
  422.     pattern = make_clique_pattern(3, labels)
  423.     print(pattern)
  424.     a = does_a_clique_match_labels(clique, pattern, l)
  425.     print(a)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement