<gh_stars>0
"""Gathers together a collection of helper functions and classes that the library needs,
but end users won't care about."""

import copy

from ..classes import UndirectedGraph, DirectedGraph


# Graph Conversions

def make_subgraph(graph, vertices, edges):
    """Converts a subgraph given by a list of vertices and edges into a graph object."""
    # Copy the entire graph
    local_graph = copy.deepcopy(graph)

    # Remove all the edges that aren't in the list
    edges_to_delete = [x for x in local_graph.get_all_edge_ids() if x not in edges]
    for e in edges_to_delete:
        local_graph.delete_edge_by_id(e)

    # Remove all the vertices that aren't in the list
    nodes_to_delete = [x for x in local_graph.get_all_node_ids() if x not in vertices]
    for n in nodes_to_delete:
        local_graph.delete_node(n)

    return local_graph


def convert_graph_directed_to_undirected(dg):
    """Converts a directed graph into an undirected graph. Directed edges are made undirected."""

    udg = UndirectedGraph()

    # Copy the graph
    # --Copy nodes
    # --Copy edges
    udg.nodes = copy.deepcopy(dg.nodes)
    udg.edges = copy.deepcopy(dg.edges)
    udg.next_node_id = dg.next_node_id
    udg.next_edge_id = dg.next_edge_id
    udg.num_nodes = dg.num_nodes

    # using naive method 
    # to remove duplicated  
    # from list  
    udg.edges = {} 
    udg_edges_vertices_sorted = []
    for e_id, e_value in dg.edges.items(): 
        if sorted(e_value['vertices']) not in udg_edges_vertices_sorted:
            udg_edges_vertices_sorted.append(sorted(e_value['vertices']))
            udg.edges[e_id] = e_value 

    # Clear old edge ids from nodes
    for n_id, n_val in udg.nodes.items():
        n_val['edges'] = []
        udg.nodes[n_id] = n_val

    # Convert the directed edges into undirected edges
    for edge_id in udg.get_all_edge_ids():
        edge = udg.get_edge(edge_id)
        source_node_id = edge['vertices'][0]
        source_node = udg.get_node(source_node_id)
        source_node['edges'].append(edge_id)
        target_node_id = edge['vertices'][1]
        target_node = udg.get_node(target_node_id)
        target_node['edges'].append(edge_id)
    udg.num_edges = len(udg.get_all_edge_ids())

    return udg


def remove_duplicate_edges_directed(dg):
    """Removes duplicate edges from a directed graph."""
    # With directed edges, we can just hash the to and from node id tuples and if
    # a node happens to conflict with one that already exists, we delete it

    # --For aesthetic, we sort the edge ids so that lower edge ids are kept
    lookup = {}
    edges = sorted(dg.get_all_edge_ids())
    for edge_id in edges:
        e = dg.get_edge(edge_id)
        tpl = e['vertices']
        if tpl in lookup:
            dg.delete_edge_by_id(edge_id)
        else:
            lookup[tpl] = edge_id


def remove_duplicate_edges_undirected(udg):
    """Removes duplicate edges from an undirected graph."""
    # With undirected edges, we need to hash both combinations of the to-from node ids, since a-b and b-a are equivalent
    # --For aesthetic, we sort the edge ids so that lower edges ids are kept
    lookup = {}
    edges = sorted(udg.get_all_edge_ids())
    for edge_id in edges:
        e = udg.get_edge(edge_id)
        tpl_a = e['vertices']
        tpl_b = (tpl_a[1], tpl_a[0])
        if tpl_a in lookup or tpl_b in lookup:
            udg.delete_edge_by_id(edge_id)
        else:
            lookup[tpl_a] = edge_id
            lookup[tpl_b] = edge_id


def get_vertices_from_edge_list(graph, edge_list):
    """Transforms a list of edges into a list of the nodes those edges connect.
    Returns a list of nodes, or an empty list if given an empty list.
    """
    node_set = set()
    for edge_id in edge_list:
        edge = graph.get_edge(edge_id)
        a, b = edge['vertices']
        node_set.add(a)
        node_set.add(b)

    return list(node_set)


def get_subgraph_from_edge_list(graph, edge_list):
    """Transforms a list of edges into a subgraph."""
    node_list = get_vertices_from_edge_list(graph, edge_list)
    subgraph = make_subgraph(graph, node_list, edge_list)

    return subgraph


