278 lines
5.3 KiB
Go
278 lines
5.3 KiB
Go
package jvozba
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
func Score(lujvo string) int {
|
|
L := len(lujvo)
|
|
// apostrophe count, hyphen count, rafsi score count, vowel count
|
|
A := 0
|
|
H := 0
|
|
R := 0
|
|
V := 0
|
|
var curr string
|
|
for len(lujvo) > 0 {
|
|
curr, lujvo = katna(lujvo)
|
|
tai := rafsiTarmi(curr)
|
|
R += int(tai)
|
|
switch tai {
|
|
case Hyphen:
|
|
H++
|
|
case CVhV:
|
|
V += 2
|
|
A++
|
|
case CVCCV, CCVCV, CVV:
|
|
V += 2
|
|
default:
|
|
V += 1
|
|
}
|
|
}
|
|
return 1000*L - 500*A + 100*H - 10*R - V
|
|
}
|
|
|
|
// the tarmi enum directly corresponds to the scores for R
|
|
type tarmi int
|
|
|
|
const (
|
|
Hyphen tarmi = iota
|
|
CVCCV
|
|
CVCC
|
|
CCVCV
|
|
CCVC
|
|
CVC
|
|
CVhV
|
|
CCV
|
|
CVV
|
|
)
|
|
|
|
func rafsiTarmi(rafsi string) tarmi {
|
|
l := len(rafsi)
|
|
switch l {
|
|
case 1:
|
|
return Hyphen
|
|
case 5:
|
|
if isVowel(rafsi[2]) {
|
|
return CCVCV
|
|
} else {
|
|
return CVCCV
|
|
}
|
|
case 4:
|
|
if rafsi[2] == '\'' {
|
|
return CVhV
|
|
} else if !isVowel(rafsi[3]) {
|
|
if isVowel(rafsi[2]) {
|
|
return CCVC
|
|
} else {
|
|
return CVCC
|
|
}
|
|
} else {
|
|
return Hyphen
|
|
}
|
|
// case 3:
|
|
default:
|
|
if !isVowel(rafsi[2]) {
|
|
return CVC
|
|
} else {
|
|
if isVowel(rafsi[1]) {
|
|
return CVV
|
|
} else {
|
|
return CCV
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type zunsna int
|
|
|
|
const (
|
|
Unvoiced zunsna = iota
|
|
Voiced
|
|
Liquid
|
|
)
|
|
|
|
func zunsnaType(one byte) zunsna {
|
|
switch one {
|
|
case 'b', 'd', 'g', 'v', 'j', 'z':
|
|
return Voiced
|
|
case 'p', 't', 'k', 'f', 'c', 's', 'x':
|
|
return Unvoiced
|
|
default:
|
|
return Liquid
|
|
}
|
|
}
|
|
|
|
func isCjsz(one byte) bool {
|
|
switch one {
|
|
case 'c', 'j', 's', 'z':
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// `needsY` checks if an y-hyphen needs to be inserted
|
|
func needsY(previous byte, current string) bool {
|
|
if isVowel(previous) {
|
|
return false
|
|
}
|
|
head := current[0]
|
|
prevType := zunsnaType(previous)
|
|
headType := zunsnaType(head)
|
|
if (prevType == Voiced && headType == Unvoiced) || (headType == Voiced && prevType == Unvoiced) || previous == head || (isCjsz(previous) && isCjsz(head)) {
|
|
return true
|
|
}
|
|
comb := string([]byte{previous, head})
|
|
switch comb {
|
|
case "cx", "kx", "xc", "xk", "mz":
|
|
return true
|
|
}
|
|
switch string(previous) + current {
|
|
case "ndj", "ndz", "ntc", "nts":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isValidInitial(twoBytes ...byte) bool {
|
|
return len(twoBytes) == 2 &&
|
|
strings.Contains("bl~br~cf~ck~cl~cm~cn~cp~cr~ct~dj~dr~dz~fl~fr~gl~gr~jb~jd~jg~jm~jv~kl~kr~ml~mr~pl~pr~sf~sk~sl~sm~sn~sp~sr~st~tc~tr~ts~vl~vr~xl~xr~zb~zd~zg~zm~zv", string(twoBytes))
|
|
}
|
|
|
|
// Whether `lujvo` could lead to tosmabru on appending -y or is one already.
|
|
func isTosmabruInitial(lujvo string) bool {
|
|
var r string
|
|
var lastChar byte
|
|
i := 0
|
|
for lujvo != "" {
|
|
r, lujvo = katna(lujvo)
|
|
switch t := rafsiTarmi(r); t {
|
|
case CVC:
|
|
if i > 0 && !isValidInitial(lastChar, r[0]) {
|
|
return false
|
|
}
|
|
lastChar = r[2]
|
|
case CVCCV:
|
|
return i > 0 &&
|
|
isValidInitial(lastChar, r[0]) &&
|
|
isValidInitial(r[2], r[3])
|
|
case Hyphen:
|
|
return i > 1 && r == "y"
|
|
default:
|
|
return false
|
|
}
|
|
i++
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Mind the lack of y.
|
|
func isVowel(one byte) bool {
|
|
switch one {
|
|
case 'a', 'e', 'i', 'o', 'u':
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Make one cut. Atrocious code.
|
|
func katna(lujvo string) (string, string) {
|
|
var point /* of cission */ int
|
|
l := len(lujvo)
|
|
switch {
|
|
case l >= 4 && (lujvo[0] == 'n' || lujvo[0] == 'r' || lujvo[0] == 'y') && !isVowel(lujvo[1]):
|
|
point = 1
|
|
case l >= 8 && lujvo[4] == 'y':
|
|
point = 4
|
|
case l >= 7 && lujvo[3] == 'y':
|
|
point = 3
|
|
case l >= 7 && lujvo[2] == '\'' && isVowel(lujvo[3]):
|
|
point = 4
|
|
case l >= 6:
|
|
point = 3
|
|
default:
|
|
point = l
|
|
}
|
|
return lujvo[:point], lujvo[point:]
|
|
}
|
|
|
|
type scored struct {
|
|
lujvo string
|
|
score int
|
|
tosmabru bool
|
|
}
|
|
|
|
// -y-: L += 1 && H += 1 -> score += 1100
|
|
const yPenalty = 1100
|
|
|
|
func Lujvo(selci [][]string) (string, error) {
|
|
candidates := []scored{{"", 0, false}}
|
|
for selciN, cnino := range selci {
|
|
isLast := selciN == len(selci)-1
|
|
newCand := []scored{}
|
|
for _, rafsi := range cnino {
|
|
var best *scored
|
|
var bestTosmabru *scored
|
|
for _, laldo := range candidates {
|
|
l := len(laldo.lujvo)
|
|
hyphen := ""
|
|
if l > 0 && needsY(laldo.lujvo[l-1], rafsi[:2]) {
|
|
hyphen = "y"
|
|
} else if selciN == 1 {
|
|
tai := rafsiTarmi(laldo.lujvo)
|
|
if (tai == CVV || tai == CVhV) && !(isLast && rafsiTarmi(rafsi) == CCV) {
|
|
if rafsi[0] == 'r' {
|
|
hyphen = "n"
|
|
} else {
|
|
hyphen = "r"
|
|
}
|
|
}
|
|
}
|
|
if !isLast && (rafsiTarmi(rafsi) == CVCC || rafsiTarmi(rafsi) == CCVC) {
|
|
rafsi += "y"
|
|
}
|
|
newPart := hyphen + rafsi
|
|
newLujvo := laldo.lujvo + newPart
|
|
newScore := laldo.score + Score(newPart)
|
|
tosmabru := isTosmabruInitial(newLujvo)
|
|
if laldo.tosmabru {
|
|
newScore -= yPenalty
|
|
}
|
|
if tosmabru {
|
|
newScore += yPenalty
|
|
}
|
|
newScored := scored{newLujvo, newScore, tosmabru}
|
|
// DRY
|
|
if tosmabru {
|
|
if bestTosmabru == nil || bestTosmabru.score > newScore {
|
|
bestTosmabru = &newScored
|
|
}
|
|
} else {
|
|
if best == nil || best.score > newScore {
|
|
best = &newScored
|
|
}
|
|
}
|
|
}
|
|
if best != nil {
|
|
newCand = append(newCand, *best)
|
|
}
|
|
if bestTosmabru != nil {
|
|
newCand = append(newCand, *bestTosmabru)
|
|
}
|
|
}
|
|
candidates = newCand
|
|
}
|
|
bestOption := candidates[0]
|
|
for _, o := range candidates {
|
|
if bestOption.score > o.score {
|
|
bestOption = o
|
|
}
|
|
}
|
|
result := bestOption.lujvo
|
|
if bestOption.tosmabru {
|
|
result = result[:3] + "y" + result[3:]
|
|
}
|
|
return result, nil
|
|
}
|