def nfa_determinization(nfa: dict) -> dict:
    """ Returns a DFA that reads the same language of the input NFA.

    Let A be an NFA, then there exists a DFA :math:`A_d` such
    that :math:`L(A_d) = L(A)`. Intuitively, :math:`A_d`
    collapses all possible runs of A on a given input word into
    one run over a larger state set.
    :math:`A_d` is defined as:

    :math:`A_d = (Σ, 2^S , s_0 , ρ_d , F_d )`

    where:

    • :math:`2^S` , i.e., the state set of :math:`A_d` , consists
      of all sets of states S in A;
    • :math:`s_0 = S^0` , i.e., the single initial state of
      :math:`A_d` is the set :math:`S_0` of initial states of A;
    • :math:`F_d = \{Q | Q ∩ F ≠ ∅\}`, i.e., the collection of
      sets of states that intersect F nontrivially;
    • :math:`ρ_d(Q, a) = \{s' | (s,a, s' ) ∈ ρ\ for\ some\ s ∈ Q\}`.

    :param dict nfa: input NFA.
    :return: *(dict)* representing a DFA
    """
    def state_name(s):
        return str(set(sorted(s)))

    dfa = {
        'alphabet': nfa['alphabet'].copy(),
        'initial_state': None,
        'states': set(),
        'accepting_states': set(),
        'transitions': dict()
    }

    if len(nfa['initial_states']) > 0:
        dfa['initial_state'] = state_name(nfa['initial_states'])
        dfa['states'].add(state_name(nfa['initial_states']))

    sets_states = list()
    sets_queue = list()
    sets_queue.append(nfa['initial_states'])
    sets_states.append(nfa['initial_states'])
    if len(sets_states[0].intersection(nfa['accepting_states'])) > 0:
        dfa['accepting_states'].add(state_name(sets_states[0]))

    while sets_queue:
        current_set = sets_queue.pop(0)
        for a in dfa['alphabet']:
            next_set = set()
            for state in current_set:
                if (state, a) in nfa['transitions']:
                    for next_state in nfa['transitions'][state, a]:
                        next_set.add(next_state)
            if len(next_set) == 0:
                continue
            if next_set not in sets_states:
                sets_states.append(next_set)
                sets_queue.append(next_set)
                dfa['states'].add(state_name(next_set))
                if next_set.intersection(nfa['accepting_states']):
                    dfa['accepting_states'].add(state_name(next_set))

            dfa['transitions'][state_name(current_set), a] = state_name(next_set)

    return dfa