Code editing task: adjacency
Topic: DSA; change kind: adaptive

Original code:
from typing import List


class Node:
    '''Simple node (No duplicate edges between nodes)'''

    def __init__(self, id: int):
        self.id = id
        self.out_edges = []
        self.in_edges = []

    def __eq__(self, __value: object) -> bool:
        if not isinstance(__value, Node):
            return False
        else:
            return self.id == __value.id

    def __hash__(self) -> int:
        return self.id


class Graph:
    '''Simple directed graph (No duplicate edges between nodes, no duplicate nodes)'''

    def __init__(self, nodes: List[Node]):
        uniques = {}
        for node in nodes:
            if node in uniques.keys():
                raise RuntimeError
            else:
                uniques[node] = True
        self.nodes = nodes

    def add_edge(self, src: Node, dest: Node):
        assert src not in dest.in_edges
        assert dest not in src.out_edges
        src.out_edges.append(dest)
        dest.in_edges.append(src)

    def reverse_edges(self):
        reversed = Graph(list(map(lambda x: Node(x.id), self.nodes)))
        for i, node in enumerate(self.nodes):
            reversed.nodes[i].in_edges = node.out_edges
            reversed.nodes[i].out_edges = node.in_edges
        return reversed

    def DFS(self, src: Node) -> List[Node]:
        assert src in self.nodes

        visited = []
        to_visit = []
        to_visit.append(src)

        while len(to_visit) != 0:
            first = to_visit.pop()
            if first in visited:
                continue
            for n in first.out_edges:
                to_visit.append(n)

            visited.append(first)

        return visited

Instruction (descriptive):
Add a function `adjacency_list(self) -> Dict[Node, List[Node]]` which returns the adjacency list of the graph by returning a dictionary where the keys are `Node` and the values are a list of `Node` which represent the nodes which can be reached from this one in one step.
The output dictionary should contain all nodes in the graph and only those nodes.

Instruction (lazy):
Add a function `adjacency_list(self) -> Dict[Node, List[Node]]` which returns the adjacency list of the graph by returning a dictionary which associates a `Node` to its list of out edges.

Edited code:
from typing import List, Dict


class Node:
    '''Simple node (No duplicate edges between nodes)'''

    def __init__(self, id: int):
        self.id = id
        self.out_edges = []
        self.in_edges = []

    def __eq__(self, __value: object) -> bool:
        if not isinstance(__value, Node):
            return False
        else:
            return self.id == __value.id

    def __hash__(self) -> int:
        return self.id


class Graph:
    '''Simple directed graph (No duplicate edges between nodes, no duplicate nodes)'''

    def __init__(self, nodes: List[Node]):
        uniques = {}
        for node in nodes:
            if node in uniques.keys():
                raise RuntimeError
            else:
                uniques[node] = True
        self.nodes = nodes

    def add_edge(self, src: Node, dest: Node):
        assert src not in dest.in_edges
        assert dest not in src.out_edges
        src.out_edges.append(dest)
        dest.in_edges.append(src)

    def reverse_edges(self):
        reversed = Graph(list(map(lambda x: Node(x.id), self.nodes)))
        for i, node in enumerate(self.nodes):
            reversed.nodes[i].in_edges = node.out_edges
            reversed.nodes[i].out_edges = node.in_edges
        return reversed

    def DFS(self, src: Node) -> List[Node]:
        assert src in self.nodes

        visited = []
        to_visit = []
        to_visit.append(src)

        while len(to_visit) != 0:
            first = to_visit.pop()
            if first in visited:
                continue
            for n in first.out_edges:
                to_visit.append(n)

            visited.append(first)

        return visited

    def adjacency_list(self) -> Dict[Node, List[Node]]:
        output = {}
        for node in self.nodes:
            output[node] = node.out_edges
        return output

Tests:
### START TESTS ###
if True:  # pragma: no cover
    n1_dup = Node(1)
    n1 = Node(1)
    n2 = Node(2)
    n3 = Node(3)
    n4 = Node(4)

    g = Graph([n1, n2, n3, n4])

    g.add_edge(n1, n2)
    g.add_edge(n2, n3)
    g.add_edge(n3, n1)

    reversed = g.reverse_edges()
    adjacencies = g.adjacency_list()

    assert n1 == n1_dup
    assert hash(n1) == 1
    assert hash(n2) == 2

    try:
        Graph(n1, n1_dup)
        assert False
    except:
        assert True

    assert len(n1.out_edges) == 1
    assert n1.out_edges[0] == n2
    assert len(n1.in_edges) == 1
    assert n1.in_edges[0] == n3

    assert len(reversed.nodes[0].in_edges) == 1
    assert len(reversed.nodes[0].out_edges) == 1
    assert reversed.nodes[0].in_edges[0] == n2
    assert reversed.nodes[0].out_edges[0] == n3

    assert n4 in g.DFS(n4)
    assert n1 in g.DFS(n1)
    assert n2 in g.DFS(n1)
    assert n3 in g.DFS(n3)

    assert n1 in g.adjacency_list().keys()
    assert n2 in g.adjacency_list().keys()
    assert n3 in g.adjacency_list().keys()
    assert n4 in g.adjacency_list().keys()

    assert n2 in adjacencies[n1]
    assert n3 in adjacencies[n2]
    assert n1 in adjacencies[n3]

    assert len(adjacencies[n4]) == 0
    assert len(adjacencies[n1]) == 1
    assert len(adjacencies[n2]) == 1
    assert len(adjacencies[n3]) == 1

    assert Node(1) == Node(1)
    assert Node(1) != Node(2)
    assert Node(1) != 1

    try:
        Graph([Node(1), Node(1)])
        assert False
    except RuntimeError:
        assert True