def merge_graphs(main_graph, addition_graph):
    """Merges an ''addition_graph'' into the ''main_graph''.
    Returns a tuple of dictionaries, mapping old node ids and edge ids to new ids.
    """

    node_mapping = {}
    edge_mapping = {}

    for node in addition_graph.get_all_node_objects():
        node_id = node['id']
        new_id = main_graph.new_node()
        node_mapping[node_id] = new_id

    for edge in addition_graph.get_all_edge_objects():
        edge_id = edge['id']
        old_vertex_a_id, old_vertex_b_id = edge['vertices']
        new_vertex_a_id = node_mapping[old_vertex_a_id]
        new_vertex_b_id = node_mapping[old_vertex_b_id]
        new_edge_id = main_graph.new_edge(new_vertex_a_id, new_vertex_b_id)
        edge_mapping[edge_id] = new_edge_id

    return node_mapping, edge_mapping


def create_graph_from_adjacency_matrix(adjacency_matrix):
    """Generates a graph from an adjacency matrix specification.
       Returns a tuple containing the graph and a list-mapping of node ids to matrix column indices.

       The graph will be an UndirectedGraph if the provided adjacency matrix is symmetric.
       The graph will be a DirectedGraph if the provided adjacency matrix is not symmetric.
       Ref: http://mathworld.wolfram.com/AdjacencyMatrix.html"""
    if is_adjacency_matrix_symmetric(adjacency_matrix):
        graph = UndirectedGraph()
    else:
        graph = DirectedGraph()

    node_column_mapping = []

    num_columns = len(adjacency_matrix)
    for _ in range(num_columns):
        node_id = graph.new_node()
        node_column_mapping.append(node_id)

    for j in range(num_columns):
        for i in range(num_columns):
            if adjacency_matrix[j][i]:
                jnode_id = node_column_mapping[j]
                inode_id = node_column_mapping[i]
                # Because of our adjacency matrix encoding, [j][i] in our code corresponds to [i][j] in a traditional matrix interpretation
                # Thus, we need to put an edge from node i to node j if [j][i] in our code is non-zero
                graph.new_edge(inode_id, jnode_id)

    return (graph, node_column_mapping)


def is_adjacency_matrix_symmetric(adjacency_matrix):
    """Determines if an adjacency matrix is symmetric.
       Ref: http://mathworld.wolfram.com/SymmetricMatrix.html"""
    # Verify that the matrix is square
    num_columns = len(adjacency_matrix)
    for column in adjacency_matrix:
        # In a square matrix, every row should be the same length as the number of columns
        if len(column) != num_columns:
            return False

    # Loop through the bottom half of the matrix and compare it to the top half
    # --We do the bottom half because of how we construct adjacency matrices
    max_i = 0
    for j in range(num_columns):
        for i in range(max_i):
            # If i == j, we can skip ahead so we don't compare with ourself
            if i == j:
                continue
            # Compare the value in the bottom half with the mirrored value in the top half
            # If they aren't the same, the matrix isn't symmetric
            if adjacency_matrix[j][i] != adjacency_matrix[i][j]:
                return False
        max_i += 1

    # If we reach this far without returning false, then we know that everything matched,
    # which makes this a symmetric matrix
    return True


"""Gathers together a collection of helper functions and classes that the library needs,
but end users won't care about."""

import copy

from ..classes import UndirectedGraph, DirectedGraph


# Graph Conversions

def make_subgraph(graph, vertices, edges):
    """Converts a subgraph given by a list of vertices and edges into a graph object."""
    # Copy the entire graph
    local_graph = copy.deepcopy(graph)

    # Remove all the edges that aren't in the list
    edges_to_delete = [x for x in local_graph.get_all_edge_ids() if x not in edges]
    for e in edges_to_delete:
        local_graph.delete_edge_by_id(e)

    # Remove all the vertices that aren't in the list
    nodes_to_delete = [x for x in local_graph.get_all_node_ids() if x not in vertices]
    for n in nodes_to_delete:
        local_graph.delete_node(n)

    return local_graph


