From 971d6c3f8abdbe0e2aa16128f65bc50f36e247bf Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 03:26:54 +0000 Subject: [PATCH] perf(reversal): reduce multiplier variant allocations Co-Authored-By: Virgil --- reversal/multiplier.go | 101 +++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/reversal/multiplier.go b/reversal/multiplier.go index 15e6d1d..4adf902 100644 --- a/reversal/multiplier.go +++ b/reversal/multiplier.go @@ -68,39 +68,27 @@ func (m *Multiplier) Expand(text string) []string { // 2. Verb transforms: for each verb, produce past and gerund variants for _, vi := range verbIndices { - pastTokens := m.applyVerbTransform(tokens, vi, "past") - addVariant(reconstruct(pastTokens)) - - gerundTokens := m.applyVerbTransform(tokens, vi, "gerund") - addVariant(reconstruct(gerundTokens)) - - baseTokens := m.applyVerbTransform(tokens, vi, "base") - addVariant(reconstruct(baseTokens)) + addVariant(m.reconstructWithVerbTransform(tokens, vi, "past")) + addVariant(m.reconstructWithVerbTransform(tokens, vi, "gerund")) + addVariant(m.reconstructWithVerbTransform(tokens, vi, "base")) } // 3. Noun transforms: for each noun, toggle plural/singular for _, ni := range nounIndices { - pluralTokens := m.applyNounTransform(tokens, ni) - addVariant(reconstruct(pluralTokens)) + addVariant(m.reconstructWithNounTransform(tokens, ni)) } // 4. Combinations: each verb transform + each noun transform for _, vi := range verbIndices { for _, ni := range nounIndices { // past + noun toggle - pastTokens := m.applyVerbTransform(tokens, vi, "past") - pastPluralTokens := m.applyNounTransformOnTokens(pastTokens, ni) - addVariant(reconstruct(pastPluralTokens)) + addVariant(m.reconstructWithVerbAndNounTransform(tokens, vi, "past", ni)) // gerund + noun toggle - gerundTokens := m.applyVerbTransform(tokens, vi, "gerund") - gerundPluralTokens := m.applyNounTransformOnTokens(gerundTokens, ni) - addVariant(reconstruct(gerundPluralTokens)) + addVariant(m.reconstructWithVerbAndNounTransform(tokens, vi, "gerund", ni)) // base + noun toggle - baseTokens := m.applyVerbTransform(tokens, vi, "base") - basePluralTokens := m.applyNounTransformOnTokens(baseTokens, ni) - addVariant(reconstruct(basePluralTokens)) + addVariant(m.reconstructWithVerbAndNounTransform(tokens, vi, "base", ni)) } } @@ -204,6 +192,81 @@ func (m *Multiplier) applyNounTransformOnTokens(tokens []Token, ni int) []Token return result } +func (m *Multiplier) reconstructWithVerbTransform(tokens []Token, vi int, targetTense string) string { + return m.reconstructWithTransforms(tokens, vi, targetTense, -1) +} + +func (m *Multiplier) reconstructWithNounTransform(tokens []Token, ni int) string { + return m.reconstructWithTransforms(tokens, -1, "", ni) +} + +func (m *Multiplier) reconstructWithVerbAndNounTransform(tokens []Token, vi int, targetTense string, ni int) string { + return m.reconstructWithTransforms(tokens, vi, targetTense, ni) +} + +func (m *Multiplier) reconstructWithTransforms(tokens []Token, vi int, targetTense string, ni int) string { + b := core.NewBuilder() + for i, tok := range tokens { + if i > 0 { + // Punctuation tokens should stay attached to the preceding token. + if tok.Type == TokenPunctuation { + b.WriteString(tok.Raw) + continue + } + b.WriteByte(' ') + } + switch { + case i == vi: + b.WriteString(transformedVerbRaw(tok, targetTense)) + case i == ni: + b.WriteString(transformedNounRaw(tok)) + default: + b.WriteString(tok.Raw) + } + } + return b.String() +} + +func transformedVerbRaw(tok Token, targetTense string) string { + base := tok.VerbInfo.Base + currentTense := tok.VerbInfo.Tense + if currentTense == targetTense { + return tok.Raw + } + + var newForm string + switch targetTense { + case "past": + newForm = i18n.PastTense(base) + case "gerund": + newForm = i18n.Gerund(base) + case "base": + newForm = base + } + if newForm == "" { + return tok.Raw + } + return preserveCase(tok.Raw, newForm) +} + +func transformedNounRaw(tok Token) string { + base := tok.NounInfo.Base + if base == "" { + return tok.Raw + } + + var newForm string + if tok.NounInfo.Plural { + newForm = base + } else { + newForm = i18n.PluralForm(base) + } + if newForm == "" { + return tok.Raw + } + return preserveCase(tok.Raw, newForm) +} + // reconstruct joins tokens back into a string, preserving spacing. func reconstruct(tokens []Token) string { b := core.NewBuilder() -- 2.45.3