Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨名詞+動詞(+助動詞)+終助詞の組み合わせの時の自然な変換を可能にしましたわ❗ #36

Merged
merged 10 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions ojosama.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ func Convert(src string, opt *ConvertOption) (string, error) {
continue
}

// 名詞+動詞+終助詞の組み合わせに対して変換する
if s, n, ok := convertSentenceEndingParticle(tokens, i); ok {
i = n
result.WriteString(s)
continue
}

// 連続する条件による変換を行う
if s, n, ok := convertContinuousConditions(tokens, i, opt); ok {
i = n
Expand All @@ -87,6 +94,88 @@ func Convert(src string, opt *ConvertOption) (string, error) {
return result.String(), nil
}

// convertSentenceEndingParticle は名詞+動詞(+助動詞)+終助詞の組み合わせすべてを満たす場合に変換する。
//
// 終助詞は文の終わりに、文を完結させつつ、文に「希望」「禁止」「詠嘆」「強意」等の意味を添える効果がある。
//
// 例えば「野球しようぜ」の場合、
// 「名詞:野球」「動詞:しよ」「助動詞:う」「終助詞:ぜ」という分解がされる。
//
// 終助詞の「ぜ」としては「希望」の意味合いが含まれるため、希望する意味合いのお嬢様言葉に変換する。
// 例:お野球をいたしませんこと
//
// その他にも「野球するな」だと「お野球をしてはいけませんわ」になる。
func convertSentenceEndingParticle(tokens []tokenizer.Token, tokenPos int) (string, int, bool) {
for _, r := range sentenceEndingParticleConvertRules {
var result strings.Builder
i := tokenPos
data := tokenizer.NewTokenData(tokens[i])

// 先頭が一致するならば次の単語に進む
if !matchAnyMultiConvertConditions(r.conditions1, data) {
continue
}
if len(tokens) <= i+1 {
continue
}
s := data.Surface
// TODO: ベタ書きしててよくない
if equalsFeatures(data.Features, nounsGeneral) || equalsFeatures(data.Features[:2], nounsSaDynamic) {
s = "お" + s
}
result.WriteString(s)
i++
data = tokenizer.NewTokenData(tokens[i])

// NOTE:
// 2つ目以降は value の値で置き換えるため
// result.WriteString(data.Surface) を実行しない。

// 2つ目は動詞のいずれかとマッチする。マッチしなければふりだしに戻る
if !matchAnyMultiConvertConditions(r.conditions2, data) {
continue
}
if len(tokens) <= i+1 {
continue
}
i++
data = tokenizer.NewTokenData(tokens[i])

// 助動詞があった場合は無視してトークンを進める。
// 別に無くても良い。
if r.auxiliaryVerb.matchAllTokenData(data) {
if len(tokens) <= i+1 {
continue
}
i++
data = tokenizer.NewTokenData(tokens[i])
}

// 最後、終助詞がどの意味分類に該当するかを取得
mt, ok := getMeaningType(r.sentenceEndingParticle, data)
if !ok {
continue
}

// 意味分類に該当する変換候補の文字列を返す
// TODO: 現状1個だけなので決め打ちで最初の1つ目を返す。
result.WriteString(r.value[mt][0])
return result.String(), i, true
}
return "", -1, false
}

func getMeaningType(typeMap map[meaningType][]convertConditions, data tokenizer.TokenData) (meaningType, bool) {
for k, v := range typeMap {
for _, cond := range v {
if cond.matchAllTokenData(data) {
return k, true
}
}
}
return meaningTypeUnknown, false
}

// convertContinuousConditions は連続する条件による変換ルールにマッチした変換結果を返す。
//
// 例えば「壱百満天原サロメ」や「横断歩道」のように、複数のTokenがこの順序で連続
Expand Down
14 changes: 14 additions & 0 deletions ojosama_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,20 @@ func TestConvert(t *testing.T) {
opt: nil,
wantErr: false,
},
{
desc: "正常系: 名詞+動詞+終助詞の組み合わせも変換いたしますわ~~!!この処理すっごく大変でしたの!!",
src: "野球しようぜ。サッカーやろうよ。バスケやるか。柔道やるな。陸上すんな。テニスするぞ。卓球やるべ。ゲームするの。",
want: "お野球をいたしませんこと。おサッカーをいたしませんこと。おバスケをいたしますわ。お柔道をしてはいけませんわ。お陸上をしてはいけませんわ。おテニスをいたしますわよ。お卓球をいたしませんこと。おゲームをいたしますわよ。",
opt: nil,
wantErr: false,
},
{
desc: "正常系: 名詞+動詞+助動詞のみで終助詞がない場合でもエラーにはなりませんのよ",
src: "流鏑馬やろう",
want: "流鏑馬やろう",
opt: nil,
wantErr: false,
},
}