def convert_graph_directed_to_undirected(dg):
    """Converts a directed graph into an undirected graph. Directed edges are made undirected."""

    udg = UndirectedGraph()

    # Copy the graph
    # --Copy nodes
    # --Copy edges
    udg.nodes = copy.deepcopy(dg.nodes)
    udg.edges = copy.deepcopy(dg.edges)
    udg.next_node_id = dg.next_node_id
    udg.next_edge_id = dg.next_edge_id
    udg.num_nodes = dg.num_nodes

    # using naive method 
    # to remove duplicated  
    # from list  
    udg.edges = {} 
    udg_edges_vertices_sorted = []
    for e_id, e_value in dg.edges.items(): 
        if sorted(e_value['vertices']) not in udg_edges_vertices_sorted:
            udg_edges_vertices_sorted.append(sorted(e_value['vertices']))
            udg.edges[e_id] = e_value 

    # Clear old edge ids from nodes
    for n_id, n_val in udg.nodes.items():
        n_val['edges'] = []
        udg.nodes[n_id] = n_val

    # Convert the directed edges into undirected edges
    for edge_id in udg.get_all_edge_ids():
        edge = udg.get_edge(edge_id)
        source_node_id = edge['vertices'][0]
        source_node = udg.get_node(source_node_id)
        source_node['edges'].append(edge_id)
        target_node_id = edge['vertices'][1]
        target_node = udg.get_node(target_node_id)
        target_node['edges'].append(edge_id)
    udg.num_edges = len(udg.get_all_edge_ids())

    return udg


def remove_duplicate_edges_directed(dg):
    """Removes duplicate edges from a directed graph."""
    # With directed edges, we can just hash the to and from node id tuples and if
    # a node happens to conflict with one that already exists, we delete it

    # --For aesthetic, we sort the edge ids so that lower edge ids are kept
    lookup = {}
    edges = sorted(dg.get_all_edge_ids())
    for edge_id in edges:
        e = dg.get_edge(edge_id)
        tpl = e['vertices']
        if tpl in lookup:
            dg.delete_edge_by_id(edge_id)
        else:
            lookup[tpl] = edge_id


def remove_duplicate_edges_undirected(udg):
    """Removes duplicate edges from an undirected graph."""
    # With undirected edges, we need to hash both combinations of the to-from node ids, since a-b and b-a are equivalent
    # --For aesthetic, we sort the edge ids so that lower edges ids are kept
    lookup = {}
    edges = sorted(udg.get_all_edge_ids())
    for edge_id in edges:
        e = udg.get_edge(edge_id)
        tpl_a = e['vertices']
        tpl_b = (tpl_a[1], tpl_a[0])
        if tpl_a in lookup or tpl_b in lookup:
            udg.delete_edge_by_id(edge_id)
        else:
            lookup[tpl_a] = edge_id
            lookup[tpl_b] = edge_id


def get_vertices_from_edge_list(graph, edge_list):
    """Transforms a list of edges into a list of the nodes those edges connect.
    Returns a list of nodes, or an empty list if given an empty list.
    """
    node_set = set()
    for edge_id in edge_list:
        edge = graph.get_edge(edge_id)
        a, b = edge['vertices']
        node_set.add(a)
        node_set.add(b)

    return list(node_set)


def get_subgraph_from_edge_list(graph, edge_list):
    """Transforms a list of edges into a subgraph."""
    node_list = get_vertices_from_edge_list(graph, edge_list)
    subgraph = make_subgraph(graph, node_list, edge_list)

    return subgraph


def merge_graphs(main_graph, addition_graph):
    """Merges an ''addition_graph'' into the ''main_graph''.
    Returns a tuple of dictionaries, mapping old node ids and edge ids to new ids.
    """

    node_mapping = {}
    edge_mapping = {}

    for node in addition_graph.get_all_node_objects():
        node_id = node['id']
        new_id = main_graph.new_node()
        node_mapping[node_id] = new_id

    for edge in addition_graph.get_all_edge_objects():
        edge_id = edge['id']
        old_vertex_a_id, old_vertex_b_id = edge['vertices']
        new_vertex_a_id = node_mapping[old_vertex_a_id]
        new_vertex_b_id = node_mapping[old_vertex_b_id]
        new_edge_id = main_graph.new_edge(new_vertex_a_id, new_vertex_b_id)
        edge_mapping[edge_id] = new_edge_id

    return node_mapping, edge_mapping


def create_graph_from_adjacency_matrix(adjacency_matrix):
    """Generates a graph from an adjacency matrix specification.
       Returns a tuple containing the graph and a list-mapping of node ids to matrix column indices.

       The graph will be an UndirectedGraph if the provided adjacency matrix is symmetric.
       The graph will be a DirectedGraph if the provided adjacency matrix is not symmetric.
       Ref: http://mathworld.wolfram.com/AdjacencyMatrix.html"""
    if is_adjacency_matrix_symmetric(adjacency_matrix):
        graph = UndirectedGraph()
    else:
        graph = DirectedGraph()

    node_column_mapping = []

    num_columns = len(adjacency_matrix)
    for _ in range(num_columns):
        node_id = graph.new_node()
        node_column_mapping.append(node_id)

    for j in range(num_columns):
        for i in range(num_columns):
            if adjacency_matrix[j][i]:
                jnode_id = node_column_mapping[j]
                inode_id = node_column_mapping[i]
                # Because of our adjacency matrix encoding, [j][i] in our code corresponds to [i][j] in a traditional matrix interpretation
                # Thus, we need to put an edge from node i to node j if [j][i] in our code is non-zero
                graph.new_edge(inode_id, jnode_id)

    return (graph, node_column_mapping)


def is_adjacency_matrix_symmetric(adjacency_matrix):
    """Determines if an adjacency matrix is symmetric.
       Ref: http://mathworld.wolfram.com/SymmetricMatrix.html"""
    # Verify that the matrix is square
    num_columns = len(adjacency_matrix)
    for column in adjacency_matrix:
        # In a square matrix, every row should be the same length as the number of columns
        if len(column) != num_columns:
            return False

    # Loop through the bottom half of the matrix and compare it to the top half
    # --We do the bottom half because of how we construct adjacency matrices
    max_i = 0
    for j in range(num_columns):
        for i in range(max_i):
            # If i == j, we can skip ahead so we don't compare with ourself
            if i == j:
                continue
            # Compare the value in the bottom half with the mirrored value in the top half
            # If they aren't the same, the matrix isn't symmetric
            if adjacency_matrix[j][i] != adjacency_matrix[i][j]:
                return False
        max_i += 1

    # If we reach this far without returning false, then we know that everything matched,
    # which makes this a symmetric matrix
    return True
Gathers together a collection of helper functions and classes that the library needs, but end users won't care about. # Graph Conversions Converts a subgraph given by a list of vertices and edges into a graph object. # Copy the entire graph # Remove all the edges that aren't in the list # Remove all the vertices that aren't in the list Converts a directed graph into an undirected graph. Directed edges are made undirected. # Copy the graph # --Copy nodes # --Copy edges # using naive method # to remove duplicated # from list # Clear old edge ids from nodes # Convert the directed edges into undirected edges Removes duplicate edges from a directed graph. # With directed edges, we can just hash the to and from node id tuples and if # a node happens to conflict with one that already exists, we delete it # --For aesthetic, we sort the edge ids so that lower edge ids are kept Removes duplicate edges from an undirected graph. # With undirected edges, we need to hash both combinations of the to-from node ids, since a-b and b-a are equivalent # --For aesthetic, we sort the edge ids so that lower edges ids are kept Transforms a list of edges into a list of the nodes those edges connect.     Returns a list of nodes, or an empty list if given an empty list. Transforms a list of edges into a subgraph. Merges an ''addition_graph'' into the ''main_graph''.     Returns a tuple of dictionaries, mapping old node ids and edge ids to new ids. Generates a graph from an adjacency matrix specification.        Returns a tuple containing the graph and a list-mapping of node ids to matrix column indices.         The graph will be an UndirectedGraph if the provided adjacency matrix is symmetric.        The graph will be a DirectedGraph if the provided adjacency matrix is not symmetric.        Ref: http://mathworld.wolfram.com/AdjacencyMatrix.html # Because of our adjacency matrix encoding, [j][i] in our code corresponds to [i][j] in a traditional matrix interpretation # Thus, we need to put an edge from node i to node j if [j][i] in our code is non-zero Determines if an adjacency matrix is symmetric.        Ref: http://mathworld.wolfram.com/SymmetricMatrix.html # Verify that the matrix is square # In a square matrix, every row should be the same length as the number of columns # Loop through the bottom half of the matrix and compare it to the top half # --We do the bottom half because of how we construct adjacency matrices # If i == j, we can skip ahead so we don't compare with ourself # Compare the value in the bottom half with the mirrored value in the top half # If they aren't the same, the matrix isn't symmetric # If we reach this far without returning false, then we know that everything matched, # which makes this a symmetric matrix