func (t *Trimmer) Trim(seqs sequence.SequenceSet, numWorkers int) {
	ss := seqs.GetSequences()
	if t.verbosity > 0 {
		log.Println("Trimming ends and indexing all sequences against", len(t.frontAdapters), "adapters...")
	}
	done := make(chan bool, numWorkers)
	for i := 0; i < numWorkers; i++ {
		go t.trimWorker(seqs, ss, done)
	}
	for i := 0; i < numWorkers; i++ {
		<-done
	}
	ss = seqs.GetSequences()
	longestAdapter := 100  
	edgeSize := 150        
	minSeeds := 4          
	totalCount := 0
	totalBases := 0
	splits := make([]*sequenceSplit, seqs.Size()+1)
	ids := make([]int, 0, 100)
	var maxID int
	for seq := range ss {
		totalBases += seq.Len()-edgeSize*2
		for i := edgeSize; i < seq.Len()-edgeSize-longestAdapter; i += t.chunkSize - longestAdapter {  
			if i > seq.Len()-(t.chunkSize*3)/2-edgeSize {
				seedSeq := t.index.NewSeedSequence(seq.SubSequence(i, seq.Len()-edgeSize))
				totalCount += seedSeq.GetNumSeeds()
				t.index.AddSequence(seedSeq)
				break
			} else {
				endPoint := i + t.chunkSize
				if endPoint >= seq.Len()-edgeSize {
					endPoint = seq.Len() - edgeSize
				}
				seedSeq := t.index.NewSeedSequence(seq.SubSequence(i, endPoint))
				totalCount += seedSeq.GetNumSeeds()
				if seedSeq.GetNumSeeds() >= minSeeds {
					t.index.AddSequence(seedSeq)
				}
			}
		}
		if totalCount > 300000000 {  
			t.index.IndexSequences(numWorkers)
			if t.verbosity > 0 {
				log.Println("Searching", int(totalBases/1000000),"MB of sequences for splitting based on", len(t.frontAdapters), "adapters")
			}
			for i, ad := range t.frontAdapters {
				go t.findSplit(ad, t.frontAdapterSets[i], splits, &ids, &maxID, seqs, done)
			}
			for i := 0; i < len(t.frontAdapters); i++ {
				<-done
			}
			totalCount = 0
			totalBases = 0
			t.setupIndex()
		}
	}
	if totalCount > 0 {
		t.index.IndexSequences(numWorkers)
		if t.verbosity > 0 {
			log.Println("Searching", int(totalBases/1000000),"MB of sequences for splitting based on", len(t.frontAdapters), "adapters")
		}
		for i, ad := range t.frontAdapters {
			go t.findSplit(ad, t.frontAdapterSets[i], splits, &ids, &maxID, seqs, done)
		}
		for i := 0; i < len(t.frontAdapters); i++ {
			<-done
		}
	}
	if t.verbosity > 0 {
		log.Println(len(ids), "sequences require splitting")
	}
	ss = seqs.GetSequencesByID(ids)
	splitSeqs := make([]sequence.Sequence, maxID+1, maxID+1)
	for s := range ss {
		splitSeqs[s.GetID()] = s
	}
	for _, id := range ids {
		split := splits[id]
		if split == nil {
			continue
		}
		seq := splitSeqs[id]
		if t.keepSplits {
			report := fmt.Sprint("Splitting read ", split.id, " into")
			if split.aEnd > edgeSize {
				seqs.AddSequence(seq.SubSequence(0, split.aEnd), seqs.GetName(split.id)+"_(left)")
				report += fmt.Sprint(": 0 - ",split.aEnd," and ")
			} else {
				report += " ignored short left hand side and "
			}
			if seq.Len()-split.bStart > edgeSize {
				seqs.AddSequence(seq.SubSequence(split.bStart, seq.Len()), seqs.GetName(split.id)+"_(right)")
				report += fmt.Sprint(split.bStart," - ",seq.Len())
			} else {
				report += " ignored short right hand side"
			}
			if t.verbosity > 1 {
				log.Println(report)
				if split.aEnd >= 0 && split.bStart < seq.Len() && split.bStart-split.aEnd-t.extraMidTrim*2 <= longestAdapter {
					log.Println(seq.SubSequence(split.aEnd+t.extraMidTrim, split.bStart-t.extraMidTrim).String())
				}
			}
		}
		seqs.SetIgnore(split.id, true)
	}
}
Trim searches all sequences for adapters and updates the SequenceSet's offsets accordingly
Any reads that are split will be disabled in the sequence set and replaced with a new pair (or discarded, depending on current parameters)
package trim
import (
	"fmt"
	"github.com/jteutenberg/downpore/seeds"
	"github.com/jteutenberg/downpore/sequence"
	"github.com/jteutenberg/downpore/util"
	"log"
	"strings"
	"sync"
)
type Trimmer struct {
	originalFront []sequence.Sequence
	originalBack  []sequence.Sequence
	frontAdapters    []*seeds.SeedSequence
	backAdapters     []*seeds.SeedSequence
	frontAdapterSets []*util.IntSet
	backAdapterSets  []*util.IntSet
	pairsFront       []int
	pairsBack        []int
	index            *seeds.SeedIndex
	k                int
	chunkSize        int
	lock        *sync.Mutex
	frontCounts []int
	backCounts  []int
	noCount     int
	seenCount   int
	midThreshold  int
	extraEdgeTrim int
	extraMidTrim  int
	keepSplits    bool
	tagAdapters   bool
	requirePairs  bool
	verbosity     int
}
type sequenceSplit struct {
	id     int
	aEnd   int
	bStart int
}
func NewTrimmer(frontAdapters, backAdapters []sequence.Sequence, k int) *Trimmer {
	var lock sync.Mutex
	t := Trimmer{originalFront: frontAdapters, originalBack: backAdapters, frontAdapters: make([]*seeds.SeedSequence, 0, len(frontAdapters)), backAdapters: make([]*seeds.SeedSequence, 0, len(backAdapters)), frontAdapterSets: make([]*util.IntSet, 0, len(frontAdapters)), backAdapterSets: make([]*util.IntSet, 0, len(backAdapters)), index: nil, k: k, frontCounts: nil, backCounts: nil, noCount: 0, lock: &lock, verbosity: 1}
	(&t).setupIndex()
	t.SetTrimParams(85, 5, 50, 1000, false, true, false)
	return &t
}
func (t *Trimmer) setupIndex() {
	t.frontAdapters = t.frontAdapters[:0]
	t.frontAdapterSets = t.frontAdapterSets[:0]
	t.backAdapters = t.backAdapters[:0]
	t.backAdapterSets = t.backAdapterSets[:0]
	t.index = seeds.NewSeedIndex(uint(t.k))
	for _, s := range t.originalFront {
		t.frontAdapters = append(t.frontAdapters, t.index.NewAllSeedSequence(s))
		set := util.NewIntSet()
		kmers := s.ShortKmers(t.k, true)
		t.index.GetSeedsFromKmers(kmers, set)
		t.frontAdapterSets = append(t.frontAdapterSets, set)
	}
	for _, s := range t.originalBack {
		t.backAdapters = append(t.backAdapters, t.index.NewAllSeedSequence(s))
		set := util.NewIntSet()
		kmers := s.ShortKmers(t.k, true)
		t.index.GetSeedsFromKmers(kmers, set)
		t.backAdapterSets = append(t.backAdapterSets, set)
	}
	t.frontCounts = make([]int, len(t.originalFront))
	t.backCounts = make([]int, len(t.originalBack))
	pairID := 1
	t.pairsFront = make([]int, len(t.originalFront))
	t.pairsBack = make([]int, len(t.originalBack))
	for i, _ := range t.originalBack {
		t.pairsBack[i] = -1
	}
	for i, a := range t.originalFront {
		name := a.GetName()
		t.pairsFront[i] = -1
		for j, b := range t.originalBack {
			if b.GetName() == name {
				t.pairsFront[i] = pairID
				t.pairsBack[j] = pairID
				pairID++
				break
			}
		}
	}
}
func LoadTrimmer(frontAdapters, backAdapters string, k int) *Trimmer {
	fronts := make([]sequence.Sequence, 0, 100)
	backs := make([]sequence.Sequence, 0, 100)
	seqSet := sequence.NewFastaSequenceSet(frontAdapters, 0, 1, false, false)
	seqs := seqSet.GetSequences()
	for seq := range seqs {
		fronts = append(fronts, seq)
	}
	seqSet = sequence.NewFastaSequenceSet(backAdapters, 0, 1, false, false)
	seqs = seqSet.GetSequences()
	for seq := range seqs {
		backs = append(backs, seq)
	}
	return NewTrimmer(fronts, backs, k)
}
func (t *Trimmer) SetVerbosity(level int) {
	t.verbosity = level
}
func (t *Trimmer) SetTrimParams(midThreshold, extraEdgeTrim, extraMidTrim int, chunkSize int, keepSplits, tagAdapters, requirePairs bool) {
	t.midThreshold = midThreshold
	t.extraEdgeTrim = extraEdgeTrim
	t.extraMidTrim = extraMidTrim
	t.keepSplits = keepSplits
	t.tagAdapters = tagAdapters
	t.requirePairs = requirePairs
	t.chunkSize = chunkSize
}