├── version.txt ├── namedivider-python ├── assets │ ├── .gitkeep │ └── kanji.csv ├── requirements.txt ├── Makefile └── .gitignore ├── testdata ├── success.csv ├── part_of_error.csv └── invalid_format.csv ├── feature ├── characters.go ├── length.go ├── order.go ├── length_test.go ├── order_test.go ├── kanji.go └── kanji_test.go ├── .github ├── dependabot.yml └── workflows │ ├── lint.yml │ ├── test.yml │ ├── update-asset.yml │ └── release.yml ├── go.mod ├── cmd └── seimei │ └── main.go ├── .gitignore ├── .editorconfig ├── .golangci.yml ├── benchmark ├── Readme.md └── benchmark_test.go ├── Makefile ├── .goreleaser.yaml ├── go.sum ├── LICENSE ├── parser ├── rule.go ├── statistics_test.go ├── rule_test.go ├── statistics.go ├── parser.go └── parser_test.go ├── README.md ├── semei.go ├── cli.go ├── semei_test.go └── cli_test.go /version.txt: -------------------------------------------------------------------------------- 1 | devel 2 | -------------------------------------------------------------------------------- /namedivider-python/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/success.csv: -------------------------------------------------------------------------------- 1 | 田中太郎 2 | 乙一 3 | 竈門炭治郎 4 | 中曽根康弘 5 | -------------------------------------------------------------------------------- /testdata/part_of_error.csv: -------------------------------------------------------------------------------- 1 | 田中太郎 2 | 乙 3 | 竈門炭治郎 4 | 中曽根康弘 5 | -------------------------------------------------------------------------------- /namedivider-python/requirements.txt: -------------------------------------------------------------------------------- 1 | namedivider-python==0.1.0 2 | -------------------------------------------------------------------------------- /testdata/invalid_format.csv: -------------------------------------------------------------------------------- 1 | 田中太郎, 2 | 乙一 3 | 竈門炭治郎 4 | 中曽根康弘 5 | -------------------------------------------------------------------------------- /feature/characters.go: -------------------------------------------------------------------------------- 1 | package feature 2 | 3 | type PartOfNameCharacters interface { 4 | Length() int 5 | Slice() []rune 6 | IsLastName() bool 7 | } 8 | -------------------------------------------------------------------------------- /namedivider-python/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: download-csv 3 | 4 | download-csv: 5 | @mkdir -p work 6 | @pip3 install --no-deps -r requirements.txt -t work 7 | @cp -R work/namedivider/assets/* assets 8 | @rm -rR work 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: pip 8 | directory: "namedivider-python" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/glassmonkey/seimei/v2 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.9 7 | github.com/spf13/cobra v1.7.0 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /cmd/seimei/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/glassmonkey/seimei/v2" 8 | ) 9 | 10 | var ( 11 | Version = "unknown" 12 | Revision = "unknown" 13 | ) 14 | 15 | func main() { 16 | if err := seimei.Run(); err != nil { 17 | fmt.Fprintf(os.Stderr, "raised error: %s\n", err) 18 | os.Exit(1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | dist 18 | dist/ 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}] 11 | indent_style = tab 12 | indent_size = 4 13 | 14 | [*.md] 15 | indent_size = 4 16 | trim_trailing_whitespace = false 17 | 18 | eclint_indent_style = unset 19 | 20 | [Dockerfile] 21 | indent_size = 4 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | go: '1.17' 3 | linters-settings: 4 | govet: 5 | check-shadowing: false 6 | golint: 7 | min-confidence: 0 8 | gocyclo: 9 | min-complexity: 30 10 | maligned: 11 | suggest-new: true 12 | misspell: 13 | locale: US 14 | 15 | 16 | linters: 17 | enable-all: true 18 | disable: 19 | - forbidigo 20 | - ifshort 21 | - varnamelen 22 | - funlen 23 | - lll 24 | - gomnd 25 | - gochecknoglobals 26 | - nlreturn 27 | - wsl 28 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | on: [ pull_request ] 3 | jobs: 4 | golangci-lint: 5 | name: runner / golangci-lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code into the Go module directory 9 | uses: actions/checkout@v3 10 | - name: golangci-lint 11 | uses: reviewdog/action-golangci-lint@v2 12 | # uses: docker://reviewdog/action-golangci-lint:v1 # pre-build docker image 13 | with: 14 | github_token: ${{ secrets.GITHUB_TOKEN }} 15 | golangci_lint_flags: "--config=.golangci.yml ./..." 16 | -------------------------------------------------------------------------------- /benchmark/Readme.md: -------------------------------------------------------------------------------- 1 | # benchmark 2 | This package is for operation testing. 3 | 4 | # contents 5 | 6 | ## sample.csv 7 | dummy first and last name test data generated from [anonymous personal Information generator](https://testdata.userlocal.jp/). 8 | 9 | 10 | ## benchmark_test.go 11 | It has two test codes for operation testing. 12 | 13 | ### TestRunCompareOrigin 14 | this is a regression test against the original tool ([namedivider-python](https://github.com/rskmoi/namedivider-python)). 15 | 16 | ### TestRunCompareAnswer 17 | This is a test to check the algorithm's accuracy. 18 | The percent of correct answers was 99.52%. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | Version=$(shell git describe --tags --abbrev=0) 4 | Revision=$(shell git rev-parse --short HEAD) 5 | 6 | .PHONY: build 7 | build: 8 | go build -o dist/seimei cmd/seimei/main.go 9 | 10 | .PHONY: release 11 | release: 12 | goreleaser --snapshot --rm-dist 13 | 14 | .PHONY: test 15 | test: 16 | go test -v $(go list ./... | grep -v /benchmark/) 17 | 18 | .PHONY: test-coverage 19 | test-coverage: 20 | go test -cover -v ./... -coverprofile=dist/cover.out 21 | go tool cover -html=dist/cover.out -o dist/cover.html 22 | open dist/cover.html 23 | 24 | 25 | .PHONY: lint 26 | lint: 27 | golangci-lint run 28 | 29 | .PHONY: lint-fix 30 | lint-fix: 31 | golangci-lint run --fix 32 | 33 | .PHONY: version 34 | version: 35 | @echo "$(Version)-$(Revision)" > version.txt 36 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | - go mod tidy 6 | - make version 7 | builds: 8 | - env: 9 | - CGO_ENABLED=0 10 | main: ./cmd/seimei 11 | ldflags: 12 | - -s -w 13 | goos: 14 | - linux 15 | - windows 16 | - darwin 17 | archives: 18 | - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 19 | replacements: 20 | darwin: Darwin 21 | linux: Linux 22 | windows: Windows 23 | 386: i386 24 | amd64: x86_64 25 | checksum: 26 | name_template: 'checksums.txt' 27 | snapshot: 28 | name_template: "{{ incpatch .Version }}-next" 29 | changelog: 30 | sort: asc 31 | filters: 32 | exclude: 33 | - '^docs:' 34 | - '^test:' 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | name: test 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | platform: [ ubuntu-latest ] 12 | runs-on: ${{ matrix.platform }} 13 | steps: 14 | - name: Install Go 15 | if: success() 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: '^1.20' # The Go version to download (if necessary) and use. 19 | - name: Checkout code 20 | uses: actions/checkout@v3 21 | - name: Use Cache 22 | uses: actions/cache@v2 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Download Modules 29 | if: steps.cache.outputs.cache-hit != 'true' 30 | run: go mod download 31 | - name: Run tests 32 | run: make test 33 | -------------------------------------------------------------------------------- /.github/workflows/update-asset.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths: 4 | - 'namedivider-python/**' 5 | name: update-assets 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | platform: [ ubuntu-latest ] 11 | runs-on: ${{ matrix.platform }} 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | - name: Install Python 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: '3.11' 19 | cache: 'pip' 20 | cache-dependency-path: '**/requirements.txt' 21 | - name: update-csv 22 | run: cd namedivider-python && make download-csv 23 | - name: Commit assets 24 | run: | 25 | git config --local user.email "action@github.com" 26 | git config --local user.name "GitHub Action" 27 | git status 28 | git add namedivider-python/assets 29 | git commit -m "chore: update assets" -a 30 | git push 31 | -------------------------------------------------------------------------------- /feature/length.go: -------------------------------------------------------------------------------- 1 | package feature 2 | 3 | import "fmt" 4 | 5 | type KanjiLengthFeatureCalculator struct { 6 | Manager KanjiFeatureManager 7 | } 8 | 9 | func (fc KanjiLengthFeatureCalculator) Score(pieceOfName PartOfNameCharacters, fullNameLength int) (float64, error) { 10 | score := 0.0 11 | offset := 0 12 | 13 | if !pieceOfName.IsLastName() { 14 | offset = fullNameLength - pieceOfName.Length() 15 | } 16 | 17 | for i, c := range pieceOfName.Slice() { 18 | ci := i + offset 19 | 20 | mask, err := fc.Manager.LengthMask(fullNameLength, ci) 21 | if err != nil { 22 | return 0.0, fmt.Errorf("failed order score: %w", err) 23 | } 24 | 25 | index, err := fc.Manager.SelectLengthFeaturePosition(pieceOfName) 26 | if err != nil { 27 | return 0.0, fmt.Errorf("failed order score: %w", err) 28 | } 29 | 30 | v, err := fc.Manager.Get(Character(c)).GetLengthValue(index, mask) 31 | if err != nil { 32 | return 0.0, fmt.Errorf("failed order score: %w", err) 33 | } 34 | 35 | score += v 36 | } 37 | 38 | return score, nil 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: 'Version to bump to' 11 | required: false 12 | default: '' 13 | jobs: 14 | goreleaser: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | - name: push-tag 22 | run: | 23 | git tag ${{ github.event.inputs.version }} 24 | git push origin ${{ github.event.inputs.version }} 25 | if: ${{ github.event.inputs.version }} != '' 26 | - name: Set up Go 27 | uses: actions/setup-go@v3 28 | with: 29 | go-version: '1.20' 30 | - name: Run GoReleaser 31 | uses: goreleaser/goreleaser-action@v4 32 | with: 33 | version: latest 34 | args: release --clean 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 3 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 4 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 5 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 6 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 7 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 8 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /feature/order.go: -------------------------------------------------------------------------------- 1 | package feature 2 | 3 | import "fmt" 4 | 5 | type KanjiOrderFeatureCalculator struct { 6 | Manager KanjiFeatureManager 7 | } 8 | 9 | func (fc KanjiOrderFeatureCalculator) Score(pieceOfName PartOfNameCharacters, fullNameLength int) (float64, error) { 10 | score := 0.0 11 | offset := 0 12 | 13 | if !pieceOfName.IsLastName() { 14 | offset = fullNameLength - pieceOfName.Length() 15 | } 16 | 17 | for i, c := range pieceOfName.Slice() { 18 | ci := i + offset 19 | if ci == 0 || ci == fullNameLength-1 { 20 | continue 21 | } 22 | 23 | mask, err := fc.Manager.OrderMask(fullNameLength, ci) 24 | if err != nil { 25 | return 0.0, fmt.Errorf("failed order score: %w", err) 26 | } 27 | 28 | index, err := fc.Manager.SelectOrderFeaturePosition(pieceOfName, i) 29 | if err != nil { 30 | return 0.0, fmt.Errorf("failed order score: %w", err) 31 | } 32 | 33 | v, err := fc.Manager.Get(Character(c)).GetOrderValue(index, mask) 34 | if err != nil { 35 | return 0.0, fmt.Errorf("failed order score: %w", err) 36 | } 37 | 38 | score += v 39 | } 40 | 41 | return score, nil 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 glassmonkey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /parser/rule.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | const ( 9 | Rule = Algorithm("rule") 10 | separateConditionCount = 2 11 | ) 12 | 13 | func NewRuleBaseParser() RuleBaseParser { 14 | re := regexp.MustCompile(`\p{Han}+`) 15 | 16 | return RuleBaseParser{ 17 | re: re, 18 | } 19 | } 20 | 21 | type RuleBaseParser struct { 22 | re *regexp.Regexp 23 | } 24 | 25 | // Parse referer: https://github.com/rskmoi/namedivider-python/blob/master/namedivider/name_divider.py#L238 26 | func (p RuleBaseParser) Parse(fullname FullName, separator Separator) (DividedName, error) { 27 | length := fullname.Length() 28 | 29 | if length == minNameLength { 30 | l, f, err := fullname.Split(1) 31 | if err != nil { 32 | return DividedName{}, fmt.Errorf("rule parser error: %w", err) 33 | } 34 | 35 | return DividedName{ 36 | FirstName: f, 37 | LastName: l, 38 | Separator: separator, 39 | Score: 1, 40 | Algorithm: Rule, 41 | }, nil 42 | } 43 | 44 | isKanjiList := make([]bool, length) 45 | 46 | for i, c := range fullname.Slice() { 47 | isKanji := p.re.MatchString(string(c)) 48 | isKanjiList[i] = isKanji 49 | 50 | if i >= separateConditionCount { 51 | if isKanjiList[0] != isKanji && isKanjiList[i-1] == isKanji { 52 | l, f, err := fullname.Split(i - 1) 53 | if err != nil { 54 | return DividedName{}, fmt.Errorf("rule parser error: %w", err) 55 | } 56 | 57 | return DividedName{ 58 | FirstName: f, 59 | LastName: l, 60 | Separator: separator, 61 | Score: 1, 62 | Algorithm: Rule, 63 | }, nil 64 | } 65 | } 66 | } 67 | //nolint:exhaustivestruct 68 | return DividedName{}, nil 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seimei 2 | 3 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) 4 | [![test](https://github.com/glassmonkey/seimei/workflows/test/badge.svg)](https://github.com/glassmonkey/seimei/actions?query=workflow%3Atest) 5 | [![reviewdog](https://github.com/glassmonkey/seimei/workflows/reviewdog/badge.svg)](https://github.com/glassmonkey/seimei/actions?query=workflow%3Areviewdog) 6 | 7 | **seimei** is a Go port of a tool ([namedivider-python](https://github.com/rskmoi/namedivider-python)) created in python to split Japanese first and last names. 8 | 9 | For implementation details, please check ([namedivider-python](https://github.com/rskmoi/namedivider-python)) from which porting. 10 | 11 | 12 | # Installation 13 | 14 | ```bash 15 | go install github.com/glassmonkey/seimei/v2/cmd/seimei@latest 16 | ``` 17 | 18 | # Usage 19 | 20 | ## Options 21 | 22 | ```bash 23 | $ seimei -h 24 | Usage: 25 | seimei [flags] 26 | seimei [command] 27 | 28 | Available Commands: 29 | name It parse single full name. 30 | file It bulk parse full name lit in the file. 31 | help Help about any command 32 | 33 | Flags: 34 | -h, --help help for seimei 35 | -v, --version version for seimei 36 | 37 | Use "seimei [command] --help" for more information about a command 38 | 39 | ``` 40 | 41 | ## Example 42 | 43 | ```bash 44 | $ seimei name --name 竈門炭治郎 45 | 竈門 炭治郎 46 | 47 | $ seimei name --name 竈門禰豆子 --parse @ 48 | 竈門@禰豆子 49 | ``` 50 | 51 | ``` 52 | $ cat /tmp/kimetsu.txt 53 | 竈門炭治郎 54 | 竈門禰豆子 55 | 我妻善逸 56 | 嘴平伊之助 57 | 58 | $ seimei file --file /tmp/kimetsu.txt 59 | 竈門 炭治郎 60 | 竈門 禰豆子 61 | 我妻 善逸 62 | 嘴平 伊之助 63 | 64 | $ seimei file --file /tmp/kimetsu.txt --parse @ 65 | 竈門@炭治郎 66 | 竈門@禰豆子 67 | 我妻@善逸 68 | 嘴平@伊之助 69 | ``` 70 | 71 | # License 72 | [Mit](LICENSE) 73 | 74 | # Author 75 | glassmonkey([@glassmonekey](https://twitter.com/glassmonekey)) 76 | 77 | -------------------------------------------------------------------------------- /benchmark/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package benchmark_test 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "os/exec" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/glassmonkey/seimei/v2" 12 | ) 13 | 14 | //go:embed sample.csv 15 | // generate from https://testdata.userlocal.jp 16 | var testdata string 17 | 18 | func TestRunCompareOrigin(t *testing.T) { 19 | tests := strings.Split(testdata, "\n") 20 | total := len(tests) 21 | 22 | t.Parallel() 23 | // nolint: paralleltest 24 | for i, tt := range tests { 25 | tt := tt 26 | i := i 27 | 28 | t.Run(tt, func(t *testing.T) { 29 | t.Parallel() 30 | out := &bytes.Buffer{} 31 | input := strings.ReplaceAll(tt, " ", "") 32 | origin, err := exec.Command("nmdiv", "name", input).Output() 33 | if err != nil { 34 | t.Fatalf("happen error: %v", err) 35 | } 36 | err = seimei.Run(out, input, " ") 37 | if err != nil { 38 | t.Fatalf("happen error: %v", err) 39 | } 40 | if out.String() != string(origin) { 41 | t.Errorf("failed to test diff from origin. got: %s, origin: %s", out, origin) 42 | } 43 | t.Logf("ok: %s, %d/%d", tt, i, total) 44 | }) 45 | } 46 | } 47 | 48 | func TestRunCompareAnswer(t *testing.T) { 49 | t.Parallel() 50 | 51 | tests := strings.Split(testdata, "\n") 52 | total := len(tests) 53 | // nolint: paralleltest 54 | for i, tt := range tests { 55 | tt := tt 56 | 57 | if len(tt) == 0 { 58 | continue 59 | } 60 | 61 | i := i 62 | 63 | t.Run(tt, func(t *testing.T) { 64 | t.Parallel() 65 | orig := tt 66 | out := &bytes.Buffer{} 67 | input := strings.ReplaceAll(orig, " ", "") 68 | want := fmt.Sprintf("%s\n", orig) 69 | err := seimei.Run(out, input, " ") 70 | if err != nil { 71 | t.Fatalf("happen error: %v", err) 72 | } 73 | if out.String() != want { 74 | t.Errorf("failed to test diff correct answer. got: %s, want: %s", out, want) 75 | } 76 | t.Logf("ok: %s, %d/%d", tt, i, total) 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /parser/statistics_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/glassmonkey/seimei/v2" 7 | "github.com/glassmonkey/seimei/v2/parser" 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestStatisticsParser_Parse(t *testing.T) { 12 | t.Parallel() 13 | 14 | type testdata struct { 15 | name string 16 | input parser.FullName 17 | want parser.DividedName 18 | } 19 | 20 | separator := parser.Separator("/") 21 | tests := []testdata{ 22 | { 23 | name: "3文字", 24 | input: "菅義偉", 25 | want: parser.DividedName{ 26 | LastName: "菅", 27 | FirstName: "義偉", 28 | Separator: separator, 29 | Score: 0.48027055739279506, 30 | Algorithm: parser.Statistics, 31 | }, 32 | }, 33 | { 34 | name: "4文字", 35 | input: "阿部晋三", 36 | want: parser.DividedName{ 37 | LastName: "阿部", 38 | FirstName: "晋三", 39 | Separator: separator, 40 | Score: 0.47397644480584417, 41 | Algorithm: parser.Statistics, 42 | }, 43 | }, 44 | { 45 | name: "5文字", 46 | input: "中曽根康弘", 47 | want: parser.DividedName{ 48 | LastName: "中曽根", 49 | FirstName: "康弘", 50 | Separator: separator, 51 | Score: 0.3127240879300895, 52 | Algorithm: parser.Statistics, 53 | }, 54 | }, 55 | { 56 | name: "すべてひらがな", 57 | input: "やまだはなこ", 58 | want: parser.DividedName{ 59 | LastName: "や", 60 | FirstName: "まだはなこ", 61 | Separator: separator, 62 | Score: 0.16666666666666666, 63 | Algorithm: parser.Statistics, 64 | }, 65 | }, 66 | } 67 | 68 | for _, tt := range tests { 69 | tt := tt 70 | t.Run(tt.name, func(t *testing.T) { 71 | t.Parallel() 72 | sut := parser.NewStatisticsParser(seimei.InitKanjiFeatureManager()) 73 | got, err := sut.Parse(tt.input, separator) 74 | if err != nil { 75 | t.Errorf("error is not nil, err=%v", err) 76 | } 77 | 78 | if diff := cmp.Diff(got, tt.want); diff != "" { 79 | t.Errorf("divided name mismatch (-got +want):\n%s", diff) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /parser/rule_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/glassmonkey/seimei/v2/parser" 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestRuleBaseParser_Parse(t *testing.T) { 11 | t.Parallel() 12 | 13 | type testdata struct { 14 | name string 15 | input parser.FullName 16 | want parser.DividedName 17 | } 18 | 19 | separator := parser.Separator("/") 20 | tests := []testdata{ 21 | { 22 | name: "2文字の場合", 23 | input: "乙一", 24 | want: parser.DividedName{ 25 | LastName: "乙", 26 | FirstName: "一", 27 | Separator: separator, 28 | Score: 1, 29 | Algorithm: parser.Rule, 30 | }, 31 | }, 32 | { 33 | name: "カタカナの名前の場合", 34 | input: "中山マサ", 35 | want: parser.DividedName{ 36 | LastName: "中山", 37 | FirstName: "マサ", 38 | Separator: separator, 39 | Score: 1, 40 | Algorithm: parser.Rule, 41 | }, 42 | }, 43 | { 44 | name: "名字にカタカナを含む場合で名前がカタカナ", 45 | input: "関ヶ原マサ", 46 | want: parser.DividedName{ 47 | LastName: "関ヶ原", 48 | FirstName: "マサ", 49 | Separator: separator, 50 | Score: 1, 51 | Algorithm: parser.Rule, 52 | }, 53 | }, 54 | { 55 | name: "名字にカタカナを含む名前が漢字の場合", 56 | input: "関ヶ原太郎", 57 | //nolint:exhaustivestruct 58 | want: parser.DividedName{}, 59 | }, 60 | { 61 | name: "フルネームが漢字の場合", 62 | input: "中山太郎", 63 | //nolint:exhaustivestruct 64 | want: parser.DividedName{}, 65 | }, 66 | { 67 | name: "名前がひらがなの場合", 68 | input: "平塚らいてう", 69 | want: parser.DividedName{ 70 | LastName: "平塚", 71 | FirstName: "らいてう", 72 | Separator: separator, 73 | Score: 1, 74 | Algorithm: parser.Rule, 75 | }, 76 | }, 77 | } 78 | 79 | for _, tt := range tests { 80 | tt := tt 81 | t.Run(tt.name, func(t *testing.T) { 82 | t.Parallel() 83 | sut := parser.NewRuleBaseParser() 84 | got, err := sut.Parse(tt.input, separator) 85 | if err != nil { 86 | t.Errorf("error is not nil, err=%v", err) 87 | } 88 | 89 | if diff := cmp.Diff(got, tt.want); diff != "" { 90 | t.Errorf("divided name mismatch (-got +want):\n%s", diff) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /parser/statistics.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/glassmonkey/seimei/v2/feature" 7 | ) 8 | 9 | const ( 10 | Statistics = Algorithm("statistics") 11 | ) 12 | 13 | func NewStatisticsParser(m feature.KanjiFeatureManager) StatisticsParser { 14 | return StatisticsParser{ 15 | OrderCalculator: feature.KanjiOrderFeatureCalculator{ 16 | Manager: m, 17 | }, 18 | LengthCalculator: feature.KanjiLengthFeatureCalculator{ 19 | Manager: m, 20 | }, 21 | } 22 | } 23 | 24 | type StatisticsParser struct { 25 | OrderCalculator feature.KanjiOrderFeatureCalculator 26 | LengthCalculator feature.KanjiLengthFeatureCalculator 27 | } 28 | 29 | func (s StatisticsParser) Parse(fullname FullName, separator Separator) (DividedName, error) { 30 | ms := 0.0 31 | mi := 1 32 | features := feature.Features{} 33 | 34 | for i := range fullname.Slice() { 35 | l, f, err := fullname.Split(i) 36 | if err != nil { 37 | return DividedName{}, fmt.Errorf("parse error: %w", err) 38 | } 39 | 40 | cs, err := s.score(l, f) 41 | if err != nil { 42 | return DividedName{}, fmt.Errorf("parse error: %w", err) 43 | } 44 | 45 | features = append(features, cs) 46 | 47 | if cs > ms { 48 | ms = cs 49 | mi = i 50 | } 51 | } 52 | 53 | l, f, err := fullname.Split(mi) 54 | if err != nil { 55 | return DividedName{}, fmt.Errorf("parse error: %w", err) 56 | } 57 | 58 | return DividedName{ 59 | FirstName: f, 60 | LastName: l, 61 | Separator: separator, 62 | Score: features.SoftMax()[mi], 63 | Algorithm: Statistics, 64 | }, nil 65 | } 66 | 67 | const orderOnlyScoreLength = 4 68 | 69 | // Score referer: https://github.com/rskmoi/namedivider-python/blob/master/namedivider/name_divider.py#L206 70 | func (s StatisticsParser) score(lastName LastName, firstName FirstName) (float64, error) { 71 | fullname := JoinName(lastName, firstName) 72 | 73 | ols, err := s.OrderCalculator.Score(lastName, fullname.Length()) 74 | if err != nil { 75 | return 0, fmt.Errorf("failed Order Score: %w", err) 76 | } 77 | 78 | ofs, err := s.OrderCalculator.Score(firstName, fullname.Length()) 79 | if err != nil { 80 | return 0, fmt.Errorf("failed Order Score: %w", err) 81 | } 82 | 83 | os := (ols + ofs) / (float64(fullname.Length()) - minNameLength) 84 | // https://github.com/rskmoi/namedivider-python/blob/d87a488d4696bc26d2f6444ed399d83a6a1911a7/namedivider/name_divider.py#L219 85 | if fullname.Length() == orderOnlyScoreLength { 86 | return os, nil 87 | } 88 | 89 | lls, err := s.LengthCalculator.Score(lastName, fullname.Length()) 90 | if err != nil { 91 | return 0, fmt.Errorf("failed Length Score: %w", err) 92 | } 93 | 94 | lfs, err := s.LengthCalculator.Score(firstName, fullname.Length()) 95 | if err != nil { 96 | return 0, fmt.Errorf("failed Length Score: %w", err) 97 | } 98 | 99 | ls := (lls + lfs) / float64(fullname.Length()) 100 | 101 | return (os + ls) / 2, nil 102 | } 103 | -------------------------------------------------------------------------------- /namedivider-python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "unicode/utf8" 7 | 8 | "github.com/glassmonkey/seimei/v2/feature" 9 | ) 10 | 11 | type Algorithm string 12 | 13 | const ( 14 | Dummy = Algorithm("dummy") 15 | minNameLength = 2 16 | ) 17 | 18 | var ( 19 | ErrNameLength = errors.New("name length needs at least 2 chars") 20 | ErrSplitPosition = errors.New("split position is invalid") 21 | ErrParserNotWorking = errors.New("the name has an unexpected string, so the division failed") 22 | ) 23 | 24 | type Parser interface { 25 | Parse(fullname FullName, separator Separator) (DividedName, error) 26 | } 27 | type FullName string 28 | 29 | type FirstName string 30 | 31 | func (n FirstName) IsLastName() bool { 32 | return false 33 | } 34 | 35 | func (n FirstName) Length() int { 36 | return utf8.RuneCountInString(string(n)) 37 | } 38 | 39 | func (n FirstName) Slice() []rune { 40 | return []rune(n) 41 | } 42 | 43 | type LastName string 44 | 45 | func (n LastName) IsLastName() bool { 46 | return true 47 | } 48 | 49 | func (n LastName) Slice() []rune { 50 | return []rune(n) 51 | } 52 | 53 | func (n LastName) Length() int { 54 | return utf8.RuneCountInString(string(n)) 55 | } 56 | 57 | func JoinName(lastName LastName, firstName FirstName) FullName { 58 | return FullName(string(lastName) + string(firstName)) 59 | } 60 | 61 | func (f FullName) Length() int { 62 | return utf8.RuneCountInString(string(f)) 63 | } 64 | 65 | func (f FullName) Split(position int) (LastName, FirstName, error) { 66 | length := f.Length() 67 | 68 | if position < 0 { 69 | return "", "", fmt.Errorf("%w: position(=%d) must be positive", ErrSplitPosition, position) 70 | } 71 | 72 | if length < position { 73 | return "", "", fmt.Errorf("%w: position(=%d) is over text length(=%d)", ErrSplitPosition, position, length) 74 | } 75 | 76 | return LastName([]rune(f)[:position]), FirstName([]rune(f)[position:]), nil 77 | } 78 | 79 | func (f FullName) Slice() []rune { 80 | return []rune(f) 81 | } 82 | 83 | type Separator string 84 | 85 | type NameParser struct { 86 | Parsers []Parser 87 | Separator Separator 88 | } 89 | 90 | func NewNameParser(separatorString Separator, m feature.KanjiFeatureManager) NameParser { 91 | s := make([]Parser, 0) 92 | s = append(s, NewRuleBaseParser()) 93 | s = append(s, NewStatisticsParser(m)) 94 | 95 | return NameParser{ 96 | Parsers: s, 97 | Separator: separatorString, 98 | } 99 | } 100 | 101 | func (n NameParser) Parse(fullname FullName) (DividedName, error) { 102 | if err := n.validate(fullname); err != nil { 103 | return DividedName{}, fmt.Errorf("parse error: %w", err) 104 | } 105 | 106 | for _, p := range n.Parsers { 107 | v, err := p.Parse(fullname, n.Separator) 108 | if err != nil { 109 | return DividedName{}, fmt.Errorf("parse error: %w", err) 110 | } 111 | 112 | if !v.IsZero() { 113 | return v, nil 114 | } 115 | } 116 | // Currently, the process here is not reached. 117 | // The statistics parser covers all the remains. 118 | return DividedName{}, ErrParserNotWorking 119 | } 120 | 121 | func (n NameParser) validate(fullname FullName) error { 122 | if fullname.Length() < minNameLength { 123 | return ErrNameLength 124 | } 125 | 126 | return nil 127 | } 128 | 129 | type DividedName struct { 130 | FirstName FirstName 131 | LastName LastName 132 | Separator Separator 133 | Score float64 134 | Algorithm Algorithm 135 | } 136 | 137 | func (n DividedName) String() string { 138 | return string(n.LastName) + string(n.Separator) + string(n.FirstName) 139 | } 140 | 141 | //nolint:exhaustivestruct 142 | func (n DividedName) IsZero() bool { 143 | return n == DividedName{} 144 | } 145 | -------------------------------------------------------------------------------- /semei.go: -------------------------------------------------------------------------------- 1 | package seimei 2 | 3 | import ( 4 | // Using embed. 5 | _ "embed" 6 | "encoding/csv" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "os" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/glassmonkey/seimei/v2/feature" 15 | "github.com/glassmonkey/seimei/v2/parser" 16 | ) 17 | 18 | type ( 19 | Name string 20 | ParseString string 21 | Path string 22 | ) 23 | 24 | //go:embed namedivider-python/assets/kanji.csv 25 | var assets string 26 | 27 | func InitNameParser(parseString ParseString, manager feature.KanjiFeatureManager) parser.NameParser { 28 | return parser.NewNameParser(parser.Separator(parseString), manager) 29 | } 30 | 31 | func InitKanjiFeatureManager() feature.KanjiFeatureManager { 32 | r := csv.NewReader(strings.NewReader(assets)) 33 | m := make(map[feature.Character]feature.KanjiFeature) 34 | 35 | for i := 0; ; i++ { 36 | record, err := r.Read() 37 | if errors.Is(err, io.EOF) { 38 | break 39 | } 40 | 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | if i == 0 { 46 | continue 47 | } 48 | 49 | c := feature.Character(record[0]) 50 | 51 | var os, ls []float64 52 | 53 | for _, o := range record[feature.CharacterFeatureSize : feature.CharacterFeatureSize+feature.OrderFeatureSize] { 54 | s, err := strconv.ParseFloat(o, 64) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | os = append(os, s) 60 | } 61 | 62 | for _, l := range record[feature.CharacterFeatureSize+feature.OrderFeatureSize : feature.CharacterFeatureSize+feature.OrderFeatureSize+feature.LengthFeatureSize] { 63 | s, err := strconv.ParseFloat(l, 64) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | ls = append(ls, s) 69 | } 70 | 71 | kf, err := feature.NewKanjiFeature(c, os, ls) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | m[c] = kf 77 | } 78 | 79 | return feature.KanjiFeatureManager{ 80 | KanjiFeatureMap: m, 81 | } 82 | } 83 | 84 | func InitReader(path Path) (*csv.Reader, error) { 85 | f, err := os.Open(string(path)) 86 | if err != nil { 87 | return nil, fmt.Errorf("fatal error file load: %w", err) 88 | } 89 | 90 | return csv.NewReader(f), nil 91 | } 92 | 93 | func ParseName(out, stderr io.Writer, fullname Name, parseString ParseString) error { 94 | m := InitKanjiFeatureManager() 95 | p := InitNameParser(parseString, m) 96 | 97 | name, err := p.Parse(parser.FullName(fullname)) 98 | if err != nil { 99 | _, err := fmt.Fprintf(stderr, "%s\n", err.Error()) 100 | if err != nil { 101 | return fmt.Errorf("happen error write stderr: %w", err) 102 | } 103 | return nil 104 | } 105 | 106 | _, err = fmt.Fprintf(out, "%s\n", name.String()) 107 | if err != nil { 108 | return fmt.Errorf("happen error write stdout: %w", err) 109 | } 110 | 111 | return nil 112 | } 113 | 114 | func ParseFile(out, stderr io.Writer, path Path, parseString ParseString) error { 115 | m := InitKanjiFeatureManager() 116 | p := InitNameParser(parseString, m) 117 | 118 | r, err := InitReader(path) 119 | if err != nil { 120 | return fmt.Errorf("happen error load file: %w", err) 121 | } 122 | 123 | for c := 1; ; c++ { 124 | record, err := r.Read() 125 | 126 | if errors.Is(err, io.EOF) { 127 | break 128 | } 129 | 130 | if err != nil { 131 | fmt.Fprintf(stderr, "load line error on line %d: %v\n", c, err) 132 | continue 133 | } 134 | 135 | if len(record) != 1 { 136 | fmt.Fprintf(stderr, "format error on line %d: %v\n", c, record) 137 | continue 138 | } 139 | 140 | name, err := p.Parse(parser.FullName(record[0])) 141 | if err != nil { 142 | fmt.Fprintf(stderr, "parse error on line %d: %v\n", c, err) 143 | continue 144 | } 145 | 146 | fmt.Fprintf(out, "%s\n", name.String()) 147 | } 148 | 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /cli.go: -------------------------------------------------------------------------------- 1 | package seimei 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | // Using embed. 8 | _ "embed" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var ( 14 | //go:embed version.txt 15 | VersionText string 16 | ErrEmptyName = errors.New("provide name is empty (ex. 田中太郎)") 17 | ErrInvalidName = errors.New("provide path is invalid") 18 | ErrEmptyPath = errors.New("provide path is empty (ex. /tmp/foo.csv)") 19 | ErrInvalidPath = errors.New("provide path is invalid") 20 | ErrInvalidParseString = errors.New("provide parse string is invalid") 21 | ) 22 | 23 | type CmdMode string 24 | 25 | func (c CmdMode) String() string { 26 | return string(c) 27 | } 28 | 29 | const ( 30 | NameCmd CmdMode = "name" 31 | FileCmd CmdMode = "file" 32 | ParseOption string = "parse" 33 | ) 34 | 35 | func BuildMainCmd() *cobra.Command { 36 | c := cobra.Command{ 37 | Use: "seimei", 38 | CompletionOptions: cobra.CompletionOptions{ 39 | DisableDefaultCmd: true, 40 | }, 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | return cmd.Usage() 43 | }, 44 | Version: fmt.Sprintf(VersionText), 45 | } 46 | cobra.EnableCommandSorting = false 47 | c.AddCommand(BuildNameCmd()) 48 | c.AddCommand(BuildFileCmd()) 49 | return &c 50 | } 51 | 52 | func BuildNameCmd() *cobra.Command { 53 | c := cobra.Command{ 54 | Use: "name", 55 | Short: "It parse single full name.", 56 | Long: `It parse single full name. 57 | Provide the full name to be parsed with the required flag (--name). 58 | `, 59 | Example: "seimei name --name 田中太郎", 60 | RunE: func(cmd *cobra.Command, args []string) error { 61 | n, err := detectFlagForName(cmd) 62 | if err != nil { 63 | return fmt.Errorf("flag parse error: %w", err) 64 | } 65 | p, err := detectFlagParseString(cmd) 66 | if err != nil { 67 | return fmt.Errorf("flag parse error: %w", err) 68 | } 69 | return ParseName(cmd.OutOrStdout(), cmd.OutOrStderr(), n, p) 70 | }, 71 | } 72 | c.Flags().SortFlags = false 73 | c.Flags().StringP(NameCmd.String(), "n", "", "田中太郎") 74 | err := c.MarkFlagRequired(NameCmd.String()) 75 | // since name flag is set on above, it raise panic without returning an error. 76 | if err != nil { 77 | panic(err) 78 | } 79 | c.Flags().StringP(ParseOption, "p", " ", " ") 80 | return &c 81 | } 82 | 83 | func BuildFileCmd() *cobra.Command { 84 | c := cobra.Command{ 85 | Use: "file", 86 | Short: "It bulk parse full name lit in the file.", 87 | Long: `It bulk parse full name lit in the file. 88 | Provide the file path with full name list to the required flag (--file). 89 | `, 90 | Example: "seimei file --file /path/to/dir/foo.csv", 91 | RunE: func(cmd *cobra.Command, args []string) error { 92 | f, err := detectFlagForFile(cmd) 93 | if err != nil { 94 | return fmt.Errorf("flag parse error: %w", err) 95 | } 96 | p, err := detectFlagParseString(cmd) 97 | if err != nil { 98 | return fmt.Errorf("flag parse error: %w", err) 99 | } 100 | return ParseFile(cmd.OutOrStdout(), cmd.ErrOrStderr(), f, p) 101 | }, 102 | } 103 | c.Flags().SortFlags = false 104 | c.Flags().StringP(FileCmd.String(), "f", "", "/path/to/dir/foo.csv") 105 | err := c.MarkFlagRequired(FileCmd.String()) 106 | // since file flag is set on above, it raise panic without returning an error. 107 | if err != nil { 108 | panic(err) 109 | } 110 | c.Flags().StringP(ParseOption, "p", " ", " ") 111 | return &c 112 | } 113 | 114 | func Run() error { 115 | cmd := BuildMainCmd() 116 | return cmd.Execute() 117 | } 118 | 119 | func detectFlagForName(cmd *cobra.Command) (Name, error) { 120 | n, err := cmd.Flags().GetString(NameCmd.String()) 121 | if err != nil { 122 | return "", ErrInvalidName 123 | } 124 | if n == "" { 125 | return "", ErrEmptyName 126 | } 127 | 128 | return Name(n), nil 129 | } 130 | 131 | func detectFlagForFile(cmd *cobra.Command) (Path, error) { 132 | n, err := cmd.Flags().GetString(FileCmd.String()) 133 | if err != nil { 134 | return "", ErrInvalidPath 135 | } 136 | if n == "" { 137 | return "", ErrEmptyPath 138 | } 139 | 140 | return Path(n), nil 141 | } 142 | 143 | func detectFlagParseString(cmd *cobra.Command) (ParseString, error) { 144 | p, err := cmd.Flags().GetString(ParseOption) 145 | if err != nil { 146 | return "", ErrInvalidParseString 147 | } 148 | return ParseString(p), nil 149 | } 150 | -------------------------------------------------------------------------------- /feature/length_test.go: -------------------------------------------------------------------------------- 1 | package feature_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/glassmonkey/seimei/v2" 8 | "github.com/glassmonkey/seimei/v2/feature" 9 | "github.com/glassmonkey/seimei/v2/parser" 10 | "github.com/google/go-cmp/cmp" 11 | ) 12 | 13 | func TestKanjiLengthFeatureCalculator_ScoreWithStub(t *testing.T) { 14 | t.Parallel() 15 | 16 | type testdata struct { 17 | name string 18 | inputName feature.PartOfNameCharacters 19 | inputFullNameLength int 20 | wantScore float64 21 | wantErr error 22 | } 23 | 24 | tests := []testdata{ 25 | { 26 | name: "名字", 27 | inputName: parser.FirstName("冬馬"), 28 | inputFullNameLength: 5, 29 | wantScore: 0.5, 30 | wantErr: nil, 31 | }, 32 | { 33 | name: "名前", 34 | inputName: parser.LastName("天ケ瀬"), 35 | inputFullNameLength: 5, 36 | wantScore: 0.75, 37 | wantErr: nil, 38 | }, 39 | { 40 | name: "設定ファイルがない名前の場合は0", 41 | inputName: parser.LastName("太郎"), 42 | inputFullNameLength: 4, 43 | wantScore: 0, 44 | wantErr: nil, 45 | }, 46 | { 47 | name: "フルネームと同じサイズ指定の場合はスコアは0", 48 | inputName: parser.LastName("天ケ瀬"), 49 | inputFullNameLength: 3, 50 | wantScore: 0, 51 | wantErr: nil, 52 | }, 53 | { 54 | name: "指定文字列がフルネームより大きい場合はマスクデータの作成でエラーになる", 55 | inputName: parser.LastName("天ケ瀬"), 56 | inputFullNameLength: 2, 57 | wantScore: 0, 58 | wantErr: feature.ErrOutRangeOrderMask, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | tt := tt 64 | t.Run(tt.name, func(t *testing.T) { 65 | t.Parallel() 66 | sut := feature.KanjiLengthFeatureCalculator{ 67 | Manager: stubKanjiManagerForLengthFeature(), 68 | } 69 | got, err := sut.Score(tt.inputName, tt.inputFullNameLength) 70 | if !errors.Is(err, tt.wantErr) { 71 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 72 | } 73 | if tt.wantErr != nil { 74 | return 75 | } 76 | 77 | if diff := cmp.Diff(got, tt.wantScore); diff != "" { 78 | t.Errorf("score value mismatch (-got +want):\n%s", diff) 79 | } 80 | }) 81 | } 82 | } 83 | 84 | //nolint:dupl 85 | func TestKanjiLengthFeatureCalculator_ScoreWithCSVData(t *testing.T) { 86 | t.Parallel() 87 | 88 | type testdata struct { 89 | name string 90 | inputName feature.PartOfNameCharacters 91 | inputFullNameLength int 92 | wantScore float64 93 | wantErr error 94 | } 95 | 96 | tests := []testdata{ 97 | { 98 | name: "新海誠(名前)", 99 | inputName: parser.FirstName("誠"), 100 | inputFullNameLength: 3, 101 | wantScore: 0.5414201183431953, 102 | wantErr: nil, 103 | }, 104 | { 105 | name: "新海誠(名字)", 106 | inputName: parser.LastName("新海"), 107 | inputFullNameLength: 3, 108 | wantScore: 1.6721919841662545, 109 | wantErr: nil, 110 | }, 111 | { 112 | name: "清武弘嗣(名前)", 113 | inputName: parser.FirstName("弘嗣"), 114 | inputFullNameLength: 4, 115 | wantScore: 1.982873228774868, 116 | wantErr: nil, 117 | }, 118 | { 119 | name: "清武弘嗣(名字)", 120 | inputName: parser.LastName("清武"), 121 | inputFullNameLength: 4, 122 | wantScore: 1.9431977559607292, 123 | wantErr: nil, 124 | }, 125 | } 126 | 127 | for _, tt := range tests { 128 | tt := tt 129 | t.Run(tt.name, func(t *testing.T) { 130 | t.Parallel() 131 | sut := feature.KanjiLengthFeatureCalculator{ 132 | Manager: seimei.InitKanjiFeatureManager(), 133 | } 134 | got, err := sut.Score(tt.inputName, tt.inputFullNameLength) 135 | if !errors.Is(err, tt.wantErr) { 136 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 137 | } 138 | if tt.wantErr != nil { 139 | return 140 | } 141 | 142 | if diff := cmp.Diff(got, tt.wantScore); diff != "" { 143 | t.Errorf("score value mismatch (-got +want):\n%s", diff) 144 | } 145 | }) 146 | } 147 | } 148 | 149 | func stubKanjiManagerForLengthFeature() feature.KanjiFeatureManager { 150 | o := feature.Features{1, 1, 1, 1, 1, 1} 151 | l := feature.Features{1, 1, 1, 1, 1, 1, 1, 1} 152 | 153 | return feature.KanjiFeatureManager{ 154 | KanjiFeatureMap: map[feature.Character]feature.KanjiFeature{ 155 | "冬": {Character: "冬", Order: o, Length: l}, 156 | "馬": {Character: "馬", Order: o, Length: l}, 157 | "天": {Character: "天", Order: o, Length: l}, 158 | "ケ": {Character: "ケ", Order: o, Length: l}, 159 | "瀬": {Character: "瀬", Order: o, Length: l}, 160 | }, 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /feature/order_test.go: -------------------------------------------------------------------------------- 1 | package feature_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/glassmonkey/seimei/v2" 8 | "github.com/glassmonkey/seimei/v2/feature" 9 | "github.com/glassmonkey/seimei/v2/parser" 10 | "github.com/google/go-cmp/cmp" 11 | ) 12 | 13 | func TestKanjiOrderFeatureCalculator_ScoreWithStub(t *testing.T) { 14 | t.Parallel() 15 | 16 | type testdata struct { 17 | name string 18 | inputName feature.PartOfNameCharacters 19 | inputFullNameLength int 20 | wantScore float64 21 | wantErr error 22 | } 23 | 24 | tests := []testdata{ 25 | { 26 | name: "名字", 27 | inputName: parser.FirstName("冬馬"), 28 | inputFullNameLength: 5, 29 | wantScore: 1.0 / 3, // 1/4 30 | wantErr: nil, 31 | }, 32 | { 33 | name: "名前", 34 | inputName: parser.LastName("天ケ瀬"), 35 | inputFullNameLength: 5, 36 | wantScore: 0.5833333333333333, // 1/4 + 1/3 37 | wantErr: nil, 38 | }, 39 | { 40 | name: "設定ファイルがない名前の場合は0", 41 | inputName: parser.LastName("太郎"), 42 | inputFullNameLength: 4, 43 | wantScore: 0, 44 | wantErr: nil, 45 | }, 46 | { 47 | name: "フルネームと同じサイズ指定の場合はスコアは0", 48 | inputName: parser.LastName("天ケ瀬"), 49 | inputFullNameLength: 3, 50 | wantScore: 0, 51 | wantErr: nil, 52 | }, 53 | { 54 | name: "指定文字列がフルネームより大きい場合はマスクデータの作成でエラーになる", 55 | inputName: parser.LastName("天ケ瀬"), 56 | inputFullNameLength: 2, 57 | wantScore: 0, 58 | wantErr: feature.ErrOutRangeOrderMask, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | tt := tt 64 | t.Run(tt.name, func(t *testing.T) { 65 | t.Parallel() 66 | sut := feature.KanjiOrderFeatureCalculator{ 67 | Manager: stubKanjiManagerForOrderFeature(), 68 | } 69 | got, err := sut.Score(tt.inputName, tt.inputFullNameLength) 70 | if !errors.Is(err, tt.wantErr) { 71 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 72 | } 73 | if tt.wantErr != nil { 74 | return 75 | } 76 | 77 | if diff := cmp.Diff(got, tt.wantScore); diff != "" { 78 | t.Errorf("score value mismatch (-got +want):\n%s", diff) 79 | } 80 | }) 81 | } 82 | } 83 | 84 | //nolint:dupl 85 | func TestKanjiOrderFeatureCalculator_ScoreWithCSVData(t *testing.T) { 86 | t.Parallel() 87 | 88 | type testdata struct { 89 | name string 90 | inputName feature.PartOfNameCharacters 91 | inputFullNameLength int 92 | wantScore float64 93 | wantErr error 94 | } 95 | 96 | tests := []testdata{ 97 | { 98 | name: "新海誠(名前)", 99 | inputName: parser.FirstName("誠"), 100 | inputFullNameLength: 3, 101 | wantScore: 0, 102 | wantErr: nil, 103 | }, 104 | { 105 | name: "新海誠(名字)", 106 | inputName: parser.LastName("新海"), 107 | inputFullNameLength: 3, 108 | wantScore: 0.8305084745762712, 109 | wantErr: nil, 110 | }, 111 | { 112 | name: "清武弘嗣(名前)", 113 | inputName: parser.FirstName("弘嗣"), 114 | inputFullNameLength: 4, 115 | wantScore: 0.9919571045576407, 116 | wantErr: nil, 117 | }, 118 | { 119 | name: "清武弘嗣(名字)", 120 | inputName: parser.LastName("清武"), 121 | inputFullNameLength: 4, 122 | wantScore: 0.2222222222222222, 123 | wantErr: nil, 124 | }, 125 | } 126 | 127 | for _, tt := range tests { 128 | tt := tt 129 | t.Run(tt.name, func(t *testing.T) { 130 | t.Parallel() 131 | sut := feature.KanjiOrderFeatureCalculator{ 132 | Manager: seimei.InitKanjiFeatureManager(), 133 | } 134 | got, err := sut.Score(tt.inputName, tt.inputFullNameLength) 135 | if !errors.Is(err, tt.wantErr) { 136 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 137 | } 138 | if tt.wantErr != nil { 139 | return 140 | } 141 | 142 | if diff := cmp.Diff(got, tt.wantScore); diff != "" { 143 | t.Errorf("score value mismatch (-got +want):\n%s", diff) 144 | } 145 | }) 146 | } 147 | } 148 | 149 | func stubKanjiManagerForOrderFeature() feature.KanjiFeatureManager { 150 | o := feature.Features{1, 1, 1, 1, 1, 1} 151 | l := feature.Features{1, 1, 1, 1, 1, 1, 1, 1} 152 | 153 | return feature.KanjiFeatureManager{ 154 | KanjiFeatureMap: map[feature.Character]feature.KanjiFeature{ 155 | "冬": {Character: "冬", Order: o, Length: l}, 156 | "馬": {Character: "馬", Order: o, Length: l}, 157 | "天": {Character: "天", Order: o, Length: l}, 158 | "ケ": {Character: "ケ", Order: o, Length: l}, 159 | "瀬": {Character: "瀬", Order: o, Length: l}, 160 | }, 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/glassmonkey/seimei/v2" 8 | "github.com/glassmonkey/seimei/v2/feature" 9 | "github.com/glassmonkey/seimei/v2/parser" 10 | "github.com/google/go-cmp/cmp" 11 | ) 12 | 13 | func TestNameParser_Parse(t *testing.T) { 14 | t.Parallel() 15 | 16 | type testdata struct { 17 | name string 18 | input parser.FullName 19 | want parser.DividedName 20 | } 21 | 22 | separator := parser.Separator("/") 23 | tests := []testdata{ 24 | { 25 | name: "原敬", 26 | input: "原敬", 27 | want: parser.DividedName{ 28 | LastName: "原", 29 | FirstName: "敬", 30 | Separator: separator, 31 | Score: 1, 32 | Algorithm: parser.Rule, 33 | }, 34 | }, 35 | { 36 | name: "菅義偉", 37 | input: "菅義偉", 38 | want: parser.DividedName{ 39 | LastName: "菅", 40 | FirstName: "義偉", 41 | Separator: separator, 42 | Score: 0.48027055739279506, 43 | Algorithm: parser.Statistics, 44 | }, 45 | }, 46 | { 47 | name: "阿部晋三", 48 | input: "阿部晋三", 49 | want: parser.DividedName{ 50 | LastName: "阿部", 51 | FirstName: "晋三", 52 | Separator: separator, 53 | Score: 0.47397644480584417, 54 | Algorithm: parser.Statistics, 55 | }, 56 | }, 57 | { 58 | name: "中曽根康弘", 59 | input: "中曽根康弘", 60 | want: parser.DividedName{ 61 | LastName: "中曽根", 62 | FirstName: "康弘", 63 | Separator: separator, 64 | Score: 0.3127240879300895, 65 | Algorithm: parser.Statistics, 66 | }, 67 | }, 68 | { 69 | name: "中山マサ", 70 | input: "中山マサ", 71 | want: parser.DividedName{ 72 | LastName: "中山", 73 | FirstName: "マサ", 74 | Separator: separator, 75 | Score: 1, 76 | Algorithm: parser.Rule, 77 | }, 78 | }, 79 | { 80 | name: "やまだはなこ", 81 | input: "やまだはなこ", 82 | want: parser.DividedName{ 83 | LastName: "や", 84 | FirstName: "まだはなこ", 85 | Separator: separator, 86 | Score: 0.16666666666666666, 87 | Algorithm: parser.Statistics, 88 | }, 89 | }, 90 | } 91 | 92 | for _, tt := range tests { 93 | tt := tt 94 | t.Run(tt.name, func(t *testing.T) { 95 | t.Parallel() 96 | sut := parser.NewNameParser(separator, seimei.InitKanjiFeatureManager()) 97 | got, err := sut.Parse(tt.input) 98 | if err != nil { 99 | t.Errorf("error is not nil, err=%v", err) 100 | } 101 | 102 | if diff := cmp.Diff(got, tt.want); diff != "" { 103 | t.Errorf("divided name mismatch (-got +want):\n%s", diff) 104 | } 105 | }) 106 | } 107 | } 108 | 109 | func TestNameParser_Parse_Validate(t *testing.T) { 110 | t.Parallel() 111 | //nolint:exhaustivestruct 112 | sut := parser.NewNameParser("/", feature.KanjiFeatureManager{}) 113 | _, gotErr := sut.Parse("あ") 114 | wantErr := parser.ErrNameLength 115 | 116 | if !errors.Is(gotErr, wantErr) { 117 | t.Errorf("error is not expected, got error=(%v), want error=(%v)", gotErr, wantErr) 118 | } 119 | } 120 | 121 | func TestFullName_Length(t *testing.T) { 122 | t.Parallel() 123 | 124 | type testdata struct { 125 | name string 126 | input parser.FullName 127 | want int 128 | } 129 | 130 | tests := []testdata{ 131 | { 132 | name: "漢字", 133 | input: "中山", 134 | want: 2, 135 | }, 136 | { 137 | name: "アルファベット混合", 138 | input: "DJ田中", 139 | want: 4, 140 | }, 141 | } 142 | 143 | for _, tt := range tests { 144 | tt := tt 145 | t.Run(tt.name, func(t *testing.T) { 146 | t.Parallel() 147 | sut := tt.input 148 | got := sut.Length() 149 | 150 | if got != tt.want { 151 | t.Errorf("length is not expected, got=(%d), want=(%d)", got, tt.want) 152 | } 153 | }) 154 | } 155 | } 156 | 157 | func TestFullName_Sprint(t *testing.T) { 158 | t.Parallel() 159 | 160 | type testdata struct { 161 | name string 162 | input parser.FullName 163 | inputPosition int 164 | wantLastName parser.LastName 165 | wantFirstName parser.FirstName 166 | wantErr error 167 | } 168 | 169 | tests := []testdata{ 170 | { 171 | name: "0文字目", 172 | input: "寿限無寿限無", 173 | inputPosition: 0, 174 | wantLastName: "", 175 | wantFirstName: "寿限無寿限無", 176 | wantErr: nil, 177 | }, 178 | { 179 | name: "4文字目", 180 | input: "寿限無寿限無", 181 | inputPosition: 4, 182 | wantLastName: "寿限無寿", 183 | wantFirstName: "限無", 184 | wantErr: nil, 185 | }, 186 | { 187 | name: "6文字目", 188 | input: "寿限無寿限無", 189 | inputPosition: 6, 190 | wantLastName: "寿限無寿限無", 191 | wantFirstName: "", 192 | wantErr: nil, 193 | }, 194 | { 195 | name: "7文字目は制限を超えるのでエラーになる", 196 | input: "寿限無寿限無", 197 | inputPosition: 7, 198 | wantLastName: "", 199 | wantFirstName: "", 200 | wantErr: parser.ErrSplitPosition, 201 | }, 202 | { 203 | name: "-1文字目指定はエラーになる", 204 | input: "寿限無寿限無", 205 | inputPosition: -1, 206 | wantLastName: "", 207 | wantFirstName: "", 208 | wantErr: parser.ErrSplitPosition, 209 | }, 210 | } 211 | 212 | for _, tt := range tests { 213 | tt := tt 214 | t.Run(tt.name, func(t *testing.T) { 215 | t.Parallel() 216 | sut := tt.input 217 | l, f, err := sut.Split(tt.inputPosition) 218 | if !errors.Is(err, tt.wantErr) { 219 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 220 | } 221 | if tt.wantErr != nil { 222 | return 223 | } 224 | if l != tt.wantLastName { 225 | t.Errorf("LastName is not expected, got=(%s), want=(%s)", l, tt.wantLastName) 226 | } 227 | if f != tt.wantFirstName { 228 | t.Errorf("LastName is not expected, got=(%s), want=(%s)", f, tt.wantFirstName) 229 | } 230 | got := parser.JoinName(l, f) 231 | if got != tt.input { 232 | t.Errorf("fullname is not expected, got=(%s), want=(%s)", got, tt.input) 233 | } 234 | if f.Length()+l.Length() != tt.input.Length() { 235 | t.Errorf("fullname's length is not expected, got(fist_name=(%s), last_name=(%s)), want(%s)", f, l, tt.input) 236 | } 237 | }) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /semei_test.go: -------------------------------------------------------------------------------- 1 | package seimei_test 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/glassmonkey/seimei/v2" 9 | "github.com/glassmonkey/seimei/v2/feature" 10 | "github.com/google/go-cmp/cmp" 11 | ) 12 | 13 | func TestParseName(t *testing.T) { 14 | t.Parallel() 15 | 16 | type testdata struct { 17 | name string 18 | inputName seimei.Name 19 | inputParser seimei.ParseString 20 | want string 21 | wantErrMsg string 22 | } 23 | 24 | tests := []testdata{ 25 | { 26 | name: "サンプル", 27 | inputName: "田中太郎", 28 | inputParser: " ", 29 | want: "田中 太郎\n", 30 | }, 31 | { 32 | name: "分割文字列が反映される", 33 | inputName: "田中太郎", 34 | inputParser: "/", 35 | want: "田中/太郎\n", 36 | }, 37 | { 38 | name: "ルールベースで動作する", 39 | inputName: "乙一", 40 | inputParser: " ", 41 | want: "乙 一\n", 42 | }, 43 | { 44 | name: "統計量ベースで動作する", 45 | inputName: "竈門炭治郎", 46 | inputParser: " ", 47 | want: "竈門 炭治郎\n", 48 | }, 49 | { 50 | name: "統計量ベースで分割できる", 51 | inputName: "中曽根康弘", 52 | inputParser: " ", 53 | want: "中曽根 康弘\n", 54 | }, 55 | { 56 | name: "1文字は分割できない", 57 | inputName: "あ", 58 | inputParser: " ", 59 | want: "", 60 | wantErrMsg: "parse error: name length needs at least 2 chars\n", 61 | }, 62 | } 63 | 64 | for _, tt := range tests { 65 | tt := tt 66 | t.Run(tt.name, func(t *testing.T) { 67 | t.Parallel() 68 | stdout := &bytes.Buffer{} 69 | stderr := &bytes.Buffer{} 70 | 71 | if err := seimei.ParseName(stdout, stderr, tt.inputName, tt.inputParser); err != nil { 72 | t.Fatalf("happen error: %v", err) 73 | } 74 | 75 | if stdout.String() != tt.want { 76 | t.Errorf("failed to test. got: %s, want: %s", stdout, tt.want) 77 | } 78 | if stderr.String() != tt.wantErrMsg { 79 | t.Errorf("failed to test. got: %s, want: %s", stderr, tt.wantErrMsg) 80 | } 81 | }) 82 | } 83 | } 84 | 85 | func TestParseFile(t *testing.T) { 86 | t.Parallel() 87 | 88 | type testdata struct { 89 | name string 90 | inputPath seimei.Path 91 | inputParser seimei.ParseString 92 | want string 93 | wantErrOut string 94 | } 95 | 96 | tests := []testdata{ 97 | { 98 | name: "すべて成功", 99 | inputPath: "testdata/success.csv", 100 | inputParser: " ", 101 | want: `田中 太郎 102 | 乙 一 103 | 竈門 炭治郎 104 | 中曽根 康弘 105 | `, 106 | wantErrOut: "", 107 | }, 108 | { 109 | name: "フォーマットが正しくない", 110 | inputPath: "testdata/invalid_format.csv", 111 | inputParser: " ", 112 | want: ``, 113 | wantErrOut: `format error on line 1: [田中太郎 ] 114 | load line error on line 2: record on line 2: wrong number of fields 115 | load line error on line 3: record on line 3: wrong number of fields 116 | load line error on line 4: record on line 4: wrong number of fields 117 | `, 118 | }, 119 | { 120 | name: "エラーが混入している", 121 | inputPath: "testdata/part_of_error.csv", 122 | inputParser: " ", 123 | want: `田中 太郎 124 | 竈門 炭治郎 125 | 中曽根 康弘 126 | `, 127 | wantErrOut: `parse error on line 2: parse error: name length needs at least 2 chars 128 | `, 129 | }, 130 | } 131 | 132 | for _, tt := range tests { 133 | tt := tt 134 | t.Run(tt.name, func(t *testing.T) { 135 | t.Parallel() 136 | stdout := &bytes.Buffer{} 137 | stderr := &bytes.Buffer{} 138 | 139 | if err := seimei.ParseFile(stdout, stderr, tt.inputPath, tt.inputParser); err != nil { 140 | t.Fatalf("happen error: %v", err) 141 | } 142 | 143 | if diff := cmp.Diff(stdout.String(), tt.want); diff != "" { 144 | t.Errorf("failed to test. diff: %s", diff) 145 | } 146 | if diff := cmp.Diff(stderr.String(), tt.wantErrOut); diff != "" { 147 | t.Errorf("failed to test. diff: %s", diff) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | func TestParseFile_LargeFile(t *testing.T) { 154 | t.Parallel() 155 | 156 | t.Run("巨大なファイル(rows=100000)がパースできる", func(t *testing.T) { 157 | t.Parallel() 158 | 159 | stdout := &bytes.Buffer{} 160 | stderr := &bytes.Buffer{} 161 | // Since the test data is repetition of the same text, slice is used to generate the want value. 162 | lt := "竈門 炭治郎" 163 | size := 100000 164 | lts := make([]string, size) 165 | for i := 0; i < size; i++ { 166 | lts[i] = lt 167 | } 168 | lts = append(lts, "") 169 | want := strings.Join(lts, "\n") 170 | 171 | if err := seimei.ParseFile(stdout, stderr, "testdata/large.csv", " "); err != nil { 172 | t.Fatalf("happen error: %v", err) 173 | } 174 | if diff := cmp.Diff(stdout.String(), want); diff != "" { 175 | t.Errorf("failed to test. diff: %s", diff) 176 | } 177 | if diff := cmp.Diff(stderr.String(), ""); diff != "" { 178 | t.Errorf("failed to test. diff: %s", diff) 179 | } 180 | }) 181 | } 182 | 183 | func TestParseFile_NotFoundFile(t *testing.T) { 184 | t.Parallel() 185 | 186 | t.Run("存在しないファイルを指定", func(t *testing.T) { 187 | t.Parallel() 188 | 189 | stdout := &bytes.Buffer{} 190 | stderr := &bytes.Buffer{} 191 | wantErrMsg := "happen error load file: fatal error file load: open testdata/not_found.csv: no such file or directory" 192 | err := seimei.ParseFile(stdout, stderr, "testdata/not_found.csv", " ") 193 | if diff := cmp.Diff(err.Error(), wantErrMsg); diff != "" { 194 | t.Errorf("failed to test. diff: %s", diff) 195 | } 196 | if diff := cmp.Diff(stdout.String(), ""); diff != "" { 197 | t.Errorf("failed to test. diff: %s", diff) 198 | } 199 | if diff := cmp.Diff(stderr.String(), ""); diff != "" { 200 | t.Errorf("failed to test. diff: %s", diff) 201 | } 202 | 203 | }) 204 | } 205 | 206 | func TestInitKanjiFeatureManager(t *testing.T) { 207 | t.Parallel() 208 | 209 | type testdata struct { 210 | name string 211 | inputKanji feature.Character 212 | wantFeature feature.KanjiFeature 213 | } 214 | 215 | tests := []testdata{ 216 | { 217 | name: "デフォルト", 218 | inputKanji: "無", 219 | wantFeature: feature.DefaultKanjiFeature(), 220 | }, 221 | { 222 | name: "csvの最初", 223 | inputKanji: "々", 224 | wantFeature: feature.KanjiFeature{ 225 | Character: "々", 226 | Order: []float64{ 227 | 0, 275, 9, 0, 14, 25, 228 | }, 229 | Length: []float64{ 230 | 0, 7, 276, 1, 0, 23, 16, 0, 231 | }, 232 | }, 233 | }, 234 | { 235 | name: "csvの最後", 236 | inputKanji: "葵", 237 | wantFeature: feature.KanjiFeature{ 238 | Character: "葵", 239 | Order: []float64{ 240 | 1, 0, 0, 0, 0, 9, 241 | }, 242 | Length: []float64{ 243 | 0, 1, 0, 0, 6, 3, 0, 0, 244 | }, 245 | }, 246 | }, 247 | } 248 | 249 | for _, tt := range tests { 250 | tt := tt 251 | t.Run(tt.name, func(t *testing.T) { 252 | t.Parallel() 253 | sut := seimei.InitKanjiFeatureManager() 254 | got := sut.Get(tt.inputKanji) 255 | 256 | if diff := cmp.Diff(got, tt.wantFeature); diff != "" { 257 | t.Errorf("feature value mismatch (-got +want):\n%s", diff) 258 | } 259 | }) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /feature/kanji.go: -------------------------------------------------------------------------------- 1 | package feature 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | const ( 10 | CharacterFeatureSize = 1 11 | OrderFeatureSize = 6 12 | LengthFeatureSize = 8 13 | OrderFirstFeatureIndex = OrderFeatureIndexPosition(0) 14 | OrderMiddleFeatureIndex = OrderFeatureIndexPosition(1) 15 | OrderEndFeatureIndex = OrderFeatureIndexPosition(2) 16 | ) 17 | 18 | var ( 19 | ErrOrderFeatureInvalidSize = errors.New("order feature's length must be 6") 20 | ErrLengthFeatureInvalidSize = errors.New("length feature's length must be 8") 21 | ErrInvalidFeatureSize = errors.New("feature-to-feature calculations must be the same size") 22 | ErrOutRangeOrderMask = errors.New("character position is out of range when creating mask") 23 | ErrInvalidOrderMask = errors.New("first character and last character must not be created order mask") 24 | ErrOutRangeFeatureIndex = errors.New("character position is out of range when selecting features") 25 | ) 26 | 27 | type OrderFeatureIndexPosition int 28 | 29 | func (i OrderFeatureIndexPosition) MoveFirstNameIndex() OrderFeatureIndexPosition { 30 | return i + OrderFeatureSize/2 31 | } 32 | 33 | type LengthFeatureIndexPosition int 34 | 35 | func (i LengthFeatureIndexPosition) MoveFirstNameIndex() LengthFeatureIndexPosition { 36 | return i + LengthFeatureSize/2 37 | } 38 | 39 | type Character string 40 | 41 | type KanjiFeatureManager struct { 42 | KanjiFeatureMap map[Character]KanjiFeature 43 | } 44 | 45 | func (m KanjiFeatureManager) Get(c Character) KanjiFeature { 46 | v, ok := m.KanjiFeatureMap[c] 47 | if !ok { 48 | return DefaultKanjiFeature() 49 | } 50 | 51 | return v 52 | } 53 | 54 | func (m KanjiFeatureManager) OrderMask(fullNameLength, charPosition int) (Features, error) { 55 | if charPosition == 0 || charPosition == fullNameLength-1 { 56 | return Features{}, ErrInvalidOrderMask 57 | } 58 | 59 | if charPosition < 0 || charPosition >= fullNameLength { 60 | return Features{}, ErrOutRangeOrderMask 61 | } 62 | 63 | if fullNameLength == 3 { 64 | return Features{0, 0, 1, 1, 0, 0}, nil 65 | } 66 | 67 | if charPosition == 1 { 68 | return Features{0, 1, 1, 1, 0, 0}, nil 69 | } 70 | 71 | if charPosition == fullNameLength-2 { 72 | return Features{0, 0, 1, 1, 1, 0}, nil 73 | } 74 | 75 | return Features{0, 1, 1, 1, 1, 0}, nil 76 | } 77 | 78 | func (m KanjiFeatureManager) LengthMask(fullNameLength, charPosition int) (Features, error) { 79 | if charPosition < 0 || charPosition >= fullNameLength { 80 | return Features{}, ErrOutRangeOrderMask 81 | } 82 | 83 | minLastName := charPosition + 1 84 | maxLastName := fullNameLength - 1 85 | lf := m.maskLengthFuturesForPart(minLastName, maxLastName) 86 | 87 | minFirstName := fullNameLength - charPosition 88 | maxFirstName := fullNameLength - 1 89 | 90 | ff := m.maskLengthFuturesForPart(minFirstName, maxFirstName) 91 | lf = append(lf, ff...) 92 | 93 | return lf, nil 94 | } 95 | 96 | func (m KanjiFeatureManager) maskLengthFuturesForPart(min, max int) []float64 { 97 | minv := min 98 | maxv := max 99 | 100 | if maxv > LengthFeatureSize/2 { 101 | maxv = LengthFeatureSize / 2 102 | } 103 | 104 | f := []float64{0, 0, 0, 0} 105 | 106 | if minv <= maxv { 107 | for i := minv - 1; i < maxv; i++ { 108 | f[i] = 1 109 | } 110 | } 111 | 112 | return f 113 | } 114 | 115 | func (m KanjiFeatureManager) SelectOrderFeaturePosition(pieceOfName PartOfNameCharacters, positionInPieceOfName int) (OrderFeatureIndexPosition, error) { 116 | if positionInPieceOfName < 0 || positionInPieceOfName >= pieceOfName.Length() { 117 | return 0, ErrOutRangeFeatureIndex 118 | } 119 | 120 | if positionInPieceOfName == 0 { 121 | if pieceOfName.IsLastName() { 122 | return OrderFirstFeatureIndex, nil 123 | } 124 | 125 | return OrderFirstFeatureIndex.MoveFirstNameIndex(), nil 126 | } 127 | 128 | if positionInPieceOfName != pieceOfName.Length()-1 { 129 | if pieceOfName.IsLastName() { 130 | return OrderMiddleFeatureIndex, nil 131 | } 132 | 133 | return OrderMiddleFeatureIndex.MoveFirstNameIndex(), nil 134 | } 135 | 136 | if pieceOfName.IsLastName() { 137 | return OrderEndFeatureIndex, nil 138 | } 139 | 140 | return OrderEndFeatureIndex.MoveFirstNameIndex(), nil 141 | } 142 | 143 | func (m KanjiFeatureManager) SelectLengthFeaturePosition(pieceOfName PartOfNameCharacters) (LengthFeatureIndexPosition, error) { 144 | p := pieceOfName.Length() 145 | if p > LengthFeatureSize/2 { 146 | p = LengthFeatureSize / 2 147 | } 148 | 149 | if pieceOfName.IsLastName() { 150 | return LengthFeatureIndexPosition(p - 1), nil 151 | } 152 | 153 | return LengthFeatureIndexPosition(p - 1).MoveFirstNameIndex(), nil 154 | } 155 | 156 | func DefaultKanjiFeature() KanjiFeature { 157 | return KanjiFeature{ 158 | Character: "Default", 159 | Order: defaultFeature(OrderFeatureSize), 160 | Length: defaultFeature(LengthFeatureSize), 161 | } 162 | } 163 | 164 | type Features []float64 165 | 166 | func (f Features) Multiple(mask Features) (Features, error) { 167 | if len(f) != len(mask) { 168 | return Features{}, ErrInvalidFeatureSize 169 | } 170 | 171 | r := make(Features, len(f)) 172 | for i, v := range f { 173 | r[i] = v * mask[i] 174 | } 175 | 176 | return r, nil 177 | } 178 | 179 | func (f Features) Sum() float64 { 180 | t := 0.0 181 | for _, v := range f { 182 | t += v 183 | } 184 | 185 | return t 186 | } 187 | 188 | func (f Features) SoftMax() Features { 189 | e := make(Features, len(f)) 190 | 191 | for i, v := range f { 192 | e[i] = math.Exp(v) 193 | } 194 | 195 | u := e.Sum() 196 | for i, v := range e { 197 | e[i] = v / u 198 | } 199 | 200 | return e 201 | } 202 | 203 | func defaultFeature(size int) Features { 204 | return make(Features, size) 205 | } 206 | 207 | type KanjiFeature struct { 208 | Character Character 209 | Order Features 210 | Length Features 211 | } 212 | 213 | func (k KanjiFeature) GetOrderValue(p OrderFeatureIndexPosition, mask Features) (float64, error) { 214 | if p < 0 || p >= OrderFeatureSize { 215 | return 0.0, ErrOutRangeFeatureIndex 216 | } 217 | 218 | os, err := k.Order.Multiple(mask) 219 | if err != nil { 220 | return 0.0, fmt.Errorf("failed order value: %w", err) 221 | } 222 | 223 | total := os.Sum() 224 | if total == 0 { 225 | return 0, nil 226 | } 227 | 228 | return os[p] / total, nil 229 | } 230 | 231 | func (k KanjiFeature) GetLengthValue(p LengthFeatureIndexPosition, mask Features) (float64, error) { 232 | if p < 0 || p >= LengthFeatureSize { 233 | return 0.0, ErrOutRangeFeatureIndex 234 | } 235 | 236 | os, err := k.Length.Multiple(mask) 237 | if err != nil { 238 | return 0.0, fmt.Errorf("failed order value: %w", err) 239 | } 240 | 241 | total := os.Sum() 242 | if total == 0 { 243 | return 0, nil 244 | } 245 | 246 | return os[p] / total, nil 247 | } 248 | 249 | func NewKanjiFeature(c Character, o, l []float64) (KanjiFeature, error) { 250 | if len(o) != OrderFeatureSize { 251 | return KanjiFeature{}, ErrOrderFeatureInvalidSize 252 | } 253 | 254 | if len(l) != LengthFeatureSize { 255 | return KanjiFeature{}, ErrLengthFeatureInvalidSize 256 | } 257 | 258 | return KanjiFeature{ 259 | Character: c, 260 | Order: o, 261 | Length: l, 262 | }, nil 263 | } 264 | -------------------------------------------------------------------------------- /cli_test.go: -------------------------------------------------------------------------------- 1 | package seimei_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/glassmonkey/seimei/v2" 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func TestBuildNameCmd(t *testing.T) { 13 | type testdata struct { 14 | name string 15 | input []string 16 | wantOut string 17 | wantErrOut string 18 | wantErrMsg string 19 | } 20 | 21 | tests := []testdata{ 22 | { 23 | name: "基本", 24 | input: []string{"--name", "田中太郎"}, 25 | wantOut: "田中 太郎\n", 26 | }, 27 | { 28 | name: "パース文字指定", 29 | input: []string{"--name", "田中太郎", "--parse", "@"}, 30 | wantOut: "田中@太郎\n", 31 | }, 32 | { 33 | name: "短縮指定でも良い", 34 | input: []string{"-n", "田中太郎", "-p", "/"}, 35 | wantOut: "田中/太郎\n", 36 | }, 37 | { 38 | name: "指定がない", 39 | input: []string{"--name"}, 40 | wantErrMsg: "flag needs an argument: --name", 41 | }, 42 | { 43 | name: "未定義の短縮パラメータ利用", 44 | input: []string{"--name", "田中太郎", "-x"}, 45 | wantErrMsg: "unknown shorthand flag: 'x' in -x", 46 | }, 47 | { 48 | name: "未定義のパラメータ利用", 49 | input: []string{"--name", "田中太郎", "--any"}, 50 | wantErrMsg: "unknown flag: --any", 51 | }, 52 | { 53 | name: "空", 54 | input: []string{}, 55 | wantErrMsg: "required flag(s) \"name\" not set", 56 | }, 57 | } 58 | for _, tt := range tests { 59 | tt := tt 60 | t.Run(tt.name, func(t *testing.T) { 61 | t.Parallel() 62 | stdout := &bytes.Buffer{} 63 | stderr := &bytes.Buffer{} 64 | sut := seimei.BuildNameCmd() 65 | sut.SetOut(stdout) 66 | sut.SetErr(stderr) 67 | sut.SetArgs(tt.input) 68 | 69 | _, gotErr := sut.ExecuteC() 70 | if tt.wantErrMsg == "" && gotErr != nil { 71 | t.Fatalf("happen error: %v", gotErr) 72 | } 73 | if tt.wantErrMsg != "" { 74 | if gotErr == nil { 75 | t.Fatal("happen no error") 76 | } 77 | if diff := cmp.Diff(gotErr.Error(), tt.wantErrMsg); diff != "" { 78 | t.Fatalf("failed to test on error. diff: %s", diff) 79 | } 80 | return 81 | } 82 | if diff := cmp.Diff(stdout.String(), tt.wantOut); diff != "" { 83 | t.Errorf("failed to test on error. diff: %s", diff) 84 | } 85 | if diff := cmp.Diff(stderr.String(), tt.wantErrOut); diff != "" { 86 | t.Errorf("failed to test on error. diff: %s", diff) 87 | } 88 | }) 89 | } 90 | } 91 | 92 | func TestBuildFileCmd(t *testing.T) { 93 | 94 | type testdata struct { 95 | name string 96 | input []string 97 | wantOut string 98 | wantErrOut string 99 | wantErrMsg string 100 | } 101 | 102 | tests := []testdata{ 103 | { 104 | name: "基本", 105 | input: []string{"--file", "./testdata/success.csv"}, 106 | wantOut: `田中 太郎 107 | 乙 一 108 | 竈門 炭治郎 109 | 中曽根 康弘 110 | `, 111 | }, 112 | { 113 | name: "パース文字指定", 114 | input: []string{"--file", "./testdata/success.csv", "--parse", "@"}, 115 | wantOut: `田中@太郎 116 | 乙@一 117 | 竈門@炭治郎 118 | 中曽根@康弘 119 | `, 120 | }, 121 | { 122 | name: "短縮指定でも良い", 123 | input: []string{"-f", "./testdata/success.csv"}, 124 | wantOut: `田中 太郎 125 | 乙 一 126 | 竈門 炭治郎 127 | 中曽根 康弘 128 | `, 129 | }, 130 | { 131 | name: "実行時エラーが混ざる場合", 132 | input: []string{"-f", "./testdata/part_of_error.csv"}, 133 | wantOut: `田中 太郎 134 | 竈門 炭治郎 135 | 中曽根 康弘 136 | `, 137 | wantErrOut: `parse error on line 2: parse error: name length needs at least 2 chars 138 | `, 139 | }, 140 | { 141 | name: "指定がない", 142 | input: []string{"--file"}, 143 | wantErrMsg: "flag needs an argument: --file", 144 | }, 145 | { 146 | name: "未定義の短縮パラメータ利用", 147 | input: []string{"--file", "/tmb/hoge.csv", "-x"}, 148 | wantErrMsg: "unknown shorthand flag: 'x' in -x", 149 | }, 150 | { 151 | name: "未定義のパラメータ利用", 152 | input: []string{"--file", "/tmb/hoge.csv", "--any"}, 153 | wantErrMsg: "unknown flag: --any", 154 | }, 155 | { 156 | name: "空", 157 | input: []string{}, 158 | wantErrMsg: "required flag(s) \"file\" not set", 159 | }, 160 | } 161 | for _, tt := range tests { 162 | tt := tt 163 | t.Run(tt.name, func(t *testing.T) { 164 | t.Parallel() 165 | stdout := &bytes.Buffer{} 166 | stderr := &bytes.Buffer{} 167 | sut := seimei.BuildFileCmd() 168 | sut.SetOut(stdout) 169 | sut.SetErr(stderr) 170 | sut.SetArgs(tt.input) 171 | 172 | gotErr := sut.Execute() 173 | if tt.wantErrMsg == "" && gotErr != nil { 174 | t.Fatalf("happen error: %v", gotErr) 175 | } 176 | if tt.wantErrMsg != "" { 177 | if gotErr == nil { 178 | t.Fatal("happen no error") 179 | } 180 | if diff := cmp.Diff(gotErr.Error(), tt.wantErrMsg); diff != "" { 181 | t.Fatalf("failed to test on error. diff: %s", diff) 182 | } 183 | return 184 | } 185 | if diff := cmp.Diff(stdout.String(), tt.wantOut); diff != "" { 186 | t.Errorf("failed to test on error. diff: %s", diff) 187 | } 188 | if diff := cmp.Diff(stderr.String(), tt.wantErrOut); diff != "" { 189 | t.Errorf("failed to test on error. diff: %s", diff) 190 | } 191 | }) 192 | } 193 | } 194 | 195 | func TestRun(t *testing.T) { 196 | t.Parallel() 197 | 198 | type testdata struct { 199 | name string 200 | input []string 201 | wantOut string 202 | wantErrOut string 203 | } 204 | seimei.VersionText = "devel" 205 | 206 | tests := []testdata{ 207 | { 208 | name: "名前指定", 209 | input: []string{"name", "--name", "田中太郎"}, 210 | wantOut: "田中 太郎\n", 211 | }, 212 | { 213 | name: "名前指定実行のヘルプ", 214 | input: []string{"name", "-h"}, 215 | wantOut: `It parse single full name. 216 | Provide the full name to be parsed with the required flag (--name). 217 | 218 | Usage: 219 | seimei name [flags] 220 | 221 | Examples: 222 | seimei name --name 田中太郎 223 | 224 | Flags: 225 | -n, --name string 田中太郎 226 | -p, --parse string (default " ") 227 | -h, --help help for name 228 | `, 229 | }, 230 | { 231 | name: "ファイル経由の実行", 232 | input: []string{"file", "--file", "testdata/success.csv"}, 233 | wantOut: `田中 太郎 234 | 乙 一 235 | 竈門 炭治郎 236 | 中曽根 康弘 237 | `, 238 | }, 239 | { 240 | name: "ファイル経由の実行のヘルプ", 241 | input: []string{"file", "-h"}, 242 | wantOut: `It bulk parse full name lit in the file. 243 | Provide the file path with full name list to the required flag (--file). 244 | 245 | Usage: 246 | seimei file [flags] 247 | 248 | Examples: 249 | seimei file --file /path/to/dir/foo.csv 250 | 251 | Flags: 252 | -f, --file string /path/to/dir/foo.csv 253 | -p, --parse string (default " ") 254 | -h, --help help for file 255 | `, 256 | }, 257 | { 258 | name: "サブコマンドなしはヘルプが表示される", 259 | input: []string{}, 260 | wantOut: `Usage: 261 | seimei [flags] 262 | seimei [command] 263 | 264 | Available Commands: 265 | name It parse single full name. 266 | file It bulk parse full name lit in the file. 267 | help Help about any command 268 | 269 | Flags: 270 | -h, --help help for seimei 271 | -v, --version version for seimei 272 | 273 | Use "seimei [command] --help" for more information about a command. 274 | `, 275 | }, 276 | { 277 | name: "ヘルプが表示される", 278 | input: []string{"-h"}, 279 | wantOut: `Usage: 280 | seimei [flags] 281 | seimei [command] 282 | 283 | Available Commands: 284 | name It parse single full name. 285 | file It bulk parse full name lit in the file. 286 | help Help about any command 287 | 288 | Flags: 289 | -h, --help help for seimei 290 | -v, --version version for seimei 291 | 292 | Use "seimei [command] --help" for more information about a command. 293 | `, 294 | }, 295 | { 296 | name: "バージョン表記", 297 | input: []string{"-v"}, 298 | wantOut: fmt.Sprintf(`seimei version devel 299 | `), 300 | }, 301 | } 302 | 303 | for _, tt := range tests { 304 | tt := tt 305 | t.Run(tt.name, func(t *testing.T) { 306 | t.Parallel() 307 | stdout := &bytes.Buffer{} 308 | stderr := &bytes.Buffer{} 309 | sut := seimei.BuildMainCmd() 310 | sut.SetOut(stdout) 311 | sut.SetErr(stderr) 312 | sut.SetArgs(tt.input) 313 | 314 | if err := sut.Execute(); err != nil { 315 | t.Fatalf("happen error: %v", err) 316 | } 317 | 318 | if diff := cmp.Diff(stdout.String(), tt.wantOut); diff != "" { 319 | t.Errorf("failed to test on error. diff: %s", diff) 320 | } 321 | if diff := cmp.Diff(stderr.String(), tt.wantErrOut); diff != "" { 322 | t.Errorf("failed to test on error. diff: %s", diff) 323 | } 324 | }) 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /feature/kanji_test.go: -------------------------------------------------------------------------------- 1 | package feature_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/glassmonkey/seimei/v2/feature" 8 | "github.com/glassmonkey/seimei/v2/parser" 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func TestKanjiOrderFeatureCalculator_OrderMask(t *testing.T) { 13 | t.Parallel() 14 | 15 | type testdata struct { 16 | name string 17 | inputLength int 18 | inputPosition int 19 | wantMask feature.Features 20 | wantErr error 21 | } 22 | 23 | tests := []testdata{ 24 | { 25 | name: "-1文字目指定", 26 | inputLength: 5, 27 | inputPosition: -1, 28 | wantMask: feature.Features{}, 29 | wantErr: feature.ErrOutRangeOrderMask, 30 | }, 31 | { 32 | name: "(1/5)文字目指定", 33 | inputLength: 5, 34 | inputPosition: 0, 35 | wantMask: feature.Features{}, 36 | wantErr: feature.ErrInvalidOrderMask, 37 | }, 38 | { 39 | name: "(2/5)文字目指定", 40 | inputLength: 5, 41 | inputPosition: 1, 42 | wantMask: feature.Features{ 43 | 0, 1, 1, 1, 0, 0, 44 | }, 45 | wantErr: nil, 46 | }, 47 | { 48 | name: "(3/5)文字目指定", 49 | inputLength: 5, 50 | inputPosition: 2, 51 | wantMask: feature.Features{ 52 | 0, 1, 1, 1, 1, 0, 53 | }, 54 | wantErr: nil, 55 | }, 56 | { 57 | name: "(4/5)文字目指定", 58 | inputLength: 5, 59 | inputPosition: 3, 60 | wantMask: feature.Features{ 61 | 0, 0, 1, 1, 1, 0, 62 | }, 63 | wantErr: nil, 64 | }, 65 | { 66 | name: "(5/5)文字目指定", 67 | inputLength: 5, 68 | inputPosition: 4, 69 | wantMask: feature.Features{}, 70 | wantErr: feature.ErrInvalidOrderMask, 71 | }, 72 | { 73 | name: "5文字目指定", 74 | inputLength: 5, 75 | inputPosition: 5, 76 | wantMask: feature.Features{ 77 | 0, 0, 1, 1, 1, 0, 78 | }, 79 | wantErr: feature.ErrOutRangeOrderMask, 80 | }, 81 | { 82 | name: "(1/3)文字目指定", 83 | inputLength: 3, 84 | inputPosition: 0, 85 | wantMask: feature.Features{}, 86 | wantErr: feature.ErrInvalidOrderMask, 87 | }, 88 | { 89 | name: "(2/3)文字目指定", 90 | inputLength: 3, 91 | inputPosition: 1, 92 | wantMask: feature.Features{ 93 | 0, 0, 1, 1, 0, 0, 94 | }, 95 | wantErr: nil, 96 | }, 97 | { 98 | name: "(3/3)文字目指定", 99 | inputLength: 3, 100 | inputPosition: 2, 101 | wantMask: feature.Features{}, 102 | wantErr: feature.ErrInvalidOrderMask, 103 | }, 104 | } 105 | 106 | for _, tt := range tests { 107 | tt := tt 108 | t.Run(tt.name, func(t *testing.T) { 109 | t.Parallel() 110 | 111 | sut := feature.KanjiFeatureManager{ 112 | KanjiFeatureMap: map[feature.Character]feature.KanjiFeature{}, 113 | } 114 | got, err := sut.OrderMask(tt.inputLength, tt.inputPosition) 115 | if !errors.Is(err, tt.wantErr) { 116 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 117 | } 118 | if tt.wantErr != nil { 119 | return 120 | } 121 | 122 | if diff := cmp.Diff(got, tt.wantMask); diff != "" { 123 | t.Errorf("mask value mismatch (-got +want):\n%s", diff) 124 | } 125 | }) 126 | } 127 | } 128 | 129 | func TestKanjiOrderFeatureCalculator_LengthMask(t *testing.T) { 130 | t.Parallel() 131 | 132 | type testdata struct { 133 | name string 134 | inputLength int 135 | inputPosition int 136 | wantMask feature.Features 137 | wantErr error 138 | } 139 | 140 | tests := []testdata{ 141 | { 142 | name: "-1文字目指定", 143 | inputLength: 5, 144 | inputPosition: -1, 145 | wantMask: feature.Features{}, 146 | wantErr: feature.ErrOutRangeOrderMask, 147 | }, 148 | { 149 | name: "(1/5)文字目指定", 150 | inputLength: 5, 151 | inputPosition: 0, 152 | wantMask: feature.Features{ 153 | 1, 1, 1, 1, 0, 0, 0, 0, 154 | }, 155 | wantErr: nil, 156 | }, 157 | { 158 | name: "(2/5)文字目指定", 159 | inputLength: 5, 160 | inputPosition: 1, 161 | wantMask: feature.Features{ 162 | 0, 1, 1, 1, 0, 0, 0, 1, 163 | }, 164 | wantErr: nil, 165 | }, 166 | { 167 | name: "(3/5)文字目指定", 168 | inputLength: 5, 169 | inputPosition: 2, 170 | wantMask: feature.Features{ 171 | 0, 0, 1, 1, 0, 0, 1, 1, 172 | }, 173 | wantErr: nil, 174 | }, 175 | { 176 | name: "(4/5)文字目指定", 177 | inputLength: 5, 178 | inputPosition: 3, 179 | wantMask: feature.Features{ 180 | 0, 0, 0, 1, 0, 1, 1, 1, 181 | }, 182 | wantErr: nil, 183 | }, 184 | { 185 | name: "(5/5)文字目指定", 186 | inputLength: 5, 187 | inputPosition: 4, 188 | wantMask: feature.Features{ 189 | 0, 0, 0, 0, 1, 1, 1, 1, 190 | }, 191 | wantErr: nil, 192 | }, 193 | { 194 | name: "5文字目指定", 195 | inputLength: 5, 196 | inputPosition: 5, 197 | wantMask: feature.Features{}, 198 | wantErr: feature.ErrOutRangeOrderMask, 199 | }, 200 | { 201 | name: "(1/3)文字目指定", 202 | inputLength: 3, 203 | inputPosition: 0, 204 | wantMask: feature.Features{ 205 | 1, 1, 0, 0, 0, 0, 0, 0, 206 | }, 207 | wantErr: nil, 208 | }, 209 | { 210 | name: "(2/3)文字目指定", 211 | inputLength: 3, 212 | inputPosition: 1, 213 | wantMask: feature.Features{ 214 | 0, 1, 0, 0, 0, 1, 0, 0, 215 | }, 216 | wantErr: nil, 217 | }, 218 | { 219 | name: "(3/3)文字目指定", 220 | inputLength: 3, 221 | inputPosition: 2, 222 | wantMask: feature.Features{ 223 | 0, 0, 0, 0, 1, 1, 0, 0, 224 | }, 225 | wantErr: nil, 226 | }, 227 | } 228 | 229 | for _, tt := range tests { 230 | tt := tt 231 | t.Run(tt.name, func(t *testing.T) { 232 | t.Parallel() 233 | 234 | sut := feature.KanjiFeatureManager{ 235 | KanjiFeatureMap: map[feature.Character]feature.KanjiFeature{}, 236 | } 237 | got, err := sut.LengthMask(tt.inputLength, tt.inputPosition) 238 | if !errors.Is(err, tt.wantErr) { 239 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 240 | } 241 | if tt.wantErr != nil { 242 | return 243 | } 244 | 245 | if diff := cmp.Diff(got, tt.wantMask); diff != "" { 246 | t.Errorf("mask value mismatch (-got +want):\n%s", diff) 247 | } 248 | }) 249 | } 250 | } 251 | 252 | func TestKanjiOrderFeatureCalculator_SelectOrderFeaturePosition(t *testing.T) { 253 | t.Parallel() 254 | 255 | type testdata struct { 256 | name string 257 | inputName feature.PartOfNameCharacters 258 | inputPosition int 259 | wantPosition feature.OrderFeatureIndexPosition 260 | wantErr error 261 | } 262 | 263 | tests := []testdata{ 264 | { 265 | name: "名前1文字目", 266 | inputName: parser.FirstName("あきら"), 267 | inputPosition: 0, 268 | wantPosition: feature.OrderFirstFeatureIndex.MoveFirstNameIndex(), 269 | wantErr: nil, 270 | }, 271 | { 272 | name: "名前2文字目", 273 | inputName: parser.FirstName("あきら"), 274 | inputPosition: 1, 275 | wantPosition: feature.OrderMiddleFeatureIndex.MoveFirstNameIndex(), 276 | wantErr: nil, 277 | }, 278 | { 279 | name: "名前3文字目", 280 | inputName: parser.FirstName("あきら"), 281 | inputPosition: 2, 282 | wantPosition: feature.OrderEndFeatureIndex.MoveFirstNameIndex(), 283 | wantErr: nil, 284 | }, 285 | { 286 | name: "名字1文字目", 287 | inputName: parser.LastName("中山田"), 288 | inputPosition: 0, 289 | wantPosition: feature.OrderFirstFeatureIndex, 290 | wantErr: nil, 291 | }, 292 | { 293 | name: "名字2文字目", 294 | inputName: parser.LastName("中山田"), 295 | inputPosition: 1, 296 | wantPosition: feature.OrderMiddleFeatureIndex, 297 | wantErr: nil, 298 | }, 299 | { 300 | name: "名前3文字目", 301 | inputName: parser.LastName("中山田"), 302 | inputPosition: 2, 303 | wantPosition: feature.OrderEndFeatureIndex, 304 | wantErr: nil, 305 | }, 306 | { 307 | name: "負の数を指定", 308 | inputName: parser.LastName("中山田"), 309 | inputPosition: -1, 310 | wantPosition: feature.OrderEndFeatureIndex, 311 | wantErr: feature.ErrOutRangeFeatureIndex, 312 | }, 313 | { 314 | name: "最大を超える", 315 | inputName: parser.LastName("中山田"), 316 | inputPosition: 3, 317 | wantPosition: feature.OrderEndFeatureIndex, 318 | wantErr: feature.ErrOutRangeFeatureIndex, 319 | }, 320 | } 321 | 322 | for _, tt := range tests { 323 | tt := tt 324 | t.Run(tt.name, func(t *testing.T) { 325 | t.Parallel() 326 | sut := feature.KanjiFeatureManager{ 327 | KanjiFeatureMap: map[feature.Character]feature.KanjiFeature{}, 328 | } 329 | got, err := sut.SelectOrderFeaturePosition(tt.inputName, tt.inputPosition) 330 | if !errors.Is(err, tt.wantErr) { 331 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 332 | } 333 | if tt.wantErr != nil { 334 | return 335 | } 336 | 337 | if diff := cmp.Diff(got, tt.wantPosition); diff != "" { 338 | t.Errorf("mask value mismatch (-got +want):\n%s", diff) 339 | } 340 | }) 341 | } 342 | } 343 | 344 | func TestKanjiOrderFeatureCalculator_SelectLengthFeaturePosition(t *testing.T) { 345 | t.Parallel() 346 | 347 | type testdata struct { 348 | name string 349 | inputName feature.PartOfNameCharacters 350 | wantPosition feature.LengthFeatureIndexPosition 351 | wantErr error 352 | } 353 | 354 | tests := []testdata{ 355 | { 356 | name: "名前指定", 357 | inputName: parser.FirstName("あきら"), 358 | wantPosition: feature.LengthFeatureIndexPosition(6), 359 | wantErr: nil, 360 | }, 361 | { 362 | name: "名字指定", 363 | inputName: parser.LastName("中山田"), 364 | wantPosition: feature.LengthFeatureIndexPosition(2), 365 | wantErr: nil, 366 | }, 367 | { 368 | name: "指定文字数が大きい場合の名字", 369 | inputName: parser.LastName("寿限無寿限"), 370 | wantPosition: feature.LengthFeatureIndexPosition(3), 371 | wantErr: nil, 372 | }, 373 | { 374 | name: "指定文字数が大きい場合の指名", 375 | inputName: parser.FirstName("寿限無寿限"), 376 | wantPosition: feature.LengthFeatureIndexPosition(7), 377 | wantErr: nil, 378 | }, 379 | } 380 | 381 | for _, tt := range tests { 382 | tt := tt 383 | t.Run(tt.name, func(t *testing.T) { 384 | t.Parallel() 385 | sut := feature.KanjiFeatureManager{ 386 | KanjiFeatureMap: map[feature.Character]feature.KanjiFeature{}, 387 | } 388 | got, err := sut.SelectLengthFeaturePosition(tt.inputName) 389 | if !errors.Is(err, tt.wantErr) { 390 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 391 | } 392 | if tt.wantErr != nil { 393 | return 394 | } 395 | 396 | if diff := cmp.Diff(got, tt.wantPosition); diff != "" { 397 | t.Errorf("mask value mismatch (-got +want):\n%s", diff) 398 | } 399 | }) 400 | } 401 | } 402 | 403 | func TestKanjiFeature_GetOrderValue(t *testing.T) { 404 | t.Parallel() 405 | 406 | type testdata struct { 407 | name string 408 | inputFeature feature.Features 409 | inputPosition feature.OrderFeatureIndexPosition 410 | inputMask feature.Features 411 | wantScore float64 412 | wantErr error 413 | } 414 | 415 | tests := []testdata{ 416 | { 417 | name: "mask is all 1", 418 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 419 | inputMask: feature.Features{1, 1, 1, 1, 1, 1}, 420 | inputPosition: 0, 421 | wantScore: 1.0 / (1 + 2 + 4 + 8 + 16 + 32), 422 | wantErr: nil, 423 | }, 424 | { 425 | name: "mask is all 0", 426 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 427 | inputMask: feature.Features{0, 0, 0, 0, 0, 0}, 428 | inputPosition: 0, 429 | wantScore: 0, 430 | wantErr: nil, 431 | }, 432 | { 433 | name: "mask is half 1", 434 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 435 | inputMask: feature.Features{1, 0, 1, 0, 1, 0}, 436 | inputPosition: 0, 437 | wantScore: 1.0 / (1 + 4 + 16), 438 | wantErr: nil, 439 | }, 440 | { 441 | name: "mask is half 1 and target index 1", 442 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 443 | inputMask: feature.Features{1, 0, 1, 0, 1, 0}, 444 | inputPosition: 1, 445 | wantScore: 0, 446 | wantErr: nil, 447 | }, 448 | { 449 | name: "mask is half 1 and target index 2", 450 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 451 | inputMask: feature.Features{1, 0, 1, 0, 1, 0}, 452 | inputPosition: 2, 453 | wantScore: 4.0 / (1 + 4 + 16), 454 | wantErr: nil, 455 | }, 456 | { 457 | name: "target index -1", 458 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 459 | inputMask: feature.Features{1, 0, 1, 0, 1, 0}, 460 | inputPosition: -1, 461 | wantScore: 0, 462 | wantErr: feature.ErrOutRangeFeatureIndex, 463 | }, 464 | { 465 | name: "target index 6", 466 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 467 | inputMask: feature.Features{1, 0, 1, 0, 1, 0}, 468 | inputPosition: 8, 469 | wantScore: 0, 470 | wantErr: feature.ErrOutRangeFeatureIndex, 471 | }, 472 | { 473 | name: "input mask is un match size", 474 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32}, 475 | inputMask: feature.Features{1, 0, 1, 0, 1}, 476 | inputPosition: 0, 477 | wantScore: 0, 478 | wantErr: feature.ErrInvalidFeatureSize, 479 | }, 480 | } 481 | 482 | for _, tt := range tests { 483 | tt := tt 484 | t.Run(tt.name, func(t *testing.T) { 485 | t.Parallel() 486 | //nolint:exhaustivestruct 487 | sut := featureFixtures(t, inputForFixtures{orders: tt.inputFeature}) 488 | got, err := sut.GetOrderValue(tt.inputPosition, tt.inputMask) 489 | if !errors.Is(err, tt.wantErr) { 490 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 491 | } 492 | if tt.wantErr != nil { 493 | return 494 | } 495 | 496 | if diff := cmp.Diff(got, tt.wantScore); diff != "" { 497 | t.Errorf("score value mismatch (-got +want):\n%s", diff) 498 | } 499 | }) 500 | } 501 | } 502 | 503 | func TestKanjiFeature_GetLengthValue(t *testing.T) { 504 | t.Parallel() 505 | 506 | type testdata struct { 507 | name string 508 | inputFeature feature.Features 509 | inputPosition feature.LengthFeatureIndexPosition 510 | inputMask feature.Features 511 | wantScore float64 512 | wantErr error 513 | } 514 | 515 | tests := []testdata{ 516 | { 517 | name: "mask is all 1", 518 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 519 | inputMask: feature.Features{1, 1, 1, 1, 1, 1, 1, 1}, 520 | inputPosition: 0, 521 | wantScore: 1.0 / (1 + 2 + 4 + 8 + 16 + 32 + 64 + 128), 522 | wantErr: nil, 523 | }, 524 | { 525 | name: "mask is all 0", 526 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 527 | inputMask: feature.Features{0, 0, 0, 0, 0, 0, 0, 0}, 528 | inputPosition: 0, 529 | wantScore: 0, 530 | wantErr: nil, 531 | }, 532 | { 533 | name: "mask is half 1", 534 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 535 | inputMask: feature.Features{1, 0, 1, 0, 1, 0, 1, 0}, 536 | inputPosition: 0, 537 | wantScore: 1.0 / (1 + 4 + 16 + 64), 538 | wantErr: nil, 539 | }, 540 | { 541 | name: "mask is half 1 and target index 1", 542 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 543 | inputMask: feature.Features{1, 0, 1, 0, 1, 0, 1, 0}, 544 | inputPosition: 1, 545 | wantScore: 0, 546 | wantErr: nil, 547 | }, 548 | { 549 | name: "mask is half 1 and target index 2", 550 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 551 | inputMask: feature.Features{1, 0, 1, 0, 1, 0, 1, 0}, 552 | inputPosition: 2, 553 | wantScore: 4.0 / (1 + 4 + 16 + 64), 554 | wantErr: nil, 555 | }, 556 | { 557 | name: "target index -1", 558 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 559 | inputMask: feature.Features{1, 0, 1, 0, 1, 0, 1, 0}, 560 | inputPosition: -1, 561 | wantScore: 0, 562 | wantErr: feature.ErrOutRangeFeatureIndex, 563 | }, 564 | { 565 | name: "target index 6", 566 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 567 | inputMask: feature.Features{1, 0, 1, 0, 1, 0, 1, 0}, 568 | inputPosition: 8, 569 | wantScore: 0, 570 | wantErr: feature.ErrOutRangeFeatureIndex, 571 | }, 572 | { 573 | name: "input mask is un match size", 574 | inputFeature: feature.Features{1, 2, 4, 8, 16, 32, 64, 128}, 575 | inputMask: feature.Features{1, 0, 1, 0, 1}, 576 | inputPosition: 0, 577 | wantScore: 0, 578 | wantErr: feature.ErrInvalidFeatureSize, 579 | }, 580 | } 581 | 582 | for _, tt := range tests { 583 | tt := tt 584 | t.Run(tt.name, func(t *testing.T) { 585 | t.Parallel() 586 | //nolint:exhaustivestruct 587 | sut := featureFixtures(t, inputForFixtures{length: tt.inputFeature}) 588 | got, err := sut.GetLengthValue(tt.inputPosition, tt.inputMask) 589 | if !errors.Is(err, tt.wantErr) { 590 | t.Fatalf("error is not expected, got error=(%v), want error=(%v)", err, tt.wantErr) 591 | } 592 | if tt.wantErr != nil { 593 | return 594 | } 595 | 596 | if diff := cmp.Diff(got, tt.wantScore); diff != "" { 597 | t.Errorf("score value mismatch (-got +want):\n%s", diff) 598 | } 599 | }) 600 | } 601 | } 602 | 603 | type inputForFixtures struct { 604 | orders []float64 605 | length []float64 606 | } 607 | 608 | func featureFixtures(t *testing.T, input inputForFixtures) feature.KanjiFeature { 609 | t.Helper() 610 | 611 | o := input.orders 612 | l := input.length 613 | 614 | if len(o) == 0 { 615 | o = []float64{1, 1, 1, 1, 1, 1} 616 | } 617 | 618 | if len(l) == 0 { 619 | l = []float64{1, 1, 1, 1, 1, 1, 1, 1} 620 | } 621 | 622 | v, err := feature.NewKanjiFeature("dummy", o, l) 623 | if err != nil { 624 | t.Fatalf("kanji feature build failed: %v", err) 625 | } 626 | 627 | return v 628 | } 629 | -------------------------------------------------------------------------------- /namedivider-python/assets/kanji.csv: -------------------------------------------------------------------------------- 1 | kanji,oc_family_first,oc_family_other,oc_family_last,oc_given_first,oc_given_other,oc_given_last,lc_family_1,lc_family_2,lc_family_3,lc_family_4,lc_given_1,lc_given_2,lc_given_3,lc_given_4 2 | 々,0,275,9,0,14,25,0,7,276,1,0,23,16,0 3 | ツ,0,10,0,0,4,0,0,0,10,0,0,0,4,0 4 | ノ,1,33,0,0,0,0,0,0,32,2,0,0,0,0 5 | ヶ,0,21,0,0,0,0,0,0,21,0,0,0,0,0 6 | 一,51,0,4,618,560,1823,0,44,11,0,32,2401,568,0 7 | 七,12,0,0,20,1,3,0,10,0,2,1,20,3,0 8 | 万,2,0,0,36,0,2,0,2,0,0,0,16,22,0 9 | 丈,0,0,0,30,0,15,0,0,0,0,0,44,1,0 10 | 三,545,2,0,116,47,205,0,504,41,2,0,254,114,0 11 | 上,413,0,738,1,0,1,5,1110,36,0,1,1,0,0 12 | 下,166,14,524,0,0,0,0,662,41,1,0,0,0,0 13 | 不,6,1,0,4,0,0,0,5,2,0,0,2,2,0 14 | 与,5,1,2,7,4,3,0,5,3,0,0,4,10,0 15 | 世,5,1,10,12,22,87,0,13,3,0,0,85,36,0 16 | 並,27,0,10,1,0,0,0,37,0,0,0,1,0,0 17 | 中,1603,8,687,0,1,4,7,2259,31,1,4,0,1,0 18 | 串,4,0,9,0,0,0,0,13,0,0,0,0,0,0 19 | 丸,132,0,73,0,0,2,2,194,9,0,0,2,0,0 20 | 丹,52,0,5,1,0,0,1,55,1,0,0,1,0,0 21 | 主,1,0,5,9,0,0,0,6,0,0,0,9,0,0 22 | 乃,2,0,0,10,5,77,0,2,0,0,0,73,18,1 23 | 久,271,169,30,328,26,295,1,209,260,0,12,456,181,0 24 | 之,0,39,0,4,49,1076,0,0,38,1,1,1079,48,1 25 | 乙,8,2,0,5,0,0,0,8,2,0,0,4,1,0 26 | 九,6,0,0,4,2,0,0,5,1,0,0,2,4,0 27 | 也,0,0,1,0,14,882,0,1,0,0,0,881,15,0 28 | 乾,11,0,0,3,0,1,11,0,0,0,1,2,1,0 29 | 亀,61,0,8,2,1,0,1,67,1,0,0,2,1,0 30 | 了,1,0,0,8,0,2,0,1,0,0,0,10,0,0 31 | 予,0,0,0,0,3,8,0,0,0,0,0,7,4,0 32 | 二,66,0,2,28,77,565,0,51,17,0,0,588,82,0 33 | 五,113,2,1,26,5,8,0,26,86,4,0,30,9,0 34 | 井,458,23,1959,1,0,0,0,2358,81,1,0,0,1,0 35 | 亘,2,0,0,2,0,27,2,0,0,0,23,6,0,0 36 | 亜,1,0,0,299,7,7,0,1,0,0,0,118,195,0 37 | 亨,0,0,0,7,0,39,0,0,0,0,38,8,0,0 38 | 享,0,0,0,16,0,8,0,0,0,0,6,18,0,0 39 | 京,13,0,2,79,0,4,0,15,0,0,2,78,3,0 40 | 亮,0,0,0,124,0,154,0,0,0,0,91,185,2,0 41 | 人,8,0,5,1,0,607,0,13,0,0,0,602,6,0 42 | 仁,18,0,3,132,9,204,0,20,1,0,67,267,11,0 43 | 今,264,1,0,5,0,0,3,255,7,0,0,0,5,0 44 | 介,0,0,0,1,0,650,0,0,0,0,0,629,22,0 45 | 仙,13,0,1,1,0,0,0,13,1,0,0,1,0,0 46 | 代,3,3,73,2,164,538,0,74,5,0,0,439,265,0 47 | 令,0,0,0,12,3,3,0,0,0,0,2,13,3,0 48 | 仲,61,0,14,1,0,3,3,69,3,0,1,3,0,0 49 | 伊,562,0,0,26,2,2,0,534,28,0,0,10,19,1 50 | 伏,8,0,10,0,0,0,0,17,1,0,0,0,0,0 51 | 伯,1,2,33,1,0,0,0,34,1,1,0,1,0,0 52 | 伴,10,0,1,9,0,1,9,2,0,0,0,10,0,0 53 | 伸,0,0,0,264,0,97,0,0,0,0,17,329,15,0 54 | 住,23,1,30,1,1,0,2,49,3,0,0,1,1,0 55 | 佐,1112,25,25,120,71,22,0,849,313,0,0,39,174,0 56 | 佑,0,0,0,75,4,41,0,0,0,0,2,106,12,0 57 | 余,4,0,1,0,2,3,0,5,0,0,0,3,2,0 58 | 作,12,0,5,3,0,74,0,17,0,0,0,76,1,0 59 | 佳,0,0,0,375,59,199,0,0,0,0,2,486,145,0 60 | 侑,0,0,0,13,1,0,0,0,0,0,0,13,1,0 61 | 依,17,0,0,12,9,13,0,17,0,0,0,22,12,0 62 | 俊,1,0,1,531,0,149,0,2,0,0,21,648,11,0 63 | 保,41,80,232,64,40,116,1,173,179,0,11,168,41,0 64 | 信,12,0,6,349,0,146,0,18,0,0,20,449,26,0 65 | 俣,1,0,36,0,0,0,0,37,0,0,0,0,0,0 66 | 修,0,0,0,133,0,117,0,0,0,0,111,135,4,0 67 | 倉,103,1,235,0,0,0,1,333,5,0,0,0,0,0 68 | 倫,0,0,0,35,0,13,0,0,0,0,3,43,2,0 69 | 健,0,0,0,634,0,164,0,0,0,0,158,466,174,0 70 | 優,0,0,0,202,2,52,0,0,0,0,46,195,15,0 71 | 允,0,0,0,4,0,8,0,0,0,0,2,10,0,0 72 | 元,22,0,146,89,0,45,1,166,1,0,34,96,4,0 73 | 充,0,0,0,62,2,61,0,0,0,0,28,92,5,0 74 | 光,33,1,33,302,2,122,1,64,2,0,28,375,23,0 75 | 克,0,0,0,224,1,35,0,0,0,0,7,251,2,0 76 | 児,42,1,3,0,0,18,0,45,1,0,0,18,0,0 77 | 入,54,0,9,0,0,0,0,60,3,0,0,0,0,0 78 | 全,3,0,1,6,0,6,3,1,0,0,3,9,0,0 79 | 八,111,2,0,27,1,10,0,91,22,0,0,20,18,0 80 | 公,2,0,0,147,1,9,0,2,0,0,0,144,13,0 81 | 六,8,0,1,8,0,7,0,8,1,0,0,12,3,0 82 | 兵,16,0,1,2,1,4,1,16,0,0,0,5,2,0 83 | 具,4,0,0,6,0,0,1,2,1,0,0,6,0,0 84 | 典,0,0,0,151,0,191,0,0,0,0,0,341,1,0 85 | 兼,21,0,5,16,0,4,0,26,0,0,0,19,1,0 86 | 内,249,7,540,0,0,0,1,740,55,0,0,0,0,0 87 | 円,4,1,1,8,0,8,0,3,3,0,8,7,1,0 88 | 冨,97,0,17,8,0,0,0,111,3,0,0,1,7,0 89 | 出,47,3,97,0,11,5,0,141,6,0,5,0,11,0 90 | 刀,4,3,5,0,0,1,0,9,3,0,0,0,1,0 91 | 分,2,0,11,0,0,0,0,13,0,0,0,0,0,0 92 | 切,3,0,18,0,0,0,0,14,7,0,0,0,0,0 93 | 初,11,0,0,28,0,2,0,11,0,0,2,28,0,0 94 | 別,13,4,0,0,0,0,0,13,4,0,0,0,0,0 95 | 利,5,4,35,197,50,85,0,38,6,0,1,254,77,0 96 | 則,4,0,0,58,0,163,0,4,0,0,0,221,0,0 97 | 削,0,0,10,0,0,0,0,10,0,0,0,0,0,0 98 | 前,269,0,32,0,0,0,3,296,2,0,0,0,0,0 99 | 剛,0,0,0,98,0,163,0,0,0,0,148,113,0,0 100 | 副,6,1,4,0,0,0,0,10,1,0,0,0,0,0 101 | 創,0,0,0,5,0,14,0,0,0,0,14,5,0,0 102 | 力,5,0,5,9,0,30,0,9,1,0,28,11,0,0 103 | 功,2,0,1,46,0,52,0,3,0,0,42,50,6,0 104 | 加,411,2,4,100,42,54,0,387,29,1,0,73,123,0 105 | 助,1,0,1,0,0,40,0,2,0,0,0,22,18,0 106 | 努,0,0,0,0,0,27,0,0,0,0,27,0,0,0 107 | 勇,3,0,0,120,0,31,2,1,0,0,28,118,5,0 108 | 勉,0,0,0,0,0,43,0,0,0,0,43,0,0,0 109 | 務,0,0,3,0,0,8,0,3,0,0,7,1,0,0 110 | 勝,67,0,9,240,0,81,2,73,1,0,53,266,2,0 111 | 勢,3,6,13,2,0,1,0,15,7,0,0,1,2,0 112 | 勲,0,0,0,0,0,27,0,0,0,0,25,2,0,0 113 | 北,309,0,26,2,0,0,12,319,4,0,0,2,0,0 114 | 匠,0,2,3,4,0,12,0,3,2,0,12,4,0,0 115 | 匡,0,0,0,47,0,17,0,0,0,0,10,54,0,0 116 | 十,10,84,0,6,5,1,0,8,86,0,0,6,6,0 117 | 千,124,0,0,489,52,3,1,114,9,0,0,358,186,0 118 | 半,24,0,0,0,0,0,0,24,0,0,0,0,0,0 119 | 卓,0,0,0,134,0,45,0,0,0,0,44,134,1,0 120 | 南,126,1,18,4,4,4,58,86,1,0,3,4,5,0 121 | 博,0,0,0,382,0,292,0,0,0,0,58,614,2,0 122 | 厚,1,0,0,46,0,21,0,1,0,0,16,51,0,0 123 | 原,274,8,1365,2,0,0,101,1441,102,3,0,2,0,0 124 | 又,5,0,22,2,0,0,0,27,0,0,0,2,0,0 125 | 及,24,0,0,0,0,0,0,24,0,0,0,0,0,0 126 | 友,27,1,27,312,12,23,0,53,2,0,2,294,51,0 127 | 取,2,0,21,0,0,0,0,22,1,0,0,0,0,0 128 | 口,1,0,1003,0,0,0,0,984,20,0,0,0,0,0 129 | 古,319,6,25,1,0,8,0,325,24,1,0,0,9,0 130 | 可,6,0,0,12,2,2,0,6,0,0,0,9,7,0 131 | 史,0,0,0,122,8,691,0,0,0,0,4,805,12,0 132 | 右,4,1,0,5,1,9,0,4,1,0,0,13,1,1 133 | 司,4,0,48,9,0,807,1,50,1,0,33,781,2,0 134 | 合,13,2,111,0,25,37,0,123,3,0,0,9,53,0 135 | 吉,761,2,82,96,1,86,0,843,2,0,0,180,3,0 136 | 名,26,9,60,4,1,6,0,73,22,0,0,6,5,0 137 | 向,46,2,19,0,0,1,1,59,7,0,0,1,0,0 138 | 君,11,0,1,17,0,1,2,8,2,0,0,18,0,0 139 | 吹,6,1,19,0,0,1,0,25,1,0,0,1,0,0 140 | 吾,4,0,1,3,0,191,0,5,0,0,0,194,0,0 141 | 呂,1,5,14,0,7,2,1,13,6,0,0,0,9,0 142 | 周,7,0,0,40,0,14,1,6,0,0,10,43,1,0 143 | 味,3,0,13,0,0,2,0,16,0,0,0,2,0,0 144 | 和,159,26,30,979,57,360,0,168,47,0,10,1299,87,0 145 | 咲,0,0,2,19,3,24,0,2,0,0,2,39,5,0 146 | 品,17,0,7,0,0,0,0,23,1,0,0,0,0,0 147 | 哉,0,0,0,1,0,274,0,0,0,0,3,271,1,0 148 | 哲,0,0,0,336,0,64,0,0,0,0,55,341,4,0 149 | 唐,22,0,1,0,0,0,0,23,0,0,0,0,0,0 150 | 唯,1,0,0,8,2,7,0,1,0,0,5,9,3,0 151 | 啓,0,0,0,207,0,57,0,0,0,0,35,214,15,0 152 | 善,8,1,5,110,0,10,1,12,1,0,1,115,4,0 153 | 喜,32,4,4,107,24,75,0,27,13,0,0,135,71,0 154 | 喬,0,0,0,9,0,8,0,0,0,0,8,9,0,0 155 | 嗣,0,0,0,4,0,95,0,0,0,0,0,98,1,0 156 | 嘉,13,3,11,97,5,24,0,23,4,0,1,114,11,0 157 | 四,25,0,0,11,14,1,0,21,4,0,0,11,15,0 158 | 国,21,0,7,23,0,7,0,28,0,0,0,29,1,0 159 | 國,48,0,17,10,0,0,0,63,2,0,0,10,0,0 160 | 園,28,2,41,11,0,1,0,61,9,1,1,11,0,0 161 | 土,205,5,14,0,0,2,0,215,9,0,0,2,0,0 162 | 圭,0,0,0,186,1,20,0,0,0,0,18,175,14,0 163 | 地,15,5,177,1,1,19,0,180,17,0,0,20,1,0 164 | 坂,338,1,202,0,0,0,6,529,6,0,0,0,0,0 165 | 均,0,0,0,3,0,33,0,0,0,0,33,3,0,0 166 | 坪,36,0,24,0,0,0,0,60,0,0,0,0,0,0 167 | 垣,19,5,115,0,0,0,0,128,11,0,0,0,0,0 168 | 城,54,2,125,1,0,17,6,171,4,0,0,18,0,0 169 | 埜,1,0,9,0,0,0,0,10,0,0,0,0,0,0 170 | 基,0,0,0,54,1,49,0,0,0,0,7,94,3,0 171 | 堀,254,0,48,0,0,0,47,248,7,0,0,0,0,0 172 | 堂,14,1,16,1,0,1,1,22,8,0,0,2,0,0 173 | 堅,5,0,3,13,0,2,0,6,2,0,1,10,4,0 174 | 堤,34,0,1,0,0,1,33,2,0,0,1,0,0,0 175 | 場,1,2,139,0,0,0,0,137,5,0,0,0,0,0 176 | 堺,13,0,0,0,0,0,9,4,0,0,0,0,0,0 177 | 塚,73,0,270,0,0,0,0,335,8,0,0,0,0,0 178 | 塩,117,0,6,0,0,0,1,119,3,0,0,0,0,0 179 | 境,9,0,2,0,0,0,6,5,0,0,0,0,0,0 180 | 増,118,0,2,4,0,0,0,120,0,0,0,4,0,0 181 | 壁,3,0,14,0,0,0,0,17,0,0,0,0,0,0 182 | 士,0,5,7,3,17,144,0,7,5,0,0,144,20,0 183 | 壮,0,0,0,32,0,10,0,0,0,0,7,31,4,0 184 | 壽,1,0,3,17,1,3,0,3,1,0,0,18,3,0 185 | 夏,22,0,1,42,3,33,1,22,0,0,0,72,6,0 186 | 夕,0,0,0,18,2,1,0,0,0,0,0,16,5,0 187 | 外,30,0,0,0,0,4,0,27,3,0,0,4,0,0 188 | 多,74,22,53,55,4,8,0,108,41,0,0,29,38,0 189 | 夢,0,0,0,4,1,7,0,0,0,0,0,10,2,0 190 | 大,1468,3,1,441,1,154,0,1347,125,0,45,535,16,0 191 | 天,63,1,3,3,0,2,0,66,1,0,1,4,0,0 192 | 太,156,0,3,130,412,357,0,151,8,0,7,475,416,1 193 | 夫,0,0,0,0,0,635,0,0,0,0,0,604,31,0 194 | 央,0,0,0,13,10,81,0,0,0,0,11,82,11,0 195 | 奈,26,2,9,226,179,160,0,26,11,0,0,225,339,1 196 | 奥,212,0,6,0,1,0,8,209,1,0,0,0,1,0 197 | 好,2,0,33,78,1,21,0,35,0,0,3,96,1,0 198 | 妃,0,0,0,9,3,4,0,0,0,0,0,6,10,0 199 | 妙,0,0,0,38,0,7,0,0,0,0,5,40,0,0 200 | 妹,11,0,0,0,0,0,0,11,0,0,0,0,0,0 201 | 妻,3,0,25,0,0,0,0,28,0,0,0,0,0,0 202 | 姫,9,0,0,0,2,5,0,9,0,0,0,5,2,0 203 | 威,0,0,0,10,0,12,0,0,0,0,5,14,3,0 204 | 子,5,12,173,1,1,7738,0,169,21,0,0,5037,2703,0 205 | 孝,0,0,0,354,0,223,0,0,0,0,51,517,9,0 206 | 季,0,0,0,15,11,28,0,0,0,0,0,39,15,0 207 | 学,0,0,0,12,0,99,0,0,0,0,97,14,0,0 208 | 孫,2,6,1,4,0,0,1,2,6,0,0,4,0,0 209 | 宅,6,0,54,0,0,0,0,60,0,0,0,0,0,0 210 | 宇,112,3,1,4,0,0,0,67,49,0,0,3,1,0 211 | 守,28,1,17,30,0,27,1,43,2,0,25,32,0,0 212 | 安,421,2,46,58,0,3,2,452,15,0,1,51,9,0 213 | 宍,11,0,0,0,0,0,0,11,0,0,0,0,0,0 214 | 宏,0,0,0,295,0,442,0,0,0,0,73,657,7,0 215 | 宗,32,4,12,87,0,5,4,40,3,1,1,82,9,0 216 | 定,11,0,4,15,0,5,0,15,0,0,2,17,1,0 217 | 宜,0,0,0,19,0,10,0,0,0,0,0,29,0,0 218 | 実,3,0,3,34,12,174,0,6,0,0,55,137,28,0 219 | 宣,0,0,0,38,0,33,0,0,0,0,0,70,1,0 220 | 室,41,0,34,0,0,0,1,74,0,0,0,0,0,0 221 | 宮,586,12,135,2,0,0,2,700,31,0,0,2,0,0 222 | 家,14,2,58,3,0,0,0,72,2,0,0,3,0,0 223 | 容,0,0,0,34,0,8,0,0,0,0,2,40,0,0 224 | 寄,2,0,10,0,0,0,0,12,0,0,0,0,0,0 225 | 富,104,1,36,70,1,5,0,134,7,0,1,44,31,0 226 | 寛,0,0,0,126,0,112,0,0,0,0,63,174,1,0 227 | 實,6,0,4,0,0,3,0,10,0,0,2,1,0,0 228 | 寧,0,0,0,4,0,9,0,0,0,0,8,5,0,0 229 | 寺,176,0,67,0,0,0,0,200,43,0,0,0,0,0 230 | 寿,1,0,0,104,30,43,0,1,0,0,12,128,37,0 231 | 将,0,0,0,121,0,39,0,0,0,0,13,146,1,0 232 | 將,0,0,0,7,0,6,0,0,0,0,4,8,1,0 233 | 尊,1,0,0,9,0,2,0,1,0,0,1,10,0,0 234 | 尋,0,0,1,6,0,31,0,1,0,0,1,36,0,0 235 | 小,1916,4,0,46,1,0,0,1658,261,1,0,9,38,0 236 | 尚,1,0,0,269,0,60,0,1,0,0,23,302,4,0 237 | 尻,0,0,37,0,0,0,0,37,0,0,0,0,0,0 238 | 尾,166,1,440,0,1,0,0,597,10,0,0,0,1,0 239 | 居,4,1,59,0,0,2,0,62,2,0,0,2,0,0 240 | 屋,10,11,185,0,0,0,0,184,22,0,0,0,0,0 241 | 展,0,0,0,20,0,15,0,0,0,0,1,34,0,0 242 | 山,1845,20,1631,0,0,1,1,3436,59,0,0,1,0,0 243 | 岐,2,0,11,0,1,6,0,13,0,0,0,6,1,0 244 | 岡,525,1,647,0,0,1,35,1131,7,0,0,1,0,0 245 | 岩,480,3,34,8,0,0,0,511,6,0,0,7,1,0 246 | 岳,0,0,2,45,0,7,0,2,0,0,4,48,0,0 247 | 岸,96,0,86,2,0,0,17,165,0,0,0,2,0,0 248 | 峯,25,0,15,0,0,1,5,35,0,0,0,1,0,0 249 | 峰,11,0,14,17,0,7,2,23,0,0,0,23,1,0 250 | 島,163,2,891,0,0,0,19,1019,18,0,0,0,0,0 251 | 崇,0,0,0,88,0,73,0,0,0,0,59,102,0,0 252 | 崎,10,1,889,0,0,0,0,876,24,0,0,0,0,0 253 | 嵐,0,0,71,0,0,0,0,0,71,0,0,0,0,0 254 | 嵜,0,0,23,0,0,0,0,23,0,0,0,0,0,0 255 | 嶋,61,0,252,0,0,0,4,308,1,0,0,0,0,0 256 | 嶌,1,0,9,0,0,0,0,10,0,0,0,0,0,0 257 | 嶺,2,0,14,4,0,0,0,13,3,0,0,3,1,0 258 | 巌,1,0,0,0,0,16,0,1,0,0,16,0,0,0 259 | 川,596,8,1987,0,0,2,0,2361,229,1,0,2,0,0 260 | 工,77,3,1,0,0,5,0,78,3,0,5,0,0,0 261 | 左,2,1,0,6,8,1,0,2,1,0,0,3,11,1 262 | 巧,0,0,0,0,0,11,0,0,0,0,10,1,0,0 263 | 己,0,0,4,2,8,103,0,4,0,0,0,103,10,0 264 | 巳,0,0,6,5,3,55,0,6,0,0,0,55,8,0 265 | 巻,3,0,33,1,0,2,1,35,0,0,0,3,0,0 266 | 市,143,0,23,7,0,21,0,164,2,0,0,26,2,0 267 | 布,20,1,3,5,4,2,0,19,5,0,0,4,7,0 268 | 帆,2,0,0,0,6,32,0,2,0,0,0,32,6,0 269 | 希,0,0,0,30,113,168,0,0,0,0,20,162,129,0 270 | 師,1,0,8,3,0,2,0,9,0,0,0,5,0,0 271 | 常,14,0,0,19,0,2,0,14,0,0,0,21,0,0 272 | 幡,3,0,32,0,0,0,0,35,0,0,0,0,0,0 273 | 平,510,2,98,8,2,411,8,596,6,0,2,416,3,0 274 | 年,0,0,2,11,1,17,0,2,0,0,0,26,3,0 275 | 幸,21,0,2,550,0,464,2,20,1,0,25,967,22,0 276 | 幹,0,0,0,70,0,10,0,0,0,0,4,76,0,0 277 | 幾,2,0,0,11,0,1,0,2,0,0,0,9,3,0 278 | 広,47,0,8,138,0,137,0,55,0,0,8,264,3,0 279 | 庄,41,1,14,10,0,0,1,54,0,1,0,9,1,0 280 | 府,5,2,11,0,0,1,0,12,6,0,1,0,0,0 281 | 座,3,2,6,0,0,1,0,9,2,0,0,0,1,0 282 | 庭,3,0,34,0,0,0,0,37,0,0,0,0,0,0 283 | 康,2,0,0,506,0,93,1,1,0,0,24,562,13,0 284 | 庸,0,0,0,28,0,3,0,0,0,0,2,29,0,0 285 | 廉,0,0,0,7,0,5,0,0,0,0,5,4,3,0 286 | 廣,136,0,16,20,0,14,1,151,0,0,0,34,0,0 287 | 延,7,0,2,19,0,14,0,9,0,0,0,32,1,0 288 | 建,4,0,0,19,0,11,0,4,0,0,11,16,3,0 289 | 弓,15,0,1,14,3,91,0,16,0,0,1,101,6,0 290 | 引,6,0,6,0,0,0,0,12,0,0,0,0,0,0 291 | 弘,12,0,3,370,0,510,1,14,0,0,26,848,6,0 292 | 弥,3,0,0,52,23,95,0,3,0,0,3,141,26,0 293 | 張,16,0,4,0,0,1,8,10,2,0,0,1,0,0 294 | 強,1,0,0,6,0,4,0,1,0,0,4,6,0,0 295 | 彌,3,0,0,2,0,9,0,3,0,0,1,10,0,0 296 | 形,2,0,37,0,0,0,0,39,0,0,0,0,0,0 297 | 彦,3,0,3,3,0,960,0,6,0,0,1,953,9,0 298 | 彩,0,0,0,64,0,27,0,0,0,0,23,68,0,0 299 | 彬,0,0,0,2,0,10,0,0,0,0,2,10,0,0 300 | 彰,0,0,0,79,0,69,0,0,0,0,38,108,2,0 301 | 影,9,0,2,0,0,1,0,11,0,0,0,1,0,0 302 | 征,1,0,0,33,0,7,0,1,0,0,0,39,1,0 303 | 律,0,0,0,32,3,2,0,0,0,0,2,32,3,0 304 | 後,155,1,16,0,0,0,0,169,3,0,0,0,0,0 305 | 得,5,0,0,4,0,2,1,4,0,0,0,4,2,0 306 | 御,18,0,0,0,0,0,0,8,10,0,0,0,0,0 307 | 徳,85,0,10,66,1,135,0,95,0,0,1,193,8,0 308 | 德,18,0,2,2,0,0,0,20,0,0,0,2,0,0 309 | 徹,0,1,0,48,0,159,0,0,1,0,158,48,1,0 310 | 忍,3,0,0,1,0,55,0,3,0,0,55,1,0,0 311 | 志,58,8,11,130,29,517,0,64,13,0,0,606,70,0 312 | 忠,0,0,0,143,0,28,0,0,0,0,17,149,5,0 313 | 怜,0,0,0,12,0,6,0,0,0,0,5,13,0,0 314 | 恒,8,0,1,55,0,15,0,9,0,0,9,58,3,0 315 | 恭,0,0,0,175,0,14,0,0,0,0,5,180,4,0 316 | 恵,4,0,0,442,139,654,0,3,1,0,108,818,309,0 317 | 悟,0,1,0,12,0,86,0,0,1,0,42,55,1,0 318 | 悠,0,0,0,67,0,23,0,0,0,0,21,62,7,0 319 | 悦,0,1,0,105,0,3,0,0,1,0,0,107,1,0 320 | 惠,0,0,0,30,8,37,0,0,0,0,6,52,17,0 321 | 愛,3,0,0,84,1,83,0,3,0,0,72,90,6,0 322 | 愼,1,0,0,11,0,1,1,0,0,0,1,8,3,0 323 | 慎,0,0,0,174,0,12,0,0,0,0,12,117,57,0 324 | 慶,5,2,1,111,0,21,1,5,2,0,12,110,10,0 325 | 憲,0,0,0,164,0,83,0,0,0,0,12,219,16,0 326 | 成,78,2,30,98,0,131,1,107,2,0,2,225,2,0 327 | 我,7,8,18,0,0,2,0,25,6,2,0,2,0,0 328 | 戸,89,21,131,0,0,0,0,212,29,0,0,0,0,0 329 | 房,2,0,6,14,0,3,1,7,0,0,0,17,0,0 330 | 所,5,0,22,0,0,0,4,23,0,0,0,0,0,0 331 | 手,35,10,30,0,0,0,0,63,12,0,0,0,0,0 332 | 才,6,0,1,4,1,0,0,7,0,0,0,4,1,0 333 | 折,22,0,2,0,0,0,0,24,0,0,0,0,0,0 334 | 押,16,0,2,0,0,0,1,16,1,0,0,0,0,0 335 | 拓,0,0,0,132,0,30,0,0,0,0,27,135,0,0 336 | 持,18,0,15,0,0,1,0,33,0,0,0,1,0,0 337 | 掛,10,0,9,0,0,0,0,18,1,0,0,0,0,0 338 | 摩,3,0,3,8,0,2,0,6,0,0,0,9,1,0 339 | 操,0,0,0,2,0,10,0,0,0,0,10,2,0,0 340 | 政,6,0,11,141,0,25,0,17,0,0,2,163,1,0 341 | 敏,0,0,0,224,0,87,0,0,0,0,24,287,0,0 342 | 教,0,0,0,21,0,15,0,0,0,0,0,36,0,0 343 | 敦,1,0,0,144,0,61,0,1,0,0,59,146,0,0 344 | 敬,0,0,2,148,0,57,0,2,0,0,27,174,4,0 345 | 数,3,0,5,2,0,0,0,8,0,0,0,2,0,0 346 | 敷,3,0,13,0,0,0,0,12,4,0,0,0,0,0 347 | 文,11,3,2,221,0,214,1,12,3,0,8,421,6,0 348 | 斉,89,0,0,6,0,7,0,89,0,0,6,7,0,0 349 | 斎,63,0,0,0,0,1,1,62,0,0,1,0,0,0 350 | 斐,0,2,30,0,0,0,0,29,3,0,0,0,0,0 351 | 斗,0,2,0,3,3,12,0,0,2,0,0,12,6,0 352 | 新,290,0,1,39,0,19,5,277,9,0,18,29,11,0 353 | 方,4,2,72,8,0,3,0,74,4,0,0,10,1,0 354 | 施,2,2,9,0,0,2,2,8,3,0,0,2,0,0 355 | 日,131,9,14,23,19,1,0,115,39,0,0,6,37,0 356 | 早,80,1,2,88,0,4,0,78,5,0,0,73,19,0 357 | 昇,1,0,0,24,0,37,1,0,0,0,36,22,3,0 358 | 昌,1,0,0,373,0,32,0,1,0,0,5,398,2,0 359 | 明,25,1,8,392,0,663,0,32,2,0,66,967,22,0 360 | 星,102,0,10,2,0,5,29,82,1,0,1,6,0,0 361 | 映,0,0,0,20,5,5,0,0,0,0,2,20,8,0 362 | 春,48,0,4,78,0,78,0,50,2,0,0,155,1,0 363 | 昭,0,0,0,163,0,209,0,0,0,0,14,356,2,0 364 | 是,10,1,0,3,1,2,0,10,1,0,1,4,1,0 365 | 時,14,0,8,13,0,5,0,21,1,0,0,17,1,0 366 | 晃,0,0,0,142,0,114,0,0,0,0,57,193,6,0 367 | 晋,0,0,0,62,0,32,0,0,0,0,30,53,11,0 368 | 景,4,0,1,41,0,11,0,5,0,0,1,50,1,0 369 | 晴,0,0,0,109,0,90,0,0,0,0,3,195,1,0 370 | 晶,1,0,0,69,0,41,1,0,0,0,10,99,1,0 371 | 智,1,1,24,785,109,89,0,25,1,0,56,752,175,0 372 | 暁,0,0,0,29,0,29,0,0,0,0,12,46,0,0 373 | 暢,0,0,0,19,0,11,0,0,0,0,5,25,0,0 374 | 暮,1,0,12,0,0,0,0,13,0,0,0,0,0,0 375 | 曽,42,7,12,0,0,0,3,42,15,1,0,0,0,0 376 | 月,19,0,84,2,0,32,0,100,3,0,0,34,0,0 377 | 有,106,0,0,181,7,17,0,106,0,0,10,144,51,0 378 | 朋,0,0,0,133,0,10,0,0,0,0,4,139,0,0 379 | 服,70,0,0,0,0,0,0,70,0,0,0,0,0,0 380 | 朗,0,0,0,10,0,235,0,0,0,0,16,165,64,0 381 | 望,50,0,0,7,0,35,0,50,0,0,28,14,0,0 382 | 朝,42,0,1,43,0,0,1,38,4,0,0,42,1,0 383 | 木,506,26,1712,2,3,4,0,1919,323,2,0,5,4,0 384 | 未,0,0,0,50,4,25,0,0,0,0,0,65,14,0 385 | 末,50,0,13,3,0,0,0,63,0,0,0,3,0,0 386 | 本,208,4,2236,0,0,0,3,2426,19,0,0,0,0,0 387 | 朱,2,0,0,23,1,2,1,0,1,0,2,23,1,0 388 | 杉,344,0,43,2,0,0,7,379,1,0,0,2,0,0 389 | 李,14,0,0,3,1,1,14,0,0,0,0,3,2,0 390 | 杏,1,0,0,9,0,1,1,0,0,0,0,9,1,0 391 | 村,448,2,2067,1,0,2,0,2492,24,1,0,3,0,0 392 | 条,0,0,12,0,0,0,0,12,0,0,0,0,0,0 393 | 来,3,0,12,0,3,21,0,14,1,0,1,20,3,0 394 | 東,144,0,80,11,0,4,58,160,6,0,2,9,4,0 395 | 松,1121,13,292,13,0,3,0,1409,17,0,0,11,5,0 396 | 板,55,2,4,0,0,0,0,58,3,0,0,0,0,0 397 | 林,271,0,584,4,0,2,248,600,7,0,0,3,3,0 398 | 果,0,0,0,4,1,8,0,0,0,0,1,10,2,0 399 | 枝,8,0,46,9,19,230,0,54,0,0,0,198,60,0 400 | 柏,70,0,0,1,0,0,5,64,1,0,0,1,0,0 401 | 染,25,0,0,0,0,1,0,25,0,0,0,1,0,0 402 | 柳,101,1,70,2,0,0,11,158,3,0,0,2,0,0 403 | 柴,143,0,13,1,0,0,6,146,4,0,0,0,1,0 404 | 柿,33,0,1,0,0,0,0,33,1,0,0,0,0,0 405 | 栁,4,0,9,0,0,0,0,13,0,0,0,0,0,0 406 | 栄,4,0,3,138,13,52,2,5,0,0,16,150,37,0 407 | 栗,129,0,22,0,0,1,0,147,4,0,0,0,1,0 408 | 根,65,9,152,1,2,4,0,197,29,0,1,4,2,0 409 | 格,0,0,0,2,0,12,0,0,0,0,9,5,0,0 410 | 桂,14,0,0,53,1,11,7,7,0,0,11,50,4,0 411 | 桃,7,2,0,18,0,1,0,7,2,0,1,17,1,0 412 | 桐,28,0,25,3,0,0,1,47,5,0,0,3,0,0 413 | 桑,66,0,5,0,0,0,0,69,2,0,0,0,0,0 414 | 桜,27,0,0,3,3,5,0,27,0,0,3,5,3,0 415 | 桝,11,0,0,0,0,0,1,10,0,0,0,0,0,0 416 | 梅,122,0,2,3,0,0,0,124,0,0,0,2,1,0 417 | 梓,0,0,0,0,0,10,0,0,0,0,10,0,0,0 418 | 條,0,0,56,0,0,0,0,56,0,0,0,0,0,0 419 | 梢,0,0,1,1,0,10,0,1,0,0,10,1,0,0 420 | 梨,1,0,16,35,19,20,0,17,0,0,0,46,28,0 421 | 梶,64,0,4,0,0,0,6,60,2,0,0,0,0,0 422 | 棚,17,1,0,0,0,0,0,17,1,0,0,0,0,0 423 | 森,638,3,199,1,0,3,200,631,9,0,2,2,0,0 424 | 植,124,0,10,0,0,0,1,133,0,0,0,0,0,0 425 | 椎,21,0,0,0,0,0,0,21,0,0,0,0,0,0 426 | 楠,29,0,2,3,0,1,7,24,0,0,1,2,1,0 427 | 楽,0,1,8,1,1,1,0,8,1,0,0,2,0,1 428 | 榊,22,0,0,0,0,0,4,18,0,0,0,0,0,0 429 | 榎,43,0,0,0,0,0,4,39,0,0,0,0,0,0 430 | 榮,7,0,1,5,0,2,2,6,0,0,0,7,0,0 431 | 槙,5,0,0,6,0,1,0,5,0,0,1,5,1,0 432 | 槻,2,0,18,0,0,0,0,20,0,0,0,0,0,0 433 | 樋,67,0,0,0,0,0,0,67,0,0,0,0,0,0 434 | 権,9,0,0,0,0,1,0,9,0,0,1,0,0,0 435 | 横,270,0,0,1,0,0,0,265,5,0,0,0,0,1 436 | 樫,15,0,17,0,0,0,0,29,3,0,0,0,0,0 437 | 樹,3,0,4,16,23,920,0,7,0,0,4,928,27,0 438 | 橋,233,1,814,0,0,0,0,1040,8,0,0,0,0,0 439 | 橘,23,0,0,1,0,0,15,8,0,0,0,1,0,0 440 | 檜,14,0,0,0,0,0,0,14,0,0,0,0,0,0 441 | 櫻,81,0,0,0,0,2,0,81,0,0,2,0,0,0 442 | 次,1,1,23,21,48,129,0,23,2,0,0,149,48,1 443 | 欣,0,0,0,17,0,3,0,0,0,0,0,19,1,0 444 | 歌,3,0,0,1,7,6,0,3,0,0,0,6,8,0 445 | 正,32,0,6,837,0,138,0,38,0,0,33,933,9,0 446 | 武,205,1,42,146,1,73,3,242,2,1,49,167,4,0 447 | 歩,0,0,1,14,2,42,0,0,1,0,31,25,2,0 448 | 殿,9,0,4,0,0,0,0,13,0,0,0,0,0,0 449 | 毅,0,0,0,13,0,111,0,0,0,0,72,51,1,0 450 | 比,22,25,5,17,10,0,0,16,36,0,0,0,27,0 451 | 毛,20,1,17,0,0,0,1,35,2,0,0,0,0,0 452 | 民,1,0,0,9,0,7,0,0,1,0,1,15,0,0 453 | 水,248,7,256,4,0,12,0,493,18,0,0,15,1,0 454 | 永,274,1,280,13,1,11,0,554,1,0,0,20,5,0 455 | 江,176,4,192,36,20,250,0,349,23,0,0,202,104,0 456 | 池,303,0,176,0,0,0,2,472,5,0,0,0,0,0 457 | 沖,29,0,3,0,0,0,6,26,0,0,0,0,0,0 458 | 沙,0,0,0,65,21,28,0,0,0,0,0,67,47,0 459 | 沢,21,0,243,0,0,0,1,249,14,0,0,0,0,0 460 | 河,341,24,29,1,1,2,0,365,27,2,0,2,2,0 461 | 治,2,10,22,72,13,622,0,21,13,0,30,660,17,0 462 | 沼,34,0,127,0,0,0,2,155,4,0,0,0,0,0 463 | 泉,42,1,93,4,1,49,23,112,1,0,45,8,1,0 464 | 法,9,1,2,23,0,11,0,8,4,0,0,34,0,0 465 | 波,24,9,76,1,0,8,0,80,28,1,0,8,1,0 466 | 泰,0,0,0,350,0,57,0,0,0,0,20,381,6,0 467 | 洋,0,0,0,424,2,197,0,0,0,0,75,521,27,0 468 | 津,80,15,171,4,142,7,0,227,39,0,0,8,145,0 469 | 浅,185,0,20,0,0,0,0,205,0,0,0,0,0,0 470 | 浜,68,0,15,0,0,0,5,78,0,0,0,0,0,0 471 | 浦,61,0,258,0,0,1,6,313,0,0,0,1,0,0 472 | 浩,0,0,0,640,0,300,0,0,0,0,95,808,37,0 473 | 浪,9,0,12,0,0,2,0,21,0,0,0,2,0,0 474 | 海,64,4,49,10,1,34,0,89,28,0,3,40,2,0 475 | 涼,0,0,0,29,0,4,0,0,0,0,4,29,0,0 476 | 淑,0,0,0,21,0,5,0,0,0,0,2,24,0,0 477 | 深,111,1,1,11,0,2,0,112,1,0,1,12,0,0 478 | 淳,0,0,0,221,0,163,0,0,0,0,158,209,17,0 479 | 淵,3,0,20,0,0,0,0,23,0,0,0,0,0,0 480 | 淺,16,0,2,0,0,0,0,18,0,0,0,0,0,0 481 | 添,5,0,28,0,0,0,0,33,0,0,0,0,0,0 482 | 清,268,3,5,193,0,38,3,267,6,0,31,192,7,1 483 | 渉,0,0,0,5,0,27,0,0,0,0,27,4,1,0 484 | 渋,24,0,0,0,0,0,0,24,0,0,0,0,0,0 485 | 渕,7,0,62,0,0,0,1,68,0,0,0,0,0,0 486 | 渚,1,0,0,1,1,8,1,0,0,0,8,1,1,0 487 | 渡,507,2,34,0,0,1,1,532,10,0,1,0,0,0 488 | 温,4,0,0,12,1,5,0,3,1,0,5,12,1,0 489 | 湖,1,0,5,3,0,5,0,6,0,0,2,4,2,0 490 | 湯,58,0,2,0,0,0,0,56,4,0,0,0,0,0 491 | 満,11,0,14,37,1,37,0,25,0,0,25,34,16,0 492 | 源,11,0,0,12,0,3,5,6,0,0,1,9,5,0 493 | 準,0,0,0,12,0,7,0,0,0,0,7,10,2,0 494 | 溝,47,0,14,0,0,0,0,59,2,0,0,0,0,0 495 | 滋,3,0,0,22,0,24,0,3,0,0,22,24,0,0 496 | 滝,54,0,14,0,0,0,9,59,0,0,0,0,0,0 497 | 潔,0,0,0,3,0,34,0,0,0,0,33,4,0,0 498 | 潤,0,0,0,53,0,65,0,0,0,0,65,42,11,0 499 | 潮,6,0,0,3,0,3,3,3,0,0,3,3,0,0 500 | 澁,16,0,0,0,0,0,0,16,0,0,0,0,0,0 501 | 澄,2,0,6,28,1,35,0,8,0,0,3,60,1,0 502 | 澤,104,0,782,0,0,0,6,852,28,0,0,0,0,0 503 | 濱,99,0,22,0,0,0,5,115,1,0,0,0,0,0 504 | 濵,32,0,9,0,0,0,0,39,2,0,0,0,0,0 505 | 瀧,42,0,12,0,0,0,3,51,0,0,0,0,0,0 506 | 瀨,1,1,13,0,0,0,0,14,1,0,0,0,0,0 507 | 瀬,72,6,331,1,0,2,0,378,31,0,0,2,1,0 508 | 照,12,0,0,58,0,10,0,11,1,0,2,66,0,0 509 | 熊,89,0,24,0,0,1,0,113,0,0,0,1,0,0 510 | 爪,0,0,18,0,0,0,0,18,0,0,0,0,0,0 511 | 父,1,9,1,0,0,0,0,2,9,0,0,0,0,0 512 | 爾,0,0,0,1,1,13,0,0,0,0,0,14,1,0 513 | 片,169,0,6,0,0,0,0,175,0,0,0,0,0,0 514 | 牛,36,1,1,0,0,1,0,36,2,0,0,1,0,0 515 | 牟,5,7,0,0,0,0,0,4,8,0,0,0,0,0 516 | 牧,89,0,19,17,0,1,13,93,2,0,1,16,1,0 517 | 犬,10,1,0,1,0,0,0,10,1,0,0,1,0,0 518 | 狩,28,0,7,0,0,0,0,35,0,0,0,0,0,0 519 | 猛,0,0,0,5,0,33,0,0,0,0,32,6,0,0 520 | 猪,38,0,1,0,0,0,0,37,2,0,0,0,0,0 521 | 猿,17,0,0,0,0,0,0,17,0,0,0,0,0,0 522 | 玄,4,0,0,10,0,11,1,3,0,0,9,8,4,0 523 | 玉,65,0,49,14,0,2,0,114,0,0,0,16,0,0 524 | 王,10,2,1,3,0,2,6,4,3,0,0,5,0,0 525 | 玲,0,0,0,74,1,6,0,0,0,0,5,72,3,1 526 | 珠,1,0,1,38,1,0,0,2,0,0,0,35,4,0 527 | 理,0,0,1,212,131,154,0,1,0,0,24,292,181,0 528 | 琢,0,0,0,49,0,5,0,0,0,0,5,49,0,0 529 | 琴,0,0,0,14,1,9,0,0,0,0,0,23,1,0 530 | 瑛,0,0,0,10,0,2,0,0,0,0,2,9,1,0 531 | 瑞,2,0,0,59,1,6,0,2,0,0,3,60,3,0 532 | 瑠,0,0,0,20,0,3,0,0,0,0,0,18,5,0 533 | 環,2,0,0,4,0,12,2,0,0,0,12,4,0,0 534 | 瓶,0,1,13,0,0,0,0,13,1,0,0,0,0,0 535 | 生,52,5,70,12,6,275,0,117,10,0,0,281,12,0 536 | 田,991,79,6370,1,2,0,2,7119,319,0,0,0,2,1 537 | 由,11,0,3,803,298,34,0,12,2,0,3,438,694,0 538 | 甲,36,1,5,1,0,3,0,39,3,0,2,2,0,0 539 | 男,2,0,2,0,1,358,0,4,0,0,0,329,30,0 540 | 町,34,0,35,2,0,0,0,68,1,0,0,2,0,0 541 | 畑,44,1,114,0,0,0,14,143,2,0,0,0,0,0 542 | 畔,9,0,1,0,0,0,0,10,0,0,0,0,0,0 543 | 留,0,13,20,33,3,4,0,16,17,0,0,20,20,0 544 | 畠,36,0,14,0,0,0,2,48,0,0,0,0,0,0 545 | 番,9,0,0,1,0,0,1,6,2,0,0,1,0,0 546 | 當,8,0,2,0,0,0,0,10,0,0,0,0,0,0 547 | 登,9,2,11,27,18,38,1,18,3,0,10,32,41,0 548 | 白,219,0,3,3,0,0,2,215,5,0,0,2,1,0 549 | 百,19,6,0,43,29,0,0,18,5,2,0,17,55,0 550 | 皆,22,0,0,1,0,0,0,22,0,0,0,1,0,0 551 | 益,21,0,0,15,0,1,0,21,0,0,1,15,0,0 552 | 盛,10,1,16,24,0,4,4,22,1,0,0,26,2,0 553 | 目,23,0,25,0,0,0,0,44,4,0,0,0,0,0 554 | 直,10,0,0,760,2,59,0,10,0,0,28,788,5,0 555 | 相,96,0,4,4,0,1,0,100,0,0,0,5,0,0 556 | 省,0,0,0,17,0,1,0,0,0,0,0,18,0,0 557 | 眞,25,1,2,82,0,19,0,25,3,0,16,56,29,0 558 | 真,72,1,0,1088,2,95,0,69,4,0,77,680,428,0 559 | 督,0,0,0,4,0,6,0,0,0,0,6,4,0,0 560 | 睦,0,0,0,52,0,12,0,0,0,0,11,53,0,0 561 | 瞳,0,0,0,0,0,18,0,0,0,0,18,0,0,0 562 | 矢,196,1,35,2,22,43,0,222,10,0,0,44,23,0 563 | 知,6,2,19,357,73,36,0,18,9,0,5,345,116,0 564 | 石,778,3,203,0,0,1,1,964,19,0,0,1,0,0 565 | 砂,27,0,8,10,11,10,0,35,0,0,0,19,12,0 566 | 研,1,0,0,34,0,7,0,1,0,0,7,30,4,0 567 | 磨,1,1,10,7,0,26,0,11,1,0,1,27,5,0 568 | 磯,64,0,4,0,0,1,5,63,0,0,0,1,0,0 569 | 礒,12,0,0,0,0,0,0,12,0,0,0,0,0,0 570 | 礼,0,0,0,55,0,10,0,0,0,0,4,57,4,0 571 | 祐,2,0,0,336,4,101,0,2,0,0,4,413,24,0 572 | 祖,9,0,3,0,0,0,0,2,10,0,0,0,0,0 573 | 神,195,0,32,1,0,0,4,214,9,0,0,1,0,0 574 | 祥,0,0,0,110,0,20,0,0,0,0,5,120,5,0 575 | 禎,0,0,0,34,0,8,0,0,0,0,5,36,1,0 576 | 福,432,2,16,8,0,0,0,448,2,0,0,7,1,0 577 | 禮,0,0,6,4,0,0,0,4,2,0,0,2,2,0 578 | 秀,5,0,1,536,0,88,0,6,0,0,7,608,9,0 579 | 秋,149,0,3,17,0,29,0,152,0,0,0,45,1,0 580 | 科,0,0,12,0,1,0,0,12,0,0,0,0,1,0 581 | 秦,21,0,1,0,0,0,19,3,0,0,0,0,0,0 582 | 稔,0,0,0,20,0,73,0,0,0,0,63,30,0,0 583 | 種,12,0,6,1,0,1,1,16,1,0,0,2,0,0 584 | 稲,136,3,1,2,0,0,0,136,4,0,0,2,0,0 585 | 穂,11,0,0,7,58,196,0,10,1,0,0,200,61,0 586 | 積,3,0,15,0,0,3,1,17,0,0,0,3,0,0 587 | 穣,0,0,0,1,0,15,0,0,0,0,15,1,0,0 588 | 穴,18,0,1,0,0,0,0,19,0,0,0,0,0,0 589 | 空,6,0,4,0,0,0,2,8,0,0,0,0,0,0 590 | 窪,36,0,15,0,0,0,1,50,0,0,0,0,0,0 591 | 立,73,1,56,9,0,4,0,127,3,0,4,9,0,0 592 | 竜,3,0,0,84,0,1,1,2,0,0,1,73,11,0 593 | 章,0,0,0,104,0,141,0,0,0,0,39,203,3,0 594 | 端,3,0,42,1,0,1,2,42,1,0,1,1,0,0 595 | 竹,392,0,66,12,0,1,0,452,6,0,0,12,1,0 596 | 笑,0,0,0,4,0,6,0,0,0,0,3,5,2,0 597 | 笠,71,29,10,0,0,0,2,79,29,0,0,0,0,0 598 | 笹,73,0,6,0,0,0,0,79,0,0,0,0,0,0 599 | 筒,22,0,2,0,0,0,0,24,0,0,0,0,0,0 600 | 箕,15,0,0,0,0,0,0,15,0,0,0,0,0,0 601 | 節,1,0,1,33,0,4,0,2,0,0,3,34,0,0 602 | 範,0,0,1,38,0,45,0,1,0,0,1,81,1,0 603 | 築,11,0,10,0,0,1,0,20,1,0,1,0,0,0 604 | 篠,108,0,3,0,0,0,2,109,0,0,0,0,0,0 605 | 篤,0,0,0,63,0,46,0,0,0,0,43,66,0,0 606 | 米,125,2,17,1,1,0,1,137,6,0,0,1,1,0 607 | 粟,18,0,0,0,0,0,0,17,1,0,0,0,0,0 608 | 精,0,0,0,24,0,0,0,0,0,0,0,22,2,0 609 | 糸,22,0,1,0,0,0,0,23,0,0,0,0,0,0 610 | 紀,5,0,0,165,213,542,2,3,0,0,2,605,313,0 611 | 納,7,0,30,0,0,1,1,36,0,0,0,1,0,0 612 | 純,0,0,0,233,0,74,0,0,0,0,50,253,4,0 613 | 紗,0,0,0,30,5,13,0,0,0,0,0,22,26,0 614 | 紘,0,0,0,36,0,12,0,0,0,0,2,44,2,0 615 | 素,1,0,0,23,0,0,0,1,0,0,0,23,0,0 616 | 紫,3,0,1,10,0,4,0,4,0,0,2,9,3,0 617 | 細,107,0,0,0,0,0,0,106,1,0,0,0,0,0 618 | 紳,0,0,0,9,0,3,0,0,0,0,3,5,4,0 619 | 紺,17,0,0,0,0,0,0,17,0,0,0,0,0,0 620 | 結,6,0,0,12,0,2,0,6,0,0,1,12,1,0 621 | 絢,0,0,0,15,0,2,0,0,0,0,2,15,0,0 622 | 統,0,0,0,3,0,9,0,0,0,0,4,8,0,0 623 | 絵,2,0,0,91,7,107,0,2,0,0,0,159,45,1 624 | 絹,5,0,0,11,0,0,0,5,0,0,0,11,0,0 625 | 継,0,0,1,2,0,7,0,1,0,0,1,8,0,0 626 | 綱,7,0,3,2,0,4,1,9,0,0,0,6,0,0 627 | 網,9,0,2,0,0,0,0,11,0,0,0,0,0,0 628 | 綾,7,0,0,102,1,31,2,5,0,0,29,102,2,1 629 | 綿,6,0,6,0,1,0,0,12,0,0,0,0,1,0 630 | 総,0,0,0,16,0,1,0,0,0,0,0,9,8,0 631 | 緑,6,0,0,4,0,11,0,6,0,0,10,4,1,0 632 | 緒,21,0,0,0,44,28,0,21,0,0,0,25,47,0 633 | 繁,12,0,5,25,0,21,0,17,0,0,19,26,1,0 634 | 織,20,0,6,10,0,181,0,26,0,0,0,190,1,0 635 | 置,3,0,18,0,0,0,0,21,0,0,0,0,0,0 636 | 羅,2,1,2,0,0,5,1,1,3,0,0,5,0,0 637 | 美,16,3,39,1453,531,2519,0,30,28,0,1,2969,1533,0 638 | 義,3,0,0,307,0,110,0,3,0,0,0,414,3,0 639 | 羽,57,1,53,2,0,1,0,104,7,0,0,2,1,0 640 | 翔,0,0,0,43,1,28,0,0,0,0,26,40,6,0 641 | 老,4,23,5,0,0,0,0,7,25,0,0,0,0,0 642 | 考,0,0,0,7,0,6,0,0,0,0,1,12,0,0 643 | 耕,0,0,0,95,0,4,0,0,0,0,4,83,12,0 644 | 耶,0,0,1,0,6,10,0,0,1,0,0,10,6,0 645 | 聖,0,0,0,109,0,18,0,0,0,0,10,116,1,0 646 | 聡,0,0,0,118,0,185,0,0,0,0,175,120,8,0 647 | 聰,0,0,0,7,0,9,0,0,0,0,7,9,0,0 648 | 肇,0,0,0,0,0,32,0,0,0,0,32,0,0,0 649 | 肥,9,0,6,0,0,0,0,14,1,0,0,0,0,0 650 | 育,0,0,0,63,0,4,0,0,0,0,0,66,1,0 651 | 能,28,0,13,13,1,5,0,40,1,0,1,16,2,0 652 | 脇,39,0,89,0,0,0,6,121,1,0,0,0,0,0 653 | 腰,0,0,11,0,0,0,0,10,1,0,0,0,0,0 654 | 臣,0,0,0,1,0,35,0,0,0,0,0,36,0,0 655 | 至,0,0,0,4,1,33,0,0,0,0,4,32,2,0 656 | 臼,30,0,0,0,0,0,0,30,0,0,0,0,0,0 657 | 與,10,1,0,2,0,0,0,2,9,0,0,0,2,0 658 | 興,3,0,0,14,0,11,0,3,0,0,1,20,4,0 659 | 舘,15,0,16,0,0,0,5,26,0,0,0,0,0,0 660 | 舛,9,0,1,0,0,0,0,10,0,0,0,0,0,0 661 | 舞,3,0,0,11,0,15,0,3,0,0,15,9,2,0 662 | 舟,14,0,1,1,0,2,0,15,0,0,0,3,0,0 663 | 舩,13,0,1,0,0,0,0,14,0,0,0,0,0,0 664 | 航,0,0,0,14,0,8,0,0,0,0,8,13,1,0 665 | 船,41,0,6,0,0,0,0,46,1,0,0,0,0,0 666 | 良,4,6,73,323,1,53,0,66,17,0,11,355,11,0 667 | 芝,18,0,7,0,0,0,5,20,0,0,0,0,0,0 668 | 芦,28,0,2,0,0,0,0,30,0,0,0,0,0,0 669 | 花,70,1,23,9,2,36,0,93,1,0,1,33,13,0 670 | 芳,23,0,1,146,0,28,0,23,1,0,6,168,0,0 671 | 芹,17,0,3,1,0,0,0,20,0,0,0,1,0,0 672 | 芽,0,0,0,7,1,2,0,0,0,0,1,6,3,0 673 | 苗,4,0,1,3,1,77,0,5,0,0,0,80,1,0 674 | 若,108,0,5,10,0,1,0,113,0,0,0,11,0,0 675 | 英,1,0,0,643,2,96,1,0,0,0,3,706,32,0 676 | 茂,34,3,12,118,3,87,1,45,3,0,68,135,5,0 677 | 茉,0,0,0,12,0,0,0,0,0,0,0,6,6,0 678 | 茜,0,0,0,0,0,21,0,0,0,0,21,0,0,0 679 | 茶,9,0,0,1,0,0,1,7,1,0,0,1,0,0 680 | 草,39,3,7,3,0,6,0,46,3,0,0,8,1,0 681 | 荒,178,0,1,0,0,0,6,171,2,0,0,0,0,0 682 | 荘,2,0,2,8,0,2,0,4,0,0,2,5,3,0 683 | 荻,59,0,2,0,0,0,2,59,0,0,0,0,0,0 684 | 莉,0,0,0,15,7,4,0,0,0,0,0,16,10,0 685 | 菅,151,0,6,0,0,0,22,134,1,0,0,0,0,0 686 | 菊,166,0,0,10,0,0,1,165,0,0,0,9,1,0 687 | 菜,0,0,1,46,18,40,0,1,0,0,0,53,51,0 688 | 華,1,2,1,21,6,30,1,1,2,0,2,40,15,0 689 | 菱,11,0,1,0,0,0,0,12,0,0,0,0,0,0 690 | 萌,0,0,0,8,0,6,0,0,0,0,6,7,1,0 691 | 萩,59,0,4,0,0,0,2,60,1,0,0,0,0,0 692 | 萬,8,0,2,4,0,1,2,8,0,0,1,3,1,0 693 | 落,31,0,2,0,0,0,0,33,0,0,0,0,0,0 694 | 葉,7,0,109,36,3,8,2,113,1,0,0,43,4,0 695 | 葛,20,0,1,0,0,0,0,21,0,0,0,0,0,0 696 | 蒲,19,0,0,0,0,0,1,17,1,0,0,0,0,0 697 | 蔵,7,0,4,0,0,32,2,7,2,0,1,31,0,0 698 | 薄,14,0,0,0,0,0,1,13,0,0,0,0,0,0 699 | 薗,5,1,8,0,0,0,0,12,2,0,0,0,0,0 700 | 薫,0,0,0,5,0,94,0,0,0,0,94,5,0,0 701 | 薮,8,0,3,0,0,0,0,11,0,0,0,0,0,0 702 | 藍,4,0,0,5,0,12,0,4,0,0,12,5,0,0 703 | 藤,829,3,2562,5,0,0,2,3387,5,0,0,4,1,0 704 | 虎,0,0,1,8,0,2,0,1,0,0,1,8,1,0 705 | 蛭,10,0,0,0,0,0,0,10,0,0,0,0,0,0 706 | 蛯,11,0,0,0,0,0,0,11,0,0,0,0,0,0 707 | 行,12,0,7,34,0,349,0,19,0,0,1,381,1,0 708 | 衛,4,0,0,0,2,15,0,4,0,0,8,6,1,2 709 | 衣,11,0,0,9,41,61,0,11,0,0,0,66,45,0 710 | 袋,1,1,11,0,0,0,0,12,1,0,0,0,0,0 711 | 裕,0,0,0,815,13,269,0,0,0,0,81,938,78,0 712 | 西,746,2,283,0,0,0,44,979,7,1,0,0,0,0 713 | 要,0,0,0,12,0,17,0,0,0,0,17,9,3,0 714 | 見,4,8,189,0,4,19,0,184,17,0,0,19,4,0 715 | 規,0,0,0,37,5,48,0,0,0,0,2,78,10,0 716 | 覚,0,0,1,2,0,15,0,1,0,0,15,2,0,0 717 | 親,3,0,0,5,0,8,0,2,1,0,0,13,0,0 718 | 角,76,0,27,3,0,1,9,94,0,0,1,3,0,0 719 | 計,2,0,1,3,0,6,0,3,0,0,2,7,0,0 720 | 訓,0,0,0,16,0,6,0,0,0,0,1,21,0,0 721 | 記,1,0,1,9,7,37,0,2,0,0,0,43,10,0 722 | 訪,0,4,12,0,0,0,0,12,4,0,0,0,0,0 723 | 詩,0,0,0,10,0,3,0,0,0,0,1,9,3,0 724 | 詰,0,0,10,0,0,0,0,10,0,0,0,0,0,0 725 | 誉,1,0,0,8,1,5,0,1,0,0,3,10,1,0 726 | 誠,0,0,0,165,0,194,0,0,0,0,183,155,21,0 727 | 諏,16,0,0,0,0,0,0,12,4,0,0,0,0,0 728 | 諒,0,0,0,4,0,9,0,0,0,0,9,4,0,0 729 | 諭,0,0,0,9,0,16,0,0,0,0,16,9,0,0 730 | 諸,30,0,1,0,0,0,0,27,4,0,0,0,0,0 731 | 謙,0,0,0,88,0,22,0,0,0,0,17,78,15,0 732 | 譲,0,0,0,11,0,10,0,0,0,0,10,11,0,0 733 | 護,1,0,0,0,0,10,1,0,0,0,10,0,0,0 734 | 谷,254,178,878,0,0,0,35,1033,241,1,0,0,0,0 735 | 豊,88,0,0,61,0,104,2,85,1,0,93,70,2,0 736 | 豪,0,0,0,14,0,29,0,0,0,0,23,20,0,0 737 | 貝,11,0,30,0,0,0,0,40,1,0,0,0,0,0 738 | 貞,11,0,4,46,0,6,0,15,0,0,2,43,7,0 739 | 貢,0,0,0,9,0,11,0,0,0,0,10,9,1,0 740 | 貫,6,0,27,2,0,0,0,32,1,0,0,2,0,0 741 | 貴,10,0,0,472,33,202,0,10,0,0,13,623,71,0 742 | 賀,8,30,157,14,19,10,0,149,45,1,0,19,24,0 743 | 資,0,0,0,5,1,14,0,0,0,0,1,18,1,0 744 | 賢,0,0,0,170,0,44,0,0,0,0,33,164,17,0 745 | 赤,113,0,0,1,0,0,0,111,2,0,0,1,0,0 746 | 起,1,0,0,5,25,16,0,1,0,0,0,14,32,0 747 | 越,53,0,95,1,0,0,2,141,5,0,0,1,0,0 748 | 足,43,0,6,0,0,0,0,48,1,0,0,0,0,0 749 | 路,1,0,28,14,0,10,0,25,3,1,0,24,0,0 750 | 輔,0,0,0,0,1,289,0,0,0,0,0,283,7,0 751 | 輝,0,0,0,66,4,107,0,0,0,0,15,158,4,0 752 | 輪,0,0,42,0,0,3,0,42,0,0,0,3,0,0 753 | 辰,16,0,0,44,0,2,0,16,0,0,1,42,3,0 754 | 辺,1,2,288,0,0,0,0,282,9,0,0,0,0,0 755 | 辻,128,0,16,0,0,0,76,68,0,0,0,0,0,0 756 | 込,3,0,12,0,0,0,0,15,0,0,0,0,0,0 757 | 近,179,0,11,0,0,1,0,189,1,0,0,1,0,0 758 | 迫,12,0,30,0,0,0,2,39,1,0,0,0,0,0 759 | 透,0,0,0,2,0,27,0,0,0,0,25,4,0,0 760 | 通,3,0,3,35,0,20,0,6,0,0,6,49,0,0 761 | 造,1,0,1,0,0,44,0,2,0,0,0,43,1,0 762 | 進,18,0,0,37,0,50,0,18,0,0,47,27,13,0 763 | 逸,2,0,0,16,1,0,0,2,0,0,0,16,1,0 764 | 道,22,0,36,107,0,82,0,56,2,0,2,186,1,0 765 | 達,3,0,39,232,0,17,1,41,0,0,11,237,1,0 766 | 遠,133,3,2,0,0,3,0,135,3,0,0,3,0,0 767 | 遥,0,0,0,7,0,7,0,0,0,0,5,9,0,0 768 | 遼,0,0,0,7,0,9,0,0,0,0,9,6,1,0 769 | 邉,1,0,133,0,0,0,0,132,2,0,0,0,0,0 770 | 邊,1,0,111,0,0,0,0,111,1,0,0,0,0,0 771 | 那,17,7,0,1,4,9,0,17,7,0,0,9,5,0 772 | 邦,0,0,0,141,0,18,0,0,0,0,0,157,2,0 773 | 郁,0,0,0,88,0,7,0,0,0,0,4,88,3,0 774 | 郎,0,1,0,0,1,1563,0,0,1,0,0,438,1124,2 775 | 郡,15,0,2,0,0,0,4,13,0,0,0,0,0,0 776 | 部,1,5,593,0,0,0,0,541,55,3,0,0,0,0 777 | 郷,7,1,24,7,0,4,1,29,2,0,0,10,1,0 778 | 都,27,12,10,9,19,5,1,36,12,0,1,11,21,0 779 | 酒,105,1,1,0,0,0,0,105,2,0,0,0,0,0 780 | 里,18,1,67,159,114,264,2,72,12,0,0,315,222,0 781 | 重,48,6,29,81,15,40,0,77,6,0,0,118,18,0 782 | 野,447,120,2190,5,0,12,0,2556,200,1,0,17,0,0 783 | 金,365,6,13,7,0,0,15,360,9,0,0,7,0,0 784 | 釜,12,0,5,0,0,0,0,16,1,0,0,0,0,0 785 | 鈴,596,0,1,11,0,13,1,596,0,0,0,22,2,0 786 | 鉄,4,0,0,24,0,1,0,3,1,0,1,24,0,0 787 | 鍋,12,1,21,0,0,0,0,33,1,0,0,0,0,0 788 | 鎌,50,0,1,1,0,0,0,51,0,0,0,1,0,0 789 | 長,532,4,21,15,0,8,7,371,178,1,1,21,1,0 790 | 門,54,0,19,0,0,14,0,72,1,0,0,11,1,2 791 | 開,5,0,8,0,0,1,3,10,0,0,1,0,0,0 792 | 間,23,8,215,0,0,0,2,192,52,0,0,0,0,0 793 | 関,173,0,38,0,0,0,65,146,0,0,0,0,0,0 794 | 阪,33,0,17,0,0,0,0,50,0,0,0,0,0,0 795 | 阿,212,0,0,5,0,0,0,190,22,0,0,2,3,0 796 | 陸,3,0,1,5,0,1,1,3,0,0,0,5,1,0 797 | 陽,0,0,0,329,1,37,0,0,0,0,20,321,26,0 798 | 隅,7,0,5,0,0,0,0,12,0,0,0,0,0,0 799 | 隆,3,0,0,436,1,256,3,0,0,0,124,555,14,0 800 | 隈,2,0,10,0,0,0,0,12,0,0,0,0,0,0 801 | 階,2,4,4,0,0,0,1,5,4,0,0,0,0,0 802 | 隼,0,0,0,25,0,6,0,0,0,0,5,26,0,0 803 | 雄,1,0,0,319,0,592,0,1,0,0,7,818,86,0 804 | 雅,0,0,0,620,2,32,0,0,0,0,10,641,2,1 805 | 難,16,0,0,0,0,0,0,16,0,0,0,0,0,0 806 | 雨,19,0,0,1,0,0,0,19,0,0,0,1,0,0 807 | 雪,5,0,0,18,0,34,0,5,0,0,1,51,0,0 808 | 雲,2,0,10,1,0,2,0,11,1,0,0,3,0,0 809 | 露,12,0,0,1,0,0,0,11,1,0,0,1,0,0 810 | 青,269,1,0,3,0,3,2,267,1,0,0,6,0,0 811 | 靖,0,0,0,132,0,57,0,0,0,0,41,148,0,0 812 | 静,2,0,0,41,0,5,1,1,0,0,5,41,0,0 813 | 音,7,0,1,1,3,13,0,8,0,0,0,12,5,0 814 | 順,0,0,0,162,0,18,0,0,0,0,13,161,6,0 815 | 須,109,17,41,0,4,0,0,145,22,0,0,0,4,0 816 | 頭,0,0,36,0,0,0,0,33,1,2,0,0,0,0 817 | 頼,6,0,1,15,0,3,0,7,0,0,0,18,0,0 818 | 顕,0,0,0,32,0,15,0,0,0,0,5,34,8,0 819 | 風,18,2,2,2,0,1,0,18,4,0,0,3,0,0 820 | 飛,18,0,2,5,1,1,0,19,1,0,0,5,2,0 821 | 飯,180,2,0,0,0,0,0,176,6,0,0,0,0,0 822 | 飼,1,0,13,0,0,0,0,14,0,0,0,0,0,0 823 | 香,39,0,4,218,91,418,0,41,1,1,39,484,204,0 824 | 馨,0,0,0,2,0,12,0,0,0,0,10,2,2,0 825 | 馬,91,0,53,0,1,32,1,139,4,0,0,31,2,0 826 | 駒,20,0,8,1,0,0,0,28,0,0,0,1,0,0 827 | 駿,1,0,0,4,0,5,0,1,0,0,5,3,1,0 828 | 高,1069,0,55,113,0,34,4,1107,13,0,6,141,0,0 829 | 髙,231,0,15,1,0,0,1,243,2,0,0,1,0,0 830 | 鬼,19,0,4,0,0,1,1,22,0,0,0,1,0,0 831 | 魚,14,0,0,0,0,0,0,14,0,0,0,0,0,0 832 | 鮎,4,0,1,5,0,0,0,5,0,0,0,5,0,0 833 | 鳥,56,2,19,0,0,4,0,74,3,0,0,4,0,0 834 | 鳴,10,0,0,1,0,0,0,10,0,0,0,1,0,0 835 | 鴨,11,0,1,0,0,0,1,10,1,0,0,0,0,0 836 | 鵜,24,1,0,0,0,0,0,24,1,0,0,0,0,0 837 | 鶴,50,0,5,2,10,23,0,54,1,0,0,25,10,0 838 | 鷲,17,0,6,0,0,0,0,21,2,0,0,0,0,0 839 | 鷹,7,0,4,2,0,0,0,11,0,0,0,2,0,0 840 | 鹿,36,0,13,0,0,0,0,43,6,0,0,0,0,0 841 | 麗,0,0,0,19,0,4,0,0,0,0,3,20,0,0 842 | 麻,16,0,3,320,0,8,0,19,0,0,0,197,131,0 843 | 黒,185,0,46,0,0,0,0,230,1,0,0,0,0,0 844 | 齊,64,0,1,1,0,0,0,65,0,0,0,0,1,0 845 | 齋,163,0,2,0,0,0,1,164,0,0,0,0,0,0 846 | 龍,7,0,0,98,0,19,3,4,0,0,14,83,20,0 847 | 﨑,4,0,229,0,0,0,0,229,4,0,0,0,0,0 848 | 葵,1,0,0,0,0,9,0,1,0,0,6,3,0,0 849 | --------------------------------------------------------------------------------