-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 041ae87
Showing
6 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# mindfa: The implementation of Hopcroft's algorithm in Go | ||
|
||
The implementation of [DFA minimization](https://en.wikipedia.org/wiki/DFA_minimization) using Hopcroft's algorithm in Go. The time complexity is O(n log n) and the memory complexity is O(n)(n is the number of the states of the input DFA). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Package mindfa implements DFA minimization using Hopcroft's algorithm. | ||
package mindfa | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
) | ||
|
||
// Minimize takes a DFA representation, minimize it and returns the groups of | ||
// the states. The states in a group has same behavior. | ||
// | ||
// The all of arguments represents a single DFA(before minimization). It has | ||
// 0..nState states, 0..nSymbol input symbols and transitions as transition function. | ||
// | ||
// transition is a function takes state and symbol and returns the destination | ||
// state. The states which is included in finals are accepting state. The order | ||
// of the resulting partitions is not defined, but the order of the states in | ||
// a partition is ascending. | ||
// | ||
// It uses Hopcroft's algorithm. The memory complexity is O(nState). | ||
// | ||
// The all numbers in finals must be in [0, nState), and two same values must not | ||
// appear. | ||
func Minimize(nState, nSymbol int, finals []int, transition func(state, symbol int) int) [][]int { | ||
if len(finals) > nState { | ||
panic(fmt.Sprintf("len(finals) should be less than or equal to nState: len(finals) = %d, nState = %d", len(finals), nState)) | ||
} | ||
|
||
whole := make([]int, nState+max(len(finals), nState-len(finals))) | ||
copy(whole, finals) | ||
sort.Ints(whole[:len(finals)]) | ||
|
||
for i := 0; i < len(finals)-1; i++ { | ||
if whole[i] == whole[i+1] { | ||
panic(fmt.Sprintf("finals contains same two value: %d", whole[i])) | ||
} | ||
} | ||
|
||
cmpl(whole[len(finals):nState], whole[:len(finals)], nState) | ||
|
||
buf := whole[nState:] | ||
|
||
partitions := [][]int{whole[:len(finals)], whole[len(finals):nState]} | ||
// works is a set of the partition which has never tried to be split. | ||
works := [][]int{whole[:len(finals)], whole[len(finals):nState]} | ||
|
||
for len(works) > 0 { | ||
for c := 0; c < nSymbol; c++ { | ||
for ip, pFrom := range partitions { | ||
ip1, ip2 := 0, len(buf)-1 | ||
for _, state := range pFrom { | ||
if includes(works[0], transition(state, c)) { | ||
buf[ip1] = state | ||
ip1++ | ||
} else { | ||
buf[ip2] = state | ||
ip2-- | ||
} | ||
} | ||
|
||
if ip1 == 0 || ip2 == len(buf)-1 { | ||
continue | ||
} | ||
|
||
p1 := pFrom[:ip1] | ||
copy(p1, buf[:ip1]) | ||
|
||
p2 := pFrom[ip1:] | ||
for i := range p2 { | ||
p2[i] = buf[len(buf)-1-i] | ||
} | ||
|
||
var split bool | ||
for i, w := range works { | ||
if &w[0] != &pFrom[0] { | ||
continue | ||
} | ||
|
||
// Split works[i]. | ||
works = append(works, p2) | ||
works[i] = p1 | ||
split = true | ||
break | ||
} | ||
|
||
if !split { | ||
if len(p1) < len(p2) { | ||
works = append(works, p1) | ||
} else { | ||
works = append(works, p2) | ||
} | ||
} | ||
partitions[ip] = p1 | ||
partitions = append(partitions, p2) // Don't worry, p2 is not iterated in the current loop. | ||
} | ||
} | ||
// pseudo-shift | ||
works[0] = works[len(works)-1] | ||
works = works[:len(works)-1] | ||
} | ||
return partitions | ||
} | ||
|
||
// cmpl returns the complement set of a in (0..upper). | ||
func cmpl(dst, a []int, upper int) { | ||
var n, i int | ||
for _, u := range a { | ||
for ; n < u; n++ { | ||
dst[i] = n | ||
i++ | ||
} | ||
n++ | ||
} | ||
for ; n < upper; n++ { | ||
dst[i] = n | ||
i++ | ||
} | ||
} | ||
|
||
func includes(a []int, e int) bool { | ||
if len(a) < 100 { | ||
var i int | ||
for ; i < len(a) && a[i] < e; i++ { | ||
} | ||
return i < len(a) && a[i] == e | ||
} | ||
i := sort.SearchInts(a, e) | ||
return i < len(a) && a[i] == e | ||
} | ||
|
||
func max(a, b int) int { | ||
if a > b { | ||
return a | ||
} | ||
return b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package mindfa_test | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
|
||
"github.com/acomagu/mindfa" | ||
) | ||
|
||
// Minimize the DFA as: https://commons.wikimedia.org/wiki/File%3ADFA_to_be_minimized.jpg#/media/File%3ADFA_to_be_minimized.jpg | ||
// The result becomes: https://commons.wikimedia.org/wiki/File%3AMinimized_DFA.jpg#/media/File%3AMinimized_DFA.jpg . | ||
func ExampleMinimize() { | ||
nState := 6 | ||
nSymbol := 2 | ||
finals := []int{2, 3, 4} | ||
transitions := [][]int{ | ||
// 0 1 | ||
0: {1, 2}, // a | ||
1: {0, 3}, // b | ||
2: {4, 5}, // c | ||
3: {4, 5}, // d | ||
4: {4, 5}, // e | ||
5: {5, 5}, // f | ||
} | ||
transitionFunc := func(state, symbol int) int { return transitions[state][symbol] } | ||
|
||
partitions := mindfa.Minimize(nState, nSymbol, finals, transitionFunc) | ||
fmt.Println(partitions) // Output: [[2 3 4] [0 1] [5]] | ||
} | ||
|
||
// This example creates the minimum DFA inputs the digits of year(like 2 -> 0 -> 2 -> 1) | ||
// and accepts if the year is a leap year. | ||
func ExampleMinimize_determiningLeapYear() { | ||
nSymbol := 10 | ||
nState := 400 | ||
|
||
// finals becomes the list of leap years up to 400. | ||
var finals []int | ||
for s := 0; s < nState; s++ { | ||
if s == 0 || (s%100 != 0 && s%4 == 0) { | ||
finals = append(finals, s) | ||
} | ||
} | ||
|
||
// e.g. 2 -> 20 -> 202 -> 21 | ||
transitions := func(state, symbol int) int { | ||
return (state*10 + symbol) % nState | ||
} | ||
|
||
partitions := mindfa.Minimize(nState, nSymbol, finals, transitions) | ||
|
||
// Mark years belonging to the same partition with identical number. | ||
classes := make([]int, nState) | ||
for _, p := range partitions { | ||
for _, s := range p { | ||
classes[s] = p[0] | ||
} | ||
} | ||
|
||
checkLeapYear := func(year int) { | ||
state := classes[0] | ||
ds := digits(year) // digits(n) returns the slice of the digits of n. | ||
for i := len(ds) - 1; i >= 0; i-- { | ||
state = transitions(classes[state], ds[i]) | ||
} | ||
|
||
// If the state is acceptable, it is a leap year. | ||
if u := sort.SearchInts(finals, state); u < len(finals) && finals[u] == state { | ||
fmt.Printf("%d is a leap year.\n", year) | ||
} else { | ||
fmt.Printf("%d is not a leap year.\n", year) | ||
} | ||
} | ||
|
||
checkLeapYear(2019) | ||
checkLeapYear(2020) | ||
checkLeapYear(2021) | ||
// Output: | ||
// 2019 is not a leap year. | ||
// 2020 is a leap year. | ||
// 2021 is not a leap year. | ||
} | ||
|
||
// digits returns the slice of the digits of n. | ||
func digits(n int) []int { | ||
var ans []int | ||
for n > 0 { | ||
ans = append(ans, n%10) | ||
n /= 10 | ||
} | ||
return ans | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package mindfa | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/matryer/is" | ||
) | ||
|
||
func TestMinimize2(t *testing.T) { | ||
is := is.New(t) | ||
|
||
nState := 5 | ||
nSymbol := 3 | ||
finals := []int{1, 3} | ||
var transitions [5][3]int | ||
transitions[0][0] = 3 | ||
transitions[0][1] = 4 | ||
transitions[0][2] = 3 | ||
transitions[1][0] = 4 | ||
transitions[1][1] = 2 | ||
transitions[1][2] = 3 | ||
transitions[2][0] = 3 | ||
transitions[2][1] = 4 | ||
transitions[2][2] = 3 | ||
transitions[3][0] = 2 | ||
transitions[3][1] = 4 | ||
transitions[3][2] = 4 | ||
transitions[4][0] = 0 | ||
transitions[4][1] = 3 | ||
transitions[4][2] = 3 | ||
|
||
partitions := Minimize(nState, nSymbol, finals, func(s, c int) int { return transitions[s][c] }) | ||
is.Equal(len(partitions), 4) | ||
} | ||
|
||
func TestMinimize1(t *testing.T) { | ||
is := is.New(t) | ||
|
||
nSymbol := 10 | ||
nState := 400 | ||
|
||
var finals []int | ||
for s := 0; s < nState; s++ { | ||
if s == 0 || (s%100 != 0 && s%4 == 0) { | ||
finals = append(finals, s) | ||
} | ||
} | ||
|
||
transitions := make([][]int, 0, nState) | ||
for i := 0; i < nState; i++ { | ||
t := make([]int, 0, nSymbol) | ||
for c := 0; c < nSymbol; c++ { | ||
t = append(t, (i*nSymbol+c)%nState) | ||
} | ||
|
||
transitions = append(transitions, t) | ||
} | ||
|
||
partitions := Minimize(nState, nSymbol, finals, func(s, c int) int { return transitions[s][c] }) | ||
is.Equal(len(partitions), 7) | ||
|
||
classes := make([]int, nState) | ||
for _, p := range partitions { | ||
for _, s := range p { | ||
classes[s] = p[0] | ||
} | ||
} | ||
|
||
for n := 0; n < 5000; n++ { | ||
cur := classes[0] | ||
ds := digits(n) | ||
for i := len(ds) - 1; i >= 0; i-- { | ||
cur = transitions[classes[cur]][ds[i]] | ||
} | ||
is.Equal(includes(finals, cur), n%4 == 0 && (n%100 != 0 || n%400 == 0)) | ||
} | ||
} | ||
|
||
var _unused interface{} | ||
|
||
func BenchmarkMinimize(b *testing.B) { | ||
nSymbol := 10 | ||
nState := 400 | ||
|
||
var finals []int | ||
for s := 0; s < nState; s++ { | ||
if s == 0 || (s%100 != 0 && s%4 == 0) { | ||
finals = append(finals, s) | ||
} | ||
} | ||
|
||
transitions := make([][]int, 0, nState) | ||
for i := 0; i < nState; i++ { | ||
t := make([]int, 0, nSymbol) | ||
for c := 0; c < nSymbol; c++ { | ||
t = append(t, (i*nSymbol+c)%nState) | ||
} | ||
|
||
transitions = append(transitions, t) | ||
} | ||
|
||
transition := func(s, c int) int { return transitions[s][c] } | ||
for i := 0; i < b.N; i++ { | ||
_unused = Minimize(nState, nSymbol, finals, transition) | ||
} | ||
} | ||
|
||
func digits(n int) []int { | ||
var ans []int | ||
for n > 0 { | ||
ans = append(ans, n%10) | ||
n /= 10 | ||
} | ||
return ans | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module github.com/acomagu/mindfa | ||
|
||
go 1.13 | ||
|
||
require ( | ||
github.com/matryer/is v1.2.0 | ||
github.com/theodesp/unionfind v0.0.0-20181009090329-54e28c9f081e // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= | ||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= | ||
github.com/theodesp/unionfind v0.0.0-20181009090329-54e28c9f081e h1:mMI3DfT0woqtLW0fkZHqa+JAh4+IYd1L3pndbUzAOpg= | ||
github.com/theodesp/unionfind v0.0.0-20181009090329-54e28c9f081e/go.mod h1:1YbOQ/RED6w5UOYsaWLb346ayQhuDN5xE5Tv31r1n38= |