docs: def determinize(self):
          """
          Transforms a Non Deterministic DFA into a Deterministic
          Args:
              None
          Returns:
              DFA: The resulting DFA
  
          Creating an equivalent DFA is done using the standard algorithm.
          A nice description can be found in the book:
          Harry R. Lewis and Christos H. Papadimitriou. 1998.
          E
          print target_dfa_statelements of the Theory of Computation.
          """
  
          # Compute the \epsilon-closure for all states and save it in a diagram
          epsilon_closure = {}
          for state in self.states:
              sid = state.stateid
              epsilon_closure[sid] = self._epsilon_closure(state)
  
          # Get a transition diagram to speed up computations
          trans_table = {}
          for state in self.states:
              trans_table[state.stateid] = defaultdict(set)
              for arc in state:
                  char = self.isyms.find(arc.ilabel)
                  trans_table[state.stateid][char].add(arc.nextstate)
  
          # is_final function:
          # Given a set of nfa states representing a dfa_state return 1 if the
          # corresponding DFA state is a final state, i.e. if any of the
          # corresponding NFA states are final.
          is_final = lambda nfa_states, dfa_state: True \
              if sum([ int(nfa_states[x].final) for x in dfa_state ]) >= 1 \
              else False
  
          # Precomputation is over, start executing the conversion algorithm
          state_idx = 1
          nfa_states = copy.deepcopy(self.states)
          self.states = []
          # Initialize the new DFA state list
          self.add_state()
          new_initial = epsilon_closure[nfa_states[0].stateid]
          self.states[0].final = is_final(nfa_states, new_initial)
  
          dfa_state_idx_map = { frozenset(new_initial) : 0 }
          stack = [new_initial]
          while True:
              # Iterate until all added DFA states are processed.
              if not stack:
                  break
              # This is a set of states from the NFA
              src_dfa_state = stack.pop()
              src_dfa_state_idx = dfa_state_idx_map[frozenset(src_dfa_state)]
              for char in self.alphabet:
                  # Compute the set of target states
                  target_dfa_state = set([])
                  for nfa_state in src_dfa_state:
                      next_states = \
                          set([y for x in trans_table[nfa_state][char] \
                               for y in epsilon_closure[x] ])
                      target_dfa_state.update(next_states)
                  # If the computed state set is not part of our new DFA add it,
                  # along with the transition for the current character.
                  if frozenset(target_dfa_state) not in dfa_state_idx_map:
                      self.add_state()
                      dfa_state_idx_map[frozenset(target_dfa_state)] = state_idx
                      self.states[state_idx].final = is_final(nfa_states,
                                                              target_dfa_state)
                      state_idx += 1
                      stack.append(target_dfa_state)
  
                  dst_state_idx = dfa_state_idx_map[frozenset(target_dfa_state)]
                  self.add_arc(src_dfa_state_idx, dst_state_idx, char)
          return self