for _, tt := range tests {
Expand Down
123 changes: 123 additions & 0 deletions rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,87 @@ type convertCondition struct {
Value []string
}

// meaningType は言葉の意味分類。
type meaningType int

// sentenceEndingParticleConvertRule は「名詞」+「動詞」+「終助詞」の組み合わせによる変換ルール。
type sentenceEndingParticleConvertRule struct {
conditions1 []convertConditions // 一番最初に評価されるルール
conditions2 []convertConditions // 二番目に評価されるルール
auxiliaryVerb convertConditions // 助動詞。マッチしなくても次にすすむ
sentenceEndingParticle map[meaningType][]convertConditions // 終助詞
value map[meaningType][]string
}

const (
convertTypeSurface convertType = iota + 1
convertTypeFeatures
convertTypeBaseForm

meaningTypeUnknown meaningType = iota
meaningTypeHope // 希望
meaningTypePoem // 詠嘆
meaningTypeProhibiton // 禁止
meaningTypeCoercion // 強制
)

var (
sentenceEndingParticleConvertRules = []sentenceEndingParticleConvertRule{
{
conditions1: []convertConditions{
{
{Type: convertTypeFeatures, Value: nounsGeneral},
},
{
{Type: convertTypeFeatures, Value: nounsSaDynamic},
},
},
conditions2: []convertConditions{
{
{Type: convertTypeFeatures, Value: verbIndependence},
{Type: convertTypeBaseForm, Value: []string{"する"}},
},
{
{Type: convertTypeFeatures, Value: verbIndependence},
{Type: convertTypeBaseForm, Value: []string{"やる"}},
},
},
sentenceEndingParticle: map[meaningType][]convertConditions{
meaningTypeHope: {
newCondSentenceEndingParticle("ぜ"),
newCondSentenceEndingParticle("よ"),
newCondSentenceEndingParticle("べ"),
},
meaningTypePoem: {
// これだけ特殊
newCond([]string{"助詞", "副助詞/並立助詞/終助詞"}, "か"),
},
meaningTypeProhibiton: {
newCondSentenceEndingParticle("な"),
},
meaningTypeCoercion: {
newCondSentenceEndingParticle("ぞ"),
newCondSentenceEndingParticle("の"),
},
},
auxiliaryVerb: newCondAuxiliaryVerb("う"),
value: map[meaningType][]string{
meaningTypeHope: {
"をいたしませんこと",
},
meaningTypePoem: {
"をいたしますわ",
},
meaningTypeProhibiton: {
"をしてはいけませんわ",
},
meaningTypeCoercion: {
"をいたしますわよ",
},
},
},
}

// continuousConditionsConvertRules は連続する条件がすべてマッチしたときに変換するルール。
//
// 例えば「壱百満天原サロメ」や「横断歩道」のように、複数のTokenがこの順序で連続
Expand Down Expand Up @@ -536,6 +611,10 @@ func (c *convertCondition) equalsTokenData(data tokenizer.TokenData) bool {
if data.Surface == c.Value[0] {
return true
}
case convertTypeBaseForm:
if data.BaseForm == c.Value[0] {
return true
}
}
return false
}
Expand All @@ -550,6 +629,10 @@ func (c *convertCondition) notEqualsTokenData(data tokenizer.TokenData) bool {
if data.Surface != c.Value[0] {
return true
}
case convertTypeBaseForm:
if data.BaseForm != c.Value[0] {
return true
}
}
return false
}
Expand All @@ -574,12 +657,26 @@ func (c *convertConditions) matchAnyTokenData(data tokenizer.TokenData) bool {
return false
}

func matchAnyMultiConvertConditions(ccs []convertConditions, data tokenizer.TokenData) bool {
for _, cs := range ccs {
// 1つでも一致すればOK
if cs.matchAllTokenData(data) {
return true
}
}
return false
}

var (
pronounGeneral = []string{"名詞", "代名詞", "一般"}
nounsGeneral = []string{"名詞", "一般"}
adnominalAdjective = []string{"連体詞"}
adjectivesSelfSupporting = []string{"形容詞", "自立"}
verbs = []string{"感動詞"}
verbIndependence = []string{"動詞", "自立"}
sentenceEndingParticle = []string{"助詞", "終助詞"}
auxiliaryVerb = []string{"助動詞"}
nounsSaDynamic = []string{"名詞", "サ変接続"}
)

func newCond(features []string, surface string) convertConditions {
Expand All @@ -595,6 +692,32 @@ func newCond(features []string, surface string) convertConditions {
}
}

func newCondSentenceEndingParticle(surface string) convertConditions {
return convertConditions{
{
Type: convertTypeFeatures,
Value: sentenceEndingParticle,
},
{
Type: convertTypeSurface,
Value: []string{surface},
},
}
}

func newCondAuxiliaryVerb(surface string) convertConditions {
return convertConditions{
{
Type: convertTypeFeatures,
Value: auxiliaryVerb,
},
{
Type: convertTypeSurface,
Value: []string{surface},
},
}
}

func newConds(surfaces []string) []convertConditions {
var c []convertConditions
for _, s := range surfaces {
Expand Down