Files
obitools4/pkg/obiprimer/match_naive.go
T

75 lines
1.8 KiB
Go
Raw Normal View History

package obiprimer
// baseCode maps a nucleotide to 0..3 (A,C,G,T; U is T) and everything else to 4,
// which never equals a word base.
var baseCode = func() (t [256]byte) {
for i := range t {
t[i] = 4
}
for i, c := range "ACGT" {
t[c], t[c+'a'-'A'] = byte(i), byte(i)
}
t['U'], t['u'] = 3, 3
return
}()
func codesOf(s string) []byte {
out := make([]byte, len(s))
for i := range out {
out[i] = baseCode[s[i]]
}
return out
}
// matchNaive is the reference implementation: every window of every sequence
// is compared with every word, base by base. It is slow and exists to define
// the exact semantics (see MatchParams) that faster implementations must
// reproduce.
func matchNaive(c *Corpus, words []uint64, p MatchParams) ([]WordMatches, error) {
if err := p.validate(); err != nil {
return nil, err
}
fwd, rev := wordStrings(words, p.K)
fc, rc := make([][]byte, len(words)), make([][]byte, len(words))
for i := range words {
fc[i], rc[i] = codesOf(fwd[i]), codesOf(rev[i])
}
mk := func() scanFunc {
var codes []byte
return func(raw []byte, emit func(word, pos, errors int, reverse bool)) {
n := len(raw)
if n < p.K {
return
}
codes = codes[:0]
for _, b := range raw {
codes = append(codes, baseCode[b])
}
windows := n - p.K + 1
if p.Circular {
codes = append(codes, codes[:p.K-1]...)
windows = n
}
for pos := range windows {
win := codes[pos : pos+p.K]
for w := range words {
for strand, pat := range [2][]byte{fc[w], rc[w]} {
errs := 0
for j, b := range pat {
if win[j] != b {
if errs++; errs > p.MaxErrors {
break
}
}
}
if errs <= p.MaxErrors {
emit(w, pos, errs, strand == 1)
}
}
}
}
}
}
return matchCorpus(c, words, mk), nil
}