Skip to content

Commit 434b91b

Browse files
committed
week4 kodutöö lahendus
1 parent a86eb88 commit 434b91b

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

sols/week4/Grep.java

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package week4;
2+
3+
import week3.FiniteAutomaton;
4+
import week4.regex.RegexParser;
5+
import week4.regex.ast.*;
6+
7+
import java.io.BufferedReader;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.io.InputStreamReader;
11+
import java.nio.charset.StandardCharsets;
12+
import java.nio.file.Files;
13+
import java.nio.file.Paths;
14+
import java.util.*;
15+
16+
import static java.util.Collections.*;
17+
18+
public class Grep {
19+
/*
20+
* main meetodit ei ole vaja muuta.
21+
*
22+
* See meetod on siin vaid selleks, et anda käesolevale harjutusele veidi
23+
* realistlikum kontekst. Aga tegelikult on see vaid mäng -- see programm ei
24+
* pretendeeri päeva kasulikuima programmi tiitlile. Päris elus kasuta päris grep-i.
25+
*/
26+
static void main(String[] args) throws IOException {
27+
if (args.length < 1 || args.length > 2) {
28+
System.err.println(
29+
"""
30+
Programm vajab vähemalt ühte argumenti: regulaaravaldist.
31+
Teiseks argumendiks võib anda failinime (kui see puudub, siis loetakse tekst standardsisendist).
32+
Failinime andmisel eeldatakse, et tegemist on UTF-8 kodeeringus tekstifailiga.
33+
Rohkem argumente programm ei aktsepteeri.
34+
"""
35+
);
36+
System.exit(1);
37+
}
38+
39+
RegexNode regex = RegexParser.parse(args[0]);
40+
FiniteAutomaton automaton = determinize(regexToFiniteAutomaton(regex));
41+
42+
InputStream inputStream;
43+
if (args.length == 2) {
44+
inputStream = Files.newInputStream(Paths.get(args[1]));
45+
} else {
46+
inputStream = System.in;
47+
}
48+
49+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
50+
51+
// kuva ekraanile need read, mis vastavad antud regulaaravaldisele/automaadile
52+
String line;
53+
while ((line = reader.readLine()) != null) {
54+
if (automaton.accepts(line)) {
55+
System.out.println(line);
56+
}
57+
}
58+
}
59+
}
60+
61+
/*
62+
* See meetod peab loenguslaididel toodud konstruktsiooni põhjal koostama ja tagastama
63+
* etteantud regulaaravaldisele vastava mittedetermineeritud lõpliku automaadi.
64+
* Selle meetodi korrektne implementeerimine on antud ülesande juures kõige tähtsam.
65+
*
66+
* (Sa võid selle meetodi implementeerimiseks kasutada abimeetodeid ja ka abiklasse,
67+
* aga ära muuda meetodi signatuuri, sest automaattestid eeldavad just sellise signatuuri
68+
* olemasolu.)
69+
*
70+
* (Selle ülesande juures pole põhjust kasutada vahetulemuste salvestamiseks klassivälju,
71+
* aga kui sa seda siiski teed, siis kontrolli, et see meetod töötab korrektselt ka siis,
72+
* kui teda kutsutakse välja mitu korda järjest.)
73+
*/
74+
public static FiniteAutomaton regexToFiniteAutomaton(RegexNode regex) {
75+
Grep grep = new Grep();
76+
Fragment fragment = grep.regexToFragment(regex);
77+
FiniteAutomaton automaton = grep.automaton;
78+
automaton.setStartState(fragment.in);
79+
int endState = automaton.addState();
80+
automaton.addAcceptingState(endState);
81+
automaton.addTransition(fragment.out, fragment.outLabel, endState);
82+
return automaton;
83+
}
84+
85+
/**
86+
* @param outLabel label for transition from out
87+
*/
88+
private record Fragment(int in, int out, Character outLabel) {
89+
}
90+
91+
private final FiniteAutomaton automaton = new FiniteAutomaton();
92+
93+
private Fragment regexToFragment(RegexNode regex) {
94+
return switch (regex) {
95+
case Alternation(RegexNode left, RegexNode right) -> {
96+
Fragment leftFragment = regexToFragment(left);
97+
Fragment rightFragment = regexToFragment(right);
98+
int in = automaton.addState();
99+
int out = automaton.addState();
100+
automaton.addTransition(in, null, leftFragment.in);
101+
automaton.addTransition(in, null, rightFragment.in);
102+
automaton.addTransition(leftFragment.out, leftFragment.outLabel, out);
103+
automaton.addTransition(rightFragment.out, rightFragment.outLabel, out);
104+
yield new Fragment(in, out, null);
105+
}
106+
case Concatenation(RegexNode left, RegexNode right) -> {
107+
Fragment leftFragment = regexToFragment(left);
108+
Fragment rightFragment = regexToFragment(right);
109+
automaton.addTransition(leftFragment.out, leftFragment.outLabel, rightFragment.in);
110+
yield new Fragment(leftFragment.in, rightFragment.out, rightFragment.outLabel);
111+
}
112+
case Epsilon _ -> {
113+
int state = automaton.addState();
114+
yield new Fragment(state, state, null);
115+
}
116+
case Letter(char symbol) -> {
117+
int state = automaton.addState();
118+
yield new Fragment(state, state, symbol);
119+
}
120+
case Repetition(RegexNode child) -> {
121+
Fragment childFragment = regexToFragment(child);
122+
int state = automaton.addState();
123+
automaton.addTransition(state, null, childFragment.in);
124+
automaton.addTransition(childFragment.out, childFragment.outLabel, state);
125+
yield new Fragment(state, state, null);
126+
}
127+
};
128+
}
129+
130+
/**
131+
* See meetod peab looma etteantud NFA-le vastava DFA, st. etteantud
132+
* automaat tuleb determineerida.
133+
* Kui sa seda ei jõua teha, siis jäta see meetod nii, nagu ta on.
134+
*/
135+
public static FiniteAutomaton determinize(FiniteAutomaton nfa) {
136+
return determinize(nfa, nfa.epsilonClosure(singleton(nfa.getStartState())));
137+
}
138+
139+
// Abimeetod BrzozowskiMinimizer-i jaoks.
140+
public static FiniteAutomaton determinize(FiniteAutomaton nfa, Set<Integer> startingState) {
141+
// Loengu slaidide järgi. Kõigepealt S', F', T' on meil uues DFAs:
142+
FiniteAutomaton dfa = new FiniteAutomaton();
143+
144+
// Algoleku s'_0 lisamine:
145+
dfa.setStartState(addNfaStatesetToDfa(nfa, startingState, dfa));
146+
147+
// W (workQueue/Set: ülevaatusele minevad DFA seisunditele vastavad NFA seisundite hulgad)
148+
Queue<Set<Integer>> workQueue = new ArrayDeque<>(singletonList(startingState));
149+
150+
// Peame kuidagi NFA seisundite hulgad vastavate DFA seisunditega kokku viima
151+
Map<Set<Integer>, Integer> nfa2dfa = new HashMap<>(singletonMap(startingState, dfa.getStartState()));
152+
153+
while (!workQueue.isEmpty()) {
154+
// Lähteolekute hulk X
155+
Set<Integer> moveFrom = workQueue.remove();
156+
157+
// Vastav DFA seisundi ID
158+
int from = nfa2dfa.get(moveFrom);
159+
160+
// Vaatame, millistesse teistesse seisunditesse sellest DFA seisundist pääsema peaks
161+
// ning lisame selleks üleminekud DFA-sse
162+
for (char label : getOutgoingLabels(nfa, moveFrom)) {
163+
// Sihtolekute hulk Y
164+
Set<Integer> moveTo = nfa.move(moveFrom, label);
165+
166+
// Sihtolekute hulgale vastava DFA seisundi leidmine:
167+
int to = nfa2dfa.computeIfAbsent(moveTo, key -> {
168+
// NB! Tegemist on uue seisundite hulgaga, seega paneme selle tööjärjekorda!
169+
workQueue.add(key);
170+
// Lisame uue seisundi DFA-sse ja tagastame selle tulemuse, et vastavus läheks nfa2dfa sõnastikku.
171+
return addNfaStatesetToDfa(nfa, key, dfa);
172+
});
173+
174+
// Lõpuks saame ülemineku DFA-sse lisada
175+
dfa.addTransition(from, label, to);
176+
}
177+
}
178+
179+
return dfa;
180+
}
181+
182+
private static int addNfaStatesetToDfa(FiniteAutomaton nfa, Set<Integer> states, FiniteAutomaton dfa) {
183+
// Juhul kui NFA seisundite hulgas on lõppolek, siis vastav DFA seisund on ka lõppolek.
184+
boolean dfaAccepting = !Collections.disjoint(states, nfa.getAcceptingStates());
185+
int state = dfa.addState();
186+
if (dfaAccepting)
187+
dfa.addAcceptingState(state);
188+
return state;
189+
}
190+
191+
private static Set<Character> getOutgoingLabels(FiniteAutomaton nfa, Set<Integer> from) {
192+
Set<Character> result = new HashSet<>();
193+
for (Integer state : from) result.addAll(nfa.getOutgoingLabels(state));
194+
// NB! Siin me kindlasti ei taha epsilon-servasid vaadata!
195+
result.remove(null);
196+
return result;
197+
}
198+
}

0 commit comments

Comments
 (0)