def countComponents3(n: int, edges: list[list[int]]) -> int:
    """
    union by rank based implementation

    Args:
        n (int): [description]
        edges (list[list[int]]): [description]

    Returns:
        int: number of connected components
    """
    connections = [idx for idx in range(n)]
    rank = [1] * n

    def get_group(node : int):

        while node != connections[node]:
            node = connections[node]

        return node

    def connect(u, v) -> None:
        
        gu, gv = get_group(u), get_group(v)

        if gu != gv:

            if rank[gu] > rank[gv]:
                connections[gv] = gu
            elif rank[gv] > rank[gu]:
                connections[gv] = gu
            else:
                connections[gv] = gu
                rank[v] = rank[v] + 1

    for edge in edges:
        u, v = edge[0], edge[1]
        connect(u, v)

    components = set()

    for node in range(n):
        components.add(get_group(node))

    return len(components)