Skip to content

Commit bd3daba

Browse files
committed
add checks for any duplicates, add test for duplicate generation
1 parent deed143 commit bd3daba

File tree

4 files changed

+74
-11
lines changed

4 files changed

+74
-11
lines changed

pyformlang/finite_automaton/finite_automaton.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -602,29 +602,29 @@ def get_accepted_words(self, max_length: Optional[int] = None) \
602602
"""
603603
if max_length is not None and max_length < 0:
604604
return []
605-
states_to_visit = deque((start_state, start_state, [])
605+
states_to_visit = deque((start_state, [])
606606
for start_state in self.start_states)
607607
states_leading_to_final = self._get_states_leading_to_final()
608+
words_by_state = {state: set() for state in self.states}
609+
yielded_words = set()
608610
while states_to_visit:
609-
last_state_before_epsilon, current_state, current_word = \
610-
states_to_visit.popleft()
611+
current_state, current_word = states_to_visit.popleft()
611612
if max_length is not None and len(current_word) > max_length:
612613
continue
614+
word_to_add = tuple(current_word)
615+
if not self.__try_add(words_by_state[current_state], word_to_add):
616+
continue
613617
transitions = self._transition_function.get_transitions_from(
614618
current_state)
615619
for symbol, next_state in transitions:
616-
if symbol == Epsilon() \
617-
and next_state == last_state_before_epsilon:
618-
continue # avoiding epsilon cycles
619620
if next_state in states_leading_to_final:
620-
temp_state = last_state_before_epsilon
621621
temp_word = current_word.copy()
622622
if symbol != Epsilon():
623-
temp_state = next_state
624623
temp_word.append(symbol)
625-
states_to_visit.append((temp_state, next_state, temp_word))
624+
states_to_visit.append((next_state, temp_word))
626625
if self.is_final_state(current_state):
627-
yield current_word
626+
if self.__try_add(yielded_words, word_to_add):
627+
yield current_word
628628

629629
def _get_states_leading_to_final(self) -> Set[State]:
630630
"""
@@ -713,6 +713,16 @@ def to_dict(self):
713713
"""
714714
return self._transition_function.to_dict()
715715

716+
@staticmethod
717+
def __try_add(set_to_add_to: Set[Any], element_to_add: Any) -> bool:
718+
"""
719+
Tries to add a given element to the given set.
720+
Returns True if element was added, otherwise False.
721+
"""
722+
initial_length = len(set_to_add_to)
723+
set_to_add_to.add(element_to_add)
724+
return len(set_to_add_to) != initial_length
725+
716726

717727
def to_state(given: Any) -> Union[State, None]:
718728
""" Transforms the input into a state

pyformlang/finite_automaton/tests/test_deterministic_finite_automaton.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ def test_word_generation(self):
288288
assert [Symbol("b"), Symbol("d")] in accepted_words
289289
assert len(accepted_words) == 3
290290

291+
def test_dfa_generating_no_words(self):
292+
dfa = get_dfa_example_without_accepted_words()
293+
accepted_words = list(dfa.get_accepted_words())
294+
assert not accepted_words
295+
291296

292297
def get_example0():
293298
""" Gives a dfa """
@@ -355,3 +360,18 @@ def get_dfa_example_for_word_generation():
355360
dfa.add_final_state(states[0])
356361
dfa.add_final_state(states[3])
357362
return dfa
363+
364+
365+
def get_dfa_example_without_accepted_words():
366+
""" DFA example accepting no words """
367+
dfa = DeterministicFiniteAutomaton()
368+
states = [State(x) for x in range(4)]
369+
symbol_a = Symbol("a")
370+
symbol_b = Symbol("b")
371+
dfa.add_transitions([
372+
(states[0], symbol_a, states[1]),
373+
(states[2], symbol_b, states[3]),
374+
])
375+
dfa.add_start_state(states[0])
376+
dfa.add_final_state(states[3])
377+
return dfa

pyformlang/finite_automaton/tests/test_epsilon_nfa.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ def test_max_length_zero_accepting_empty_string(self):
661661
def test_max_length_zero_not_accepting_empty_string(self):
662662
enfa = get_cyclic_enfa_example()
663663
accepted_words = list(enfa.get_accepted_words(0))
664-
assert accepted_words == []
664+
assert not accepted_words
665665

666666

667667
def get_digits_enfa():

pyformlang/finite_automaton/tests/test_nondeterministic_finite_automaton.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ def test_word_generation(self):
126126
assert [Symbol("d"), Symbol("e"), Symbol("f")] in accepted_words
127127
assert len(accepted_words) == 5
128128

129+
def test_for_duplicate_generation(self):
130+
nfa = get_nfa_example_with_duplicates()
131+
accepted_words = list(nfa.get_accepted_words())
132+
assert [Symbol("a"), Symbol("c")] in accepted_words
133+
assert [Symbol("b"), Symbol("c")] in accepted_words
134+
assert len(accepted_words) == 2
135+
129136

130137
def get_nfa_example_for_word_generation():
131138
"""
@@ -158,3 +165,29 @@ def get_nfa_example_for_word_generation():
158165
nfa.add_final_state(states[6])
159166
nfa.add_final_state(states[8])
160167
return nfa
168+
169+
170+
def get_nfa_example_with_duplicates():
171+
""" Gets NFA example with duplicate word chains """
172+
nfa = NondeterministicFiniteAutomaton()
173+
states = [State(x) for x in range(9)]
174+
symbol_a = Symbol("a")
175+
symbol_b = Symbol("b")
176+
symbol_c = Symbol("c")
177+
nfa.add_transitions([
178+
(states[0], symbol_a, states[2]),
179+
(states[1], symbol_a, states[2]),
180+
(states[2], symbol_c, states[3]),
181+
(states[2], symbol_c, states[4]),
182+
(states[5], symbol_a, states[7]),
183+
(states[6], symbol_b, states[7]),
184+
(states[7], symbol_c, states[8]),
185+
])
186+
nfa.add_start_state(states[0])
187+
nfa.add_start_state(states[1])
188+
nfa.add_start_state(states[5])
189+
nfa.add_start_state(states[6])
190+
nfa.add_final_state(states[3])
191+
nfa.add_final_state(states[4])
192+
nfa.add_final_state(states[8])
193+
return nfa

0 commit comments

Comments
 (0)