Skip to content

Commit 9eb1913

Browse files
committed
NFA completion
This adds two methods to NFA, - is_complete to check if the NFA is complete - complete to turn it into a complete NFA. The latter accepts an Option<State>, which is the sink to be used in new transitions. If None, self-loops will be added, otherwise new steps point ot the given (sing) state. New unit tests are included.
1 parent 4448160 commit 9eb1913

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

src/nfa.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,71 @@ pub enum StateOrdering {
4242
}
4343

4444
impl Nfa {
45+
/// checks if the nfa is is complete:
46+
/// every state has a an outgoing transition for every letter in the alphabet
47+
///
48+
/// TODO: this uses equality on (sorted) vectors. Are HashSets better?
49+
pub fn is_complete(&self) -> bool {
50+
// get the alphabet
51+
let mut letters = self.get_alphabet();
52+
letters.sort();
53+
// for each state, check if it has a transition for each letter in the alphabet
54+
for state in 0..self.nb_states() {
55+
let mut state_actions = self
56+
.transitions
57+
.iter()
58+
.filter(|t| t.from == state)
59+
.map(|t| t.label.clone())
60+
.collect::<Vec<_>>();
61+
state_actions.sort();
62+
if state_actions != letters {
63+
return false;
64+
}
65+
}
66+
true
67+
}
68+
69+
/// completes the nfa by adding self-loops for every letter in the alphabet
70+
///
71+
/// This takes an optional state to be used as target for the new transitions.
72+
/// If this is None, new transitions will be self-loops.
73+
///
74+
/// This is really horrible because we have to recompute the alphabet lots
75+
pub fn complete(&mut self, sink_state: Option<State>) {
76+
// get the alphabet
77+
let mut letters = self
78+
.get_alphabet()
79+
.iter()
80+
.map(|x| x.to_string())
81+
.collect::<Vec<_>>();
82+
letters.sort();
83+
// for each state, get all its actions
84+
for state in 0..self.nb_states() {
85+
let mut state_actions = self
86+
.transitions
87+
.iter()
88+
.filter(|t| t.from == state)
89+
.map(|t| t.label.clone())
90+
.collect::<Vec<_>>();
91+
state_actions.sort();
92+
93+
// for every alphabent letter add new transitions
94+
for letter in &letters {
95+
if !state_actions.contains(letter) {
96+
match sink_state {
97+
Some(sink) => {
98+
self.add_transition_by_index2(state, sink, letter);
99+
}
100+
None => {
101+
// add a self-loop
102+
self.add_transition_by_index2(state, state, letter);
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}
109+
45110
/// getter for the states attribute
46111
pub fn states(&self) -> &Vec<String> {
47112
&self.states
@@ -208,6 +273,8 @@ impl Nfa {
208273
nfa
209274
}
210275

276+
/// Returns the alphabet of the NFA
277+
/// TODO: return a set?
211278
pub fn get_alphabet(&self) -> Vec<&str> {
212279
let mut letters = Vec::new();
213280
self.transitions.iter().for_each(|t| {
@@ -463,6 +530,70 @@ impl fmt::Display for Nfa {
463530
mod test {
464531
use super::*;
465532

533+
#[test]
534+
fn is_complete1() {
535+
let mut nfa = Nfa::from_size(2);
536+
nfa.add_transition_by_index1(0, 1, 'a');
537+
nfa.add_transition_by_index1(0, 1, 'b');
538+
nfa.add_transition_by_index1(1, 0, 'b');
539+
540+
assert!(!nfa.is_complete());
541+
}
542+
#[test]
543+
fn is_complete2() {
544+
let mut nfa = Nfa::from_size(2);
545+
nfa.add_transition_by_index1(0, 1, 'a');
546+
nfa.add_transition_by_index1(0, 1, 'b');
547+
nfa.add_transition_by_index1(1, 0, 'a');
548+
nfa.add_transition_by_index1(1, 0, 'b');
549+
550+
assert!(nfa.is_complete());
551+
}
552+
#[test]
553+
fn complete_to_selfloops() {
554+
// this NFA is missing a 'b'-strep from state 1.
555+
// after completion, there should be a step 1 -b-> 1.
556+
let mut nfa = Nfa::from_size(2);
557+
nfa.add_transition_by_index1(0, 1, 'a');
558+
nfa.add_transition_by_index1(0, 1, 'b');
559+
nfa.add_transition_by_index1(1, 0, 'a');
560+
561+
assert!(!nfa.is_complete());
562+
nfa.complete(None);
563+
assert!(nfa.is_complete());
564+
assert!(nfa.transitions
565+
.iter()
566+
.filter(|t| t.from == 1 && t.label== "b" && t.to==1)
567+
.next().is_some()
568+
);
569+
}
570+
#[test]
571+
fn complete_to_first() {
572+
// this NFA is missing an 'a'-step from state 0 and a b'-step from state 1.
573+
// after completion, both should exist and point to 0.
574+
let mut nfa = Nfa::from_size(2);
575+
nfa.add_transition_by_index1(0, 1, 'b');
576+
nfa.add_transition_by_index1(1, 0, 'a');
577+
578+
assert!(!nfa.is_complete());
579+
nfa.complete(Some(0));
580+
581+
assert!(nfa.is_complete());
582+
583+
// check if 0 -a-> 0 exists
584+
assert!(nfa.transitions
585+
.iter()
586+
.filter(|t| t.from == 0 && t.label == "a" && t.to==0)
587+
.next().is_some()
588+
);
589+
// check if 1 -b-> 0 exists
590+
assert!(nfa.transitions
591+
.iter()
592+
.filter(|t| t.from == 1 && t.label == "b" && t.to == 0)
593+
.next().is_some()
594+
);
595+
}
596+
466597
#[test]
467598
fn create() {
468599
let mut nfa = Nfa::from_states(&["toto", "titi"]);

0 commit comments

Comments
 (0)