func StemConcurrent(words *[]string) {
	CPUs := runtime.NumCPU()
	length := len(*words)
	output := make(chan string)
	partition := length / CPUs
	var CPU int
	for CPU = 0; CPU < CPUs; CPU++ {
		go func(strs []string) {
			for _, word := range strs {
				output <- Stem(word)
			}
		}((*words)[CPU*partition : (CPU+1)*partition])
	}
	if length-(CPU)*partition > 0 {
		go func(strs []string) {
			for _, word := range strs {
				output <- Stem(word)
			}
		}((*words)[(CPU)*partition : length])
	}
	for idx := 0; idx < length; idx++ {
		(*words)[idx] = <-output
	}
}
StemConcurrent accepts a pointer to a slice of strings and stems them in place.
It tries to offload the work into multiple threads. It makes no guarantees about
the order of the stems in the modified slice.
package stemmer
import (
	"runtime"
	"strings"
)
type stem struct {
	measure    int
	oCondition bool
	vCondition bool
	dCondition bool
	lastChar   byte
}
func makeStem(m int, o, v, d bool, b byte) stem {
	return stem{
		m,
		o,
		v,
		d,
		b,
	}
}
var letterToConsonantMap = map[byte]bool{
	65: false,
	66: true,
	67: true,
	68: true,
	69: false,
	70: true,
	71: true,
	72: true,
	73: false,
	74: true,
	75: true,
	76: true,
	77: true,
	78: true,
	79: false,
	80: true,
	81: true,
	82: true,
	83: true,
	84: true,
	85: false,
	86: true,
	87: true,
	88: true,
	89: true,
	90: true,
}
func processStem(input string) (s stem) {
	previousWasVowel := false
	uppedString := strings.ToUpper(input)
	length := len(uppedString)
	doubleConsonants := 0
	for i := 0; i < length; i++ {
		char := uppedString[i]
		if shouldIncrementMeasure(char, previousWasVowel) {
			s.measure++
		}
		previousWasVowel = currentIsVowel(char, previousWasVowel, &s)
		if i == (length - 3) {
			if !previousWasVowel {
				s.oCondition = true
			}
		}
		if i == (length - 2) {
			if !previousWasVowel {
				if s.oCondition {
					s.oCondition = false
				}
				doubleConsonants++
			}
		}
		if i == (length - 1) {
			s.lastChar = char
			if !previousWasVowel {
				if s.oCondition && charCheck(char) {
					s.oCondition = false
				}
				doubleConsonants++
			} else {
				s.oCondition = false
			}
		}
	}
	s.dCondition = (doubleConsonants == 2)
	return
}
func replace(input, target, replacement string) string {
	if idx := strings.LastIndex(input, target); idx >= 0 {
		return input[0:idx] + replacement
	}
	return input
}
func find(input, target string, start, end int) bool {
	length := len(input)
	if length < end || start < 0 {
		return false
	}
	return input[start:end] == target
}
func Stem(input string) string {
	if len(input) < 3 {
		return input
	}
	upper := strings.ToUpper(input)
	stemmed := step1A(upper)
	stemmed = step1B(stemmed)
	stemmed = step1C(stemmed)
	stemmed = step2(stemmed)
	stemmed = step3(stemmed)
	stemmed = step4(stemmed)
	stemmed = step5A(stemmed)
	stemmed = step5B(stemmed)
	return stemmed
}
func StemMultiple(words []string) (output []string) {
	output = make([]string, len(words))
	for idx, word := range words {
		output[idx] = Stem(word)
	}
	return
}
func StemMultipleMutate(words *[]string) {
	for idx, word := range *words {
		(*words)[idx] = Stem(word)
	}
}