japanese_names 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 66e24980c69de00af005fe290cb738acadc6a42c
4
- data.tar.gz: fe841d057b518bc964f19d79004892565e6d80e0
3
+ metadata.gz: 6da3f9dacc4174a93a90ea9bb7dd707d4a51346c
4
+ data.tar.gz: 164351ec8ed4957d82bef3001142b802562c10c0
5
5
  SHA512:
6
- metadata.gz: e8392c49fe7e1d091889af90fd78e80b4ce41635c25a70247caf317f3697d047f85dfb17357c7b1ed53898974ad73de8e271cb2b56f76c9c4ba8a6fe75b7d304
7
- data.tar.gz: 928c0867d2ccdcaaf502d1318d2e2126cb29e5f719c414fe6b40ca530bc2a39985be870b78f3a1ea7f697f8f5931575df25b23390bfb7107f53f22459dd63f8a
6
+ metadata.gz: 30db2c3ae0959b4012e79ea257a2dcb87a6b86964fb1934540689e7b751c36a98f3749c6334a67044f3516258e67fb984ada3be8db13d1d59837072523fd76a6
7
+ data.tar.gz: 493dc8d0ed918f1e213cc7bf0526ac21298ea64d4915e4fdc4a592727dbd0c577d8ddda12ce432dba25746a9d28fddb960c29688c207cebb33b784b5f5efafa8
data/README.md CHANGED
@@ -39,7 +39,7 @@ Note that romaji data has been removed from our `enamdict.min` file in the compr
39
39
 
40
40
  ### Splitter#split
41
41
 
42
- Currently the main method is `split` which, given a kanji and kana representation of a name splits
42
+ The main method is `split` which, given a kanji and kana representation of a name splits
43
43
  into to family/given names.
44
44
 
45
45
  ```ruby
@@ -47,36 +47,46 @@ into to family/given names.
47
47
  splitter.split('堺雅美', 'さかいマサミ') #=> [['堺', '雅美'], ['さかい', 'マサミ']]
48
48
  ```
49
49
 
50
+ Over a test corpus of over 22,000 names it yields a failure rate of less than 0.5%.
51
+
50
52
  The logic is as follows:
51
53
 
52
- * Step 1: Split kanji name into possible surname sub-strings
54
+ * Step 1: Split kanji name into possible sub-strings from the middle out-ward.
53
55
 
54
56
  ```
55
57
  上原亜沙子 =>
56
58
 
57
- 上原亜沙子
58
- 上原亜沙
59
- 上原亜
60
- 上原
61
-
59
+ 上原 亜沙子
60
+ 上原亜 沙子
61
+ 上 原亜沙子
62
+ 上原亜沙 子
62
63
  ```
63
64
 
64
65
  * Step 2: Lookup possible kana matches in dictionary (done in a single pass)
65
66
 
66
67
  ```
67
- 上原亜沙子 => X
68
- 上原亜沙  => X
69
- 上原亜   => X
70
68
  上原    => かみはら かみばら うえはら うえばら...
69
+ 亜沙子 => あさこ
70
+ 上原亜   => X
71
+ 亜沙  => さこ
71
72
  上     => かみ うえ ...
73
+ 原亜沙子  => X
74
+ ...
72
75
  ```
73
76
 
74
- * Step 3: Compare kana lookups versus kana name and detect first match (starting from longest candidate string)
77
+ * Step 3: Compare kana lookups versus kana name and detect first match.
78
+ If the kana string can be matched from both sides and yield the same result,
79
+ we will return that result immediately. Otherwise we return the first single sided match
80
+ found.
75
81
 
76
82
  ```
77
83
  うえはらあさこ contains かみはら ? => X
78
84
  うえはらあさこ contains かみばら ? => X
79
85
  うえはらあさこ contains うえはら ? => YES! [うえはら]あさこ
86
+
87
+ うえはらあさこ contains あさこ ? => YES! うえはら[あさこ]
88
+
89
+ Double-sided match found! ==> Return immediately
80
90
  ```
81
91
 
82
92
  * Step 4: If match found, split names accordingly
@@ -86,22 +96,7 @@ The logic is as follows:
86
96
  [うえはら]あさこ => うえはら あさこ
87
97
  ```
88
98
 
89
- * Step 5: If match not found, repeat steps 1-4 in reverse for given name:
90
-
91
- ```
92
- 上原亜沙子 =>
93
-
94
- 上原亜沙子 => X
95
-  原亜沙子 => X
96
-   亜沙子 => あさこ
97
-    沙子 => さこ
98
-     子 => こ
99
-
100
- 上原[亜沙子] => 上原 亜沙子
101
- うえはら[あさこ] => うえはら あさこ
102
- ```
103
-
104
- * Step 6: If match still not found, return `nil`
99
+ * Step 5: If match still not found, return `nil`
105
100
 
106
101
 
107
102
  ## Rake Tasks
@@ -1,25 +1,41 @@
1
- module JapaneseNames
2
- module Backend
3
- module Memory
4
- class Store
1
+ # frozen_string_literal: true
5
2
 
6
- class << self
3
+ module JapaneseNames
4
+ module Backend
5
+ module Memory
6
+ # In-memory store of the Enamdict dictionary
7
+ class Store
8
+ class << self
9
+ # Public: Finds kanji and/or kana regex strings in the dictionary via
10
+ # a structured query interface.
11
+ #
12
+ # kanji - (String, Array) Value or array of values of the kanji name to match.
13
+ #
14
+ # Returns the dict entries as an Array of Arrays [[kanji, kana, flags], ...]
15
+ def find(kanji)
16
+ kanji = Array(kanji)
17
+ store.values_at(*kanji).reject(&:nil?).inject(&:+) || []
18
+ end
7
19
 
8
- # Public: The memoized dictionary instance.
9
- def store
10
- @store ||= File.open(filepath, 'r:utf-8').map do |line|
11
- line.chop.split('|').map(&:freeze).freeze
12
- end.freeze
13
- end
20
+ # Public: The memoized dictionary instance.
21
+ def store
22
+ @store ||= JapaneseNames::Util::Kernel.deep_freeze(
23
+ File.open(filepath, 'r:utf-8').each_with_object({}) do |line, hash|
24
+ ary = line.chop.split('|')
25
+ hash[ary[0]] ||= []
26
+ hash[ary[0]] << ary
27
+ end
28
+ )
29
+ end
14
30
 
15
- private
31
+ private
16
32
 
17
- # Internal: Returns the filepath to the enamdict.min file.
18
- def filepath
19
- File.join(JapaneseNames.root, 'bin/enamdict.min')
33
+ # Internal: Returns the filepath to the enamdict.min file.
34
+ def filepath
35
+ File.join(JapaneseNames.root, 'bin/enamdict.min')
36
+ end
37
+ end
20
38
  end
21
39
  end
22
40
  end
23
41
  end
24
- end
25
- end
@@ -1,15 +1,16 @@
1
- module JapaneseNames
1
+ # frozen_string_literal: true
2
2
 
3
+ module JapaneseNames
3
4
  # Enumerated flags for the ENAMDICT file (http://www.csse.monash.edu.au/~jwb/enamdict_doc.html)
4
5
  module Enamdict
5
- NAME_PLACE = %i(p).freeze # place-name (99,500)
6
- NAME_PERSON = %i(u).freeze # person name, either given or surname, as-yet unclassified (139,000)
7
- NAME_SURNAME = %i(s).freeze # surname (138,500)
8
- NAME_GIVEN_MALE = %i(m).freeze # male given name (14,500)
9
- NAME_GIVEN_FEMALE = %i(f).freeze # female given name (106,300)
10
- NAME_GIVEN_OTHER = %i(g).freeze # given name, as-yet not classified by sex (64,600)
6
+ NAME_PLACE = %i[p].freeze # place-name (99,500)
7
+ NAME_PERSON = %i[u].freeze # person name, either given or surname, as-yet unclassified (139,000)
8
+ NAME_SURNAME = %i[s].freeze # surname (138,500)
9
+ NAME_GIVEN_MALE = %i[m].freeze # male given name (14,500)
10
+ NAME_GIVEN_FEMALE = %i[f].freeze # female given name (106,300)
11
+ NAME_GIVEN_OTHER = %i[g].freeze # given name, as-yet not classified by sex (64,600)
11
12
  NAME_SURNAME_ANY = (NAME_PLACE | NAME_PERSON | NAME_SURNAME).freeze
12
- NAME_GIVEN_ANY = (NAME_PERSON | NAME_GIVEN_MALE| NAME_GIVEN_FEMALE | NAME_GIVEN_OTHER).freeze
13
+ NAME_GIVEN_ANY = (NAME_PERSON | NAME_GIVEN_MALE | NAME_GIVEN_FEMALE | NAME_GIVEN_OTHER).freeze
13
14
  NAME_ANY = (NAME_SURNAME_ANY | NAME_GIVEN_ANY).freeze
14
15
  end
15
16
  end
@@ -1,21 +1,21 @@
1
- module JapaneseNames
1
+ # frozen_string_literal: true
2
2
 
3
+ module JapaneseNames
3
4
  # Query interface for ENAMDICT
4
5
  class Finder
5
-
6
6
  # Hash opts
7
7
  # - kanji: String kanji to match
8
8
  # - kana: String kana to match
9
9
  # - kanji: Array<Symbol> ENAMDICT flags to match
10
- def find(opts={})
11
- backend.find(opts)
10
+ def find(*args)
11
+ backend.find(*args)
12
12
  end
13
13
 
14
14
  private
15
15
 
16
16
  # Internal: Builds regex criteria for name.
17
17
  def backend
18
- ::JapaneseNames::Backend::Memory::Finder
18
+ ::JapaneseNames::Backend::Memory::Store
19
19
  end
20
20
  end
21
21
  end
@@ -1,8 +1,8 @@
1
- module JapaneseNames
1
+ # frozen_string_literal: true
2
2
 
3
+ module JapaneseNames
3
4
  # Provides methods to split a full Japanese name strings into surname and given name.
4
5
  class Splitter
5
-
6
6
  # Given a kanji and kana representation of a name splits into to family/given names.
7
7
  #
8
8
  # The choice to prioritize family name is arbitrary. Further analysis is needed
@@ -11,37 +11,62 @@ module JapaneseNames
11
11
  # Returns Array [[kanji_fam, kanji_giv], [kana_fam, kana_giv]] if there was a match.
12
12
  # Returns nil if there was no match.
13
13
  def split(kanji, kana)
14
- split_surname(kanji, kana) || split_given(kanji, kana)
14
+ return nil unless kanji && kana
15
+ kanji = kanji.strip
16
+ kana = kana.strip
17
+
18
+ # Partition kanji into candidate n-grams
19
+ kanji_ngrams = Util::Ngram.ngram_partition(kanji)
20
+
21
+ # Find all possible matches of all kanji n-grams in dictionary
22
+ dict = finder.find(kanji_ngrams.flatten.uniq)
23
+
24
+ first_lhs_match = nil
25
+ first_rhs_match = nil
26
+ kanji_ngrams.each do |kanji_pair|
27
+ lhs_dict = dict.select { |d| d[0] == kanji_pair[0] }
28
+ rhs_dict = dict.select { |d| d[0] == kanji_pair[1] }
29
+
30
+ lhs_match = detect_lhs(lhs_dict, kanji, kana)
31
+ rhs_match = detect_rhs(rhs_dict, kanji, kana)
32
+
33
+ return lhs_match if lhs_match && lhs_match == rhs_match
34
+
35
+ first_lhs_match ||= lhs_match
36
+ first_rhs_match ||= rhs_match
37
+ end
38
+
39
+ # As a fallback, return single-sided match prioritizing surname match first
40
+ first_lhs_match || first_rhs_match
15
41
  end
16
42
 
17
- def split_giv(kanji, kana)
18
- return nil unless kanji && kana
19
- kanji, kana = kanji.strip, kana.strip
20
- dict = finder.find(kanji: Util::Ngram.ngram_right(kanji))
21
- dict.sort!{|x,y| y[0].size <=> x[0].size}
22
- kana_match = nil
23
- if match = dict.detect{|m| kana_match = kana[/#{hk m[1]}\z/]}
24
- return [[Util::Ngram.mask_right(kanji, match[0]), match[0]],[Util::Ngram.mask_right(kana, kana_match), kana_match]]
43
+ private
44
+
45
+ def detect_lhs(dict, kanji, kana)
46
+ dict_match = dict.select { |d| match_kana_lhs(d, kana) }.sort_by { |m| m[1].size * -1 }.first
47
+ if dict_match
48
+ kana_match = match_kana_lhs(dict_match, kana)
49
+ return [[dict_match[0], Util::Ngram.mask_left(kanji, dict_match[0])],
50
+ [kana_match, Util::Ngram.mask_left(kana, kana_match)]]
25
51
  end
26
52
  end
27
- alias :split_given :split_giv
28
53
 
29
- def split_sur(kanji, kana)
30
- return nil unless kanji && kana
31
- kanji, kana = kanji.strip, kana.strip
32
- dict = finder.find(kanji: Util::Ngram.ngram_left(kanji))
33
- dict.sort!{|x,y| y[0].size <=> x[0].size}
34
- kana_match = nil
35
- if match = dict.detect{|m| kana_match = kana[/\A#{hk m[1]}/]}
36
- return [[match[0], Util::Ngram.mask_left(kanji, match[0])],[kana_match, Util::Ngram.mask_left(kana, kana_match)]]
54
+ def detect_rhs(dict, kanji, kana)
55
+ dict_match = dict.select { |d| match_kana_rhs(d, kana) }.sort_by { |m| m[1].size * -1 }.first
56
+ if dict_match
57
+ kana_match = match_kana_rhs(dict_match, kana)
58
+ return [[Util::Ngram.mask_right(kanji, dict_match[0]), dict_match[0]],
59
+ [Util::Ngram.mask_right(kana, kana_match), kana_match]]
37
60
  end
38
61
  end
39
- alias :split_surname :split_sur
40
62
 
41
- # TODO: add option to strip honorific '様'
42
- # TODO: add option to infer sex (0 = unknown, 1 = male, 2 = female as per ISO/IEC 5218)
63
+ def match_kana_lhs(dict, kana)
64
+ kana[/\A#{hk dict[1]}/]
65
+ end
43
66
 
44
- private
67
+ def match_kana_rhs(dict, kana)
68
+ kana[/#{hk dict[1]}\z/]
69
+ end
45
70
 
46
71
  # Returns a regex string which matches both hiragana and katakana variations of a String.
47
72
  def hk(str)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JapaneseNames
4
+ module Util
5
+ # Provides extensions to Ruby kernel.
6
+ class Kernel
7
+ class << self
8
+ # Recursively freezes an object
9
+ def deep_freeze(object)
10
+ case object
11
+ when Hash
12
+ object.each_value { |v| deep_freeze(v) }
13
+ object.freeze
14
+ when Array
15
+ object.each { |j| deep_freeze(j) }
16
+ object.freeze
17
+ when String
18
+ object.freeze
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,46 +1,49 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JapaneseNames
2
- module Util
4
+ module Util
5
+ # Provides methods for parsing Japanese name strings.
6
+ class Ngram
7
+ class << self
8
+ # Generates middle-out partition n-grams for a string
9
+ def ngram_partition(str)
10
+ size = str.size
11
+ spiral_partition_indexes(size).map do |i|
12
+ index_partition(str, i)
13
+ end
14
+ end
3
15
 
4
- # Provides methods for parsing Japanese name strings.
5
- class Ngram
16
+ # Partitions a string based on an index
17
+ def index_partition(str, i)
18
+ [str[0...i], str[i..-1]]
19
+ end
6
20
 
7
- class << self
21
+ # Lists middle-out partition points for a given string length
22
+ def spiral_partition_indexes(size)
23
+ ary = []
24
+ last = size / 2
25
+ ary << last
26
+ (size - 2).times do |i|
27
+ last += (i + 1) * (-1)**i
28
+ ary << last
29
+ end
30
+ ary
31
+ end
8
32
 
9
- # Given a String, returns an ordered array of all possible substrings.
10
- #
11
- # Example: ngram_right("abcd") #=> ["abcd", "abc", "bcd", "ab", "bc", "cd", "a", "b", "c", "d"]
12
- def ngram(str)
13
- (0...str.size).to_a.reverse.map{|i| (0...(str.size-i)).map{|j| str[j..(i+j)]}}.flatten.uniq
14
- end
33
+ # Masks a String from the left side and returns the remaining (right) portion of the String.
34
+ #
35
+ # Example: mask_left("abcde", "ab") #=> "cde"
36
+ def mask_left(str, mask)
37
+ str.gsub(/\A#{mask}/, '')
38
+ end
15
39
 
16
- # Given a String, returns an array of progressively smaller substrings anchored on the left side.
17
- #
18
- # Example: ngram_left("abcd") #=> ["abcd", "abc", "ab", "a"]
19
- def ngram_left(str)
20
- (0...str.size).to_a.reverse.map{|i| str[0..i]}
21
- end
22
-
23
- # Given a String, returns an array of progressively smaller substrings anchored on the right side.
24
- #
25
- # Example: ngram_right("abcd") #=> ["abcd", "bcd", "cd", "d"]
26
- def ngram_right(str)
27
- (0...str.size).map{|i| str[i..-1]}
28
- end
29
-
30
- # Masks a String from the left side and returns the remaining (right) portion of the String.
31
- #
32
- # Example: mask_left("abcde", "ab") #=> "cde"
33
- def mask_left(str, mask)
34
- str.gsub(/^#{mask}/, '')
35
- end
36
-
37
- # Masks a String from the right side and returns the remaining (left) portion of the String.
38
- #
39
- # Example: mask_right("abcde", "de") #=> "abc"
40
- def mask_right(str, mask)
41
- str.gsub(/#{mask}$/, '')
40
+ # Masks a String from the right side and returns the remaining (left) portion of the String.
41
+ #
42
+ # Example: mask_right("abcde", "de") #=> "abc"
43
+ def mask_right(str, mask)
44
+ str.gsub(/#{mask}\z/, '')
45
+ end
42
46
  end
43
47
  end
44
48
  end
45
49
  end
46
- end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JapaneseNames
2
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
3
5
  end
@@ -1,4 +1,6 @@
1
- $:.unshift File.dirname(__FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
4
 
3
5
  require 'moji'
4
6
 
@@ -6,10 +8,11 @@ require 'japanese_names/version'
6
8
  require 'japanese_names/enamdict'
7
9
  require 'japanese_names/finder'
8
10
  require 'japanese_names/splitter'
11
+ require 'japanese_names/util/kernel'
9
12
  require 'japanese_names/util/ngram'
10
13
  require 'japanese_names/backend/memory/store'
11
- require 'japanese_names/backend/memory/finder'
12
14
 
15
+ # Root namespace for library
13
16
  module JapaneseNames
14
17
  def self.root
15
18
  File.join(File.dirname(__FILE__), '../')
data/spec/config.yml ADDED
@@ -0,0 +1,422 @@
1
+ ---
2
+ :last_names:
3
+ - 青木 あおき
4
+ - 秋保 アキホ
5
+ - 阿部 あべ
6
+ - 新井 あらい
7
+ - 安藤 あんどう
8
+ - 池田 いけだ
9
+ - 石井 いしい
10
+ - 石川 いしかわ
11
+ - 石田 いしだ
12
+ - 伊藤 イトウ
13
+ - 伊藤 いとう
14
+ - 犬山 イヌヤマ
15
+ - 井上 いのうえ
16
+ - 今井 いまい
17
+ - 岩崎 いわさき
18
+ - 上田 うえだ
19
+ - 上野 うえの
20
+ - 上原 ウエハラ
21
+ - 内田 うちだ
22
+ - 遠藤 えんどう
23
+ - 太田 おおた
24
+ - 大塚 おおつか
25
+ - 大野 おおの
26
+ - 岡田 オカダ
27
+ - 岡田 おかだ
28
+ - 岡本 おかもと
29
+ - 小川 おがわ
30
+ - 小野 おの
31
+ - 加藤 かとう
32
+ - 金子 かねこ
33
+ - 河野 かわの
34
+ - 菊池 きくち
35
+ - 木村 きむら
36
+ - 工藤 くどう
37
+ - 熊澤 クマザワ
38
+ - 小島 こじま
39
+ - 後藤 ごとう
40
+ - 小早志 コバヤシ
41
+ - 小林 こばやし
42
+ - 小山 こやま
43
+ - 近藤 こんどう
44
+ - 斎藤 さいとう
45
+ - 斉藤 さいとう
46
+ - 堺 サカイ
47
+ - 酒井 さかい
48
+ - 堺 さかい
49
+ - 坂本 さかもと
50
+ - 桜井 さくらい
51
+ - 佐々木 ささき
52
+ - 佐藤 サトウ
53
+ - 佐藤 さとう
54
+ - 佐野 さの
55
+ - 柴田 しばた
56
+ - 島田 しまだ
57
+ - 清水 しみず
58
+ - 菅原 すがわら
59
+ - 杉山 すぎやま
60
+ - 鈴木 すずき
61
+ - 高木 たかぎ
62
+ - 高田 たかだ
63
+ - 高野 たかの
64
+ - 高橋 たかはし
65
+ - 竹内 たけうち
66
+ - 武田 たけだ
67
+ - 田中 タナカ
68
+ - 田中 たなか
69
+ - 谷口 たにぐち
70
+ - 田村 たむら
71
+ - 千葉 ちば
72
+ - 中川 なかがわ
73
+ - 中島 なかじま
74
+ - 中野 なかの
75
+ - 中村 ナカムラ
76
+ - 中村 なかむら
77
+ - 中山 なかやま
78
+ - 西村 にしむら
79
+ - 野口 のぐち
80
+ - 野村 のむら
81
+ - 橋本 はしもと
82
+ - 長谷川 はせがわ
83
+ - 濱田 ハマダ
84
+ - 林 はやし
85
+ - 原 はら
86
+ - 原田 はらだ
87
+ - 樋口 ヒグチ
88
+ - 平野 ひらの
89
+ - 福田 ふくだ
90
+ - 藤井 ふじい
91
+ - 藤田 ふじた
92
+ - 藤原 ふじわら
93
+ - 古川 ふるかわ
94
+ - 前田 まえだ
95
+ - 増田 ますだ
96
+ - 松井 まつい
97
+ - 松田 まつだ
98
+ - 松本 まつもと
99
+ - 丸山 まるやま
100
+ - 三浦 みうら
101
+ - 光野 ミツノ
102
+ - 宮崎 みやざき
103
+ - 宮本 みやもと
104
+ - 村上 むらかみ
105
+ - 村田 むらた
106
+ - 森 モリ
107
+ - 森 もり
108
+ - 森田 もりた
109
+ - 安田 ヤスダ
110
+ - 山口 やまぐち
111
+ - 山崎 やまさき
112
+ - 山崎 ヤマザキ
113
+ - 山下 やました
114
+ - 山田 やまだ
115
+ - 山本 やまもと
116
+ - 横山 よこやま
117
+ - 吉田 よしだ
118
+ - 和田 わだ
119
+ - 渡辺 わたなべ
120
+ - 渡部 わたなべ
121
+
122
+ :first_names:
123
+ - 愛 あい
124
+ - 愛子 あいこ
125
+ - 愛美 あいみ
126
+ - 愛莉 あいり
127
+ - 愛理 あいり
128
+ - 愛梨 あいり
129
+ - 葵 あおい
130
+ - 昭夫 あきお
131
+ - 明 あきら
132
+ - 明美 あけみ
133
+ - 亜佐子 アサコ
134
+ - 朝陽 あさひ
135
+ - 杏那 あな
136
+ - 雨夜 あまや
137
+ - 彩花 あやか
138
+ - 彩乃 あやの
139
+ - 彩美 あやみ
140
+ - 郁子 イクコ
141
+ - 郁子 いくこ
142
+ - 勇 いさむ
143
+ - 樹 いつき
144
+ - 梅子 うめこ
145
+ - 瑛太 えいた
146
+ - 愛美 えみ
147
+ - 恵美 えみ
148
+ - 恵里 エリ
149
+ - 修 おさむ
150
+ - 海斗 かいと
151
+ - 香 かおり
152
+ - 一樹 かずき
153
+ - 和子 かずこ
154
+ - 香澄 カスミ
155
+ - 霞 かすみ
156
+ - 和美 かずみ
157
+ - 和也 かずや
158
+ - 克己 かつみ
159
+ - 寛 かん
160
+ - 清 きよし
161
+ - 桐子 きりこ
162
+ - 久美子 くみこ
163
+ - 敬子 ケイコ
164
+ - 慶子 けいこ
165
+ - 恵介 けいすけ
166
+ - 健三 けんぞう
167
+ - 健太 けんた
168
+ - 剛 ごう
169
+ - 康平 こうへい
170
+ - 虎太郎 こたろう
171
+ - 心春 こはる
172
+ - 咲希 さき
173
+ - 咲良 さくら
174
+ - 桜 さくら
175
+ - サクラ さくら
176
+ - 貞子 さだこ
177
+ - 禎子 さだこ
178
+ - 幸子 サチコ
179
+ - 幸子 さちこ
180
+ - 皐 さつき
181
+ - 三郎 さぶろ
182
+ - 小夜 さや
183
+ - 小百合 さゆり
184
+ - 重子 しげこ
185
+ - 茂 しげる
186
+ - 静香 しずか
187
+ - 駿 しゅん
188
+ - 翔 しょう
189
+ - 翔太 しょうた
190
+ - 四郎 しろ
191
+ - 真 しん
192
+ - 真一 しんいち
193
+ - 進 すすむ
194
+ - 節子 せつこ
195
+ - 颯太 そうた
196
+ - 颯真 そうま
197
+ - 蒼空 そら
198
+ - 大雅 たいが
199
+ - 大輝 たいき
200
+ - 大樹 だいき
201
+ - 大輝 だいき
202
+ - 大輔 だいすけ
203
+ - 大地 だいち
204
+ - 貴子 タカコ
205
+ - 孝 たかし
206
+ - 匠 たくみ
207
+ - 拓也 たくや
208
+ - 丈夫 たけお
209
+ - 武 たけし
210
+ - 武 たける
211
+ - 正 ただし
212
+ - 千夏 ちなつ
213
+ - 千代 ちよ
214
+ - 千代子 ちよこ
215
+ - 翼 つばさ
216
+ - 剛 つよし
217
+ - 貞子 ていこ
218
+ - 徹男 テツオ
219
+ - 哲也 てつや
220
+ - 智子 ともこ
221
+ - 知美 トモミ
222
+ - 朋美 ともみ
223
+ - 知美 ともみ
224
+ - 直樹 なおき
225
+ - 直子 なおこ
226
+ - 直美 なおみ
227
+ - 夏希 なつき
228
+ - 七海 ななみ
229
+ - 望 ノゾミ
230
+ - 延 のぶ
231
+ - 法子 のりこ
232
+ - 華 はな
233
+ - 隼人 はやと
234
+ - 遥 はるか
235
+ - 大翔 はると
236
+ - 陽翔 はると
237
+ - 陽斗 はると
238
+ - 悠人 はると
239
+ - 春菜 はるな
240
+ - 悠真 はるま
241
+ - 瞳 ひとみ
242
+ - 陽菜 ひな
243
+ - 陽向 ひなた
244
+ - 陽太 ひなた
245
+ - 陽葵 ひまり
246
+ - 大樹 ひろき
247
+ - 弘子 ひろこ
248
+ - 寛 ひろし
249
+ - 大翔 ひろと
250
+ - 文子 ふみこ
251
+ - 紅子 べにこ
252
+ - 螢 ほたる
253
+ - 舞 まい
254
+ - 誠 まこと
255
+ - 真 まこと
256
+ - 正男 まさお
257
+ - 正博 まさひろ
258
+ - 雅美 マサミ
259
+ - 勝 まさる
260
+ - 愛菜 まな
261
+ - 学 まなぶ
262
+ - 愛美 マナミ
263
+ - 真美 まみ
264
+ - 真弓 まゆみ
265
+ - 美羽 みう
266
+ - 美咲 みさき
267
+ - 美智 ミチ
268
+ - 緑 みどり
269
+ - 美菜 みな
270
+ - 湊 みなと
271
+ - 実 みのる
272
+ - 美優 みゆ
273
+ - 心優 みゆ
274
+ - 芽生 めい
275
+ - 芽依 めい
276
+ - めぐみ メグミ
277
+ - 恵 めぐみ
278
+ - 萌 もえ
279
+ - 桃子 ももこ
280
+ - 大和 やまと
281
+ - 結愛 ゆあ
282
+ - 結衣 ゆい
283
+ - 優香 ゆうか
284
+ - 優太 ゆうた
285
+ - 雄大 ゆうだい
286
+ - 悠斗 ゆうと
287
+ - 優斗 ゆうと
288
+ - 悠人 ゆうと
289
+ - 優奈 ゆうな
290
+ - 結菜 ゆうな
291
+ - 悠真 ゆうま
292
+ - 之子 ユキコ
293
+ - 雪子 ゆきこ
294
+ - 豊 ゆたか
295
+ - 優月 ゆづき
296
+ - 結菜 ゆな
297
+ - 由美 ゆみ
298
+ - 由美子 ゆみこ
299
+ - 百合子 ゆりこ
300
+ - 洋子 ヨウコ
301
+ - 陽子 ようこ
302
+ - 陽太 ようた
303
+ - 陽子 よこ
304
+ - 義雄 よしお
305
+ - 良子 よしこ
306
+ - 陸 りく
307
+ - 莉子 りこ
308
+ - 龍之介 りゅうのすけ
309
+ - 涼 りょう
310
+ - 凛 りん
311
+ - 麗華 れいか
312
+ - 玲子 レイコ
313
+ - 蓮 れん
314
+
315
+ :skip:
316
+ - 青木 緑
317
+ - 石井 真
318
+ - 石井 延
319
+ - 石川 舞
320
+ - 石川 緑
321
+ - 石川 湊
322
+ - 今井 真
323
+ - 今井 延
324
+ - 上野 愛
325
+ - 上野 真
326
+ - 太田 舞
327
+ - 太田 湊
328
+ - 岡本 萌
329
+ - 小野 愛
330
+ - 小野 真
331
+ - 金子 萌
332
+ - 河野 愛
333
+ - 河野 真
334
+ - 小山 咲希
335
+ - 小山 真
336
+ - 小山 緑
337
+ - 小山 陸
338
+ - 近藤 康平
339
+ - 近藤 虎太郎
340
+ - 近藤 心春
341
+ - 酒井 真
342
+ - 酒井 延
343
+ - 坂本 萌
344
+ - 桜井 真
345
+ - 桜井 延
346
+ - 佐々木 杏那
347
+ - 佐々木 三郎
348
+ - 佐々木 四郎
349
+ - 佐々木 緑
350
+ - 佐々木 陽子
351
+ - 佐々木 莉子
352
+ - 佐野 愛
353
+ - 佐野 真
354
+ - 柴田 舞
355
+ - 柴田 湊
356
+ - 杉山 真
357
+ - 杉山 緑
358
+ - 杉山 陸
359
+ - 鈴木 緑
360
+ - 高野 愛
361
+ - 高野 真
362
+ - 竹内 匠
363
+ - 田中 延
364
+ - 田中 緑
365
+ - 田中 湊
366
+ - 中野 愛
367
+ - 中野 真
368
+ - 中山 真
369
+ - 中山 緑
370
+ - 中山 陸
371
+ - 橋本 萌
372
+ - 長谷川 杏那
373
+ - 長谷川 三郎
374
+ - 長谷川 四郎
375
+ - 長谷川 陽子
376
+ - 林 咲希
377
+ - 林 孝
378
+ - 原 咲希
379
+ - 原 延
380
+ - 原 法子
381
+ - 平野 愛
382
+ - 平野 真
383
+ - 藤井 真
384
+ - 藤井 延
385
+ - 藤田 舞
386
+ - 藤田 湊
387
+ - 藤原 延
388
+ - 藤原 法子
389
+ - 古川 舞
390
+ - 古川 緑
391
+ - 古川 湊
392
+ - 松井 真
393
+ - 松井 延
394
+ - 松本 萌
395
+ - 丸山 真
396
+ - 丸山 緑
397
+ - 丸山 陸
398
+ - 宮本 萌
399
+ - 村田 舞
400
+ - 村田 湊
401
+ - 森 亜佐子
402
+ - 森 貴子
403
+ - 森 克己
404
+ - 森 久美子
405
+ - 森 咲希
406
+ - 森 孝
407
+ - 森田 舞
408
+ - 森田 湊
409
+ - 山本 萌
410
+ - 横山 真
411
+ - 横山 緑
412
+ - 横山 陸
413
+ - 和田 咲希
414
+ - 和田 重子
415
+ - 和田 茂
416
+ - 和田 静香
417
+ - 和田 駿
418
+ - 和田 翔
419
+ - 和田 翔太
420
+ - 和田 四郎
421
+ - 和田 真
422
+ - 和田 真一
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,14 @@
1
- $:.push File.expand_path('../../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
4
 
3
5
  require 'rubygems'
4
6
  require 'rspec'
7
+ require 'yaml'
5
8
  require 'japanese_names'
6
9
 
7
10
  RSpec.configure do |config|
8
11
  config.mock_with :rspec
12
+
13
+ config.disable_monkey_patching!
9
14
  end
@@ -1,39 +1,14 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe JapaneseNames::Finder do
3
+ require 'spec_helper'
4
4
 
5
+ RSpec.describe JapaneseNames::Finder do
5
6
  subject { described_class.new }
6
7
 
7
8
  describe '#find' do
8
-
9
9
  it 'should match kanji only' do
10
- result = subject.find(kanji: '外世子')
11
- result.should eq [["外世子", "とよこ", "f"]]
12
- end
13
-
14
- it 'should match kana only' do
15
- result = subject.find(kana: 'ならしま')
16
- result.should eq [["樽島", "ならしま", "u"],
17
- ["奈良島", "ならしま", "s"],
18
- ["楢島", "ならしま", "s"],
19
- ["楢嶋", "ならしま", "s"]]
20
- end
21
-
22
- it 'should match both kanji and kana only' do
23
- result = subject.find(kanji: '楢二郎', kana: 'ならじろう')
24
- result.should eq [["楢二郎", "ならじろう", "m"]]
25
- end
26
-
27
- it 'should match flags as String' do
28
- result = subject.find(kana: 'ならしま', flags: 's')
29
- result.should eq [["奈良島", "ならしま", "s"],
30
- ["楢島", "ならしま", "s"],
31
- ["楢嶋", "ならしま", "s"]]
32
- end
33
-
34
- it 'should match flags as Array' do
35
- result = subject.find(kana: 'ならしま', flags: ['u','g'])
36
- result.should eq [["樽島", "ならしま", "u"]]
10
+ result = subject.find('外世子')
11
+ expect(result).to eq [%w[外世子 とよこ f]]
37
12
  end
38
13
  end
39
14
  end
@@ -1,24 +1,35 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe JapaneseNames::Util::Ngram do
3
+ require 'spec_helper'
4
4
 
5
- describe '#ngram' do
6
- it { expect(described_class.ngram("abcd")).to eq ["abcd", "abc", "bcd", "ab", "bc", "cd", "a", "b", "c", "d"] }
5
+ RSpec.describe JapaneseNames::Util::Ngram do
6
+ describe '#ngram_partition' do
7
+ it { expect(described_class.ngram_partition('abcd')).to eq [%w[ab cd], %w[abc d], %w[a bcd]] }
8
+ it { expect(described_class.ngram_partition('abcde')).to eq [%w[ab cde], %w[abc de], %w[a bcde], %w[abcd e]] }
7
9
  end
8
10
 
9
- describe '#ngram_left' do
10
- it { expect(described_class.ngram_left("abcd")).to eq ["abcd", "abc", "ab", "a"] }
11
+ describe '#index_partition' do
12
+ it { expect(described_class.index_partition('abcde', 2)).to eq %w[ab cde] }
11
13
  end
12
14
 
13
- describe '#ngram_right' do
14
- it { expect(described_class.ngram_right("abcd")).to eq ["abcd", "bcd", "cd", "d"] }
15
+ describe '#spiral_partition_indexes' do
16
+ it { expect(described_class.spiral_partition_indexes(0)).to eq [0] }
17
+ it { expect(described_class.spiral_partition_indexes(1)).to eq [0] }
18
+ it { expect(described_class.spiral_partition_indexes(2)).to eq [1] }
19
+ it { expect(described_class.spiral_partition_indexes(3)).to eq [1, 2] }
20
+ it { expect(described_class.spiral_partition_indexes(4)).to eq [2, 3, 1] }
21
+ it { expect(described_class.spiral_partition_indexes(5)).to eq [2, 3, 1, 4] }
22
+ it { expect(described_class.spiral_partition_indexes(6)).to eq [3, 4, 2, 5, 1] }
23
+ it { expect(described_class.spiral_partition_indexes(7)).to eq [3, 4, 2, 5, 1, 6] }
24
+ it { expect(described_class.spiral_partition_indexes(8)).to eq [4, 5, 3, 6, 2, 7, 1] }
25
+ it { expect(described_class.spiral_partition_indexes(9)).to eq [4, 5, 3, 6, 2, 7, 1, 8] }
15
26
  end
16
27
 
17
28
  describe '#mask_left' do
18
- it { expect(described_class.mask_left("abcde", "ab")).to eq "cde" }
29
+ it { expect(described_class.mask_left('abcde', 'ab')).to eq 'cde' }
19
30
  end
20
31
 
21
32
  describe '#mask_right' do
22
- it { expect(described_class.mask_right("abcde", "de")).to eq "abc" }
33
+ it { expect(described_class.mask_right('abcde', 'de')).to eq 'abc' }
23
34
  end
24
35
  end
@@ -1,50 +1,42 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe JapaneseNames::Splitter do
3
+ require 'spec_helper'
4
4
 
5
+ RSpec.describe JapaneseNames::Splitter do
5
6
  subject { described_class.new }
6
7
 
7
8
  describe '#split' do
9
+ config = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'config.yml'))
10
+ skip_list = config[:skip]
8
11
 
9
- [['上原','望','ウエハラ','ノゾミ'],
10
- ['樋口','知美','ヒグチ','ともみ'],
11
- ['堺','雅美','さかい','マサミ'],
12
- ['中村','幸子','ナカムラ','サチコ'],
13
- ['秋保','郁子','アキホ','いくこ'],
14
- ['光野','亜佐子','ミツノ','アサコ'],
15
- ['熊澤','貴子','クマザワ','タカコ']].each do |kanji_fam, kanji_giv, kana_fam, kana_giv|
16
- it "should parse #{kanji_fam+kanji_giv} #{kana_fam+kana_giv}" do
17
- result = subject.split(kanji_fam+kanji_giv, kana_fam+kana_giv)
18
- result.should eq [[kanji_fam, kanji_giv], [kana_fam, kana_giv]]
19
- end
12
+ config[:last_names].each do |last_name|
13
+ config[:first_names].each do |first_name|
14
+ kanji_fam, kana_fam = last_name.split(' ')
15
+ kanji_giv, kana_giv = first_name.split(' ')
20
16
 
21
- it "should parse #{kanji_fam+kanji_giv} #{kana_fam+kana_giv} by given name" do
22
- result = subject.split_giv(kanji_fam+kanji_giv, kana_fam+kana_giv)
23
- result.should eq [[kanji_fam, kanji_giv], [kana_fam, kana_giv]]
24
- end
17
+ next if skip_list.index("#{kanji_fam} #{kanji_giv}")
25
18
 
26
- it "should parse #{kanji_fam+kanji_giv} #{kana_fam+kana_giv} by family name" do
27
- result = subject.split_sur(kanji_fam+kanji_giv, kana_fam+kana_giv)
28
- result.should eq [[kanji_fam, kanji_giv], [kana_fam, kana_giv]]
19
+ it "should parse #{kanji_fam + kanji_giv} #{kana_fam + kana_giv}" do
20
+ result = subject.split(kanji_fam + kanji_giv, kana_fam + kana_giv)
21
+ expect(result).to eq [[kanji_fam, kanji_giv], [kana_fam, kana_giv]]
22
+ end
29
23
  end
30
24
  end
31
25
 
32
- [['XXX','XXX','XXX','XXX']].each do |kanji_fam, kanji_giv, kana_fam, kana_giv|
33
- it "should return nil for invalid name #{kanji_fam+kanji_giv} #{kana_fam+kana_giv}" do
34
- result = subject.split(kanji_fam+kanji_giv, kana_fam+kana_giv)
35
- result.should be_nil
26
+ [%w[XXX XXX XXX XXX]].each do |kanji_fam, kanji_giv, kana_fam, kana_giv|
27
+ it "should return nil for invalid name #{kanji_fam + kanji_giv} #{kana_fam + kana_giv}" do
28
+ result = subject.split(kanji_fam + kanji_giv, kana_fam + kana_giv)
29
+ expect(result).to eq nil
36
30
  end
37
31
  end
38
32
 
39
33
  it 'should strip leading/trailing whitespace' do
40
- subject.split(' 上原望 ', ' ウエハラノゾミ ').should eq [['上原',''],['ウエハラ','ノゾミ']]
41
- subject.split_giv(' 上原望 ', ' ウエハラノゾミ ').should eq [['上原','望'],['ウエハラ','ノゾミ']]
42
- subject.split_sur(' 上原望 ', ' ウエハラノゾミ ').should eq [['上原','望'],['ウエハラ','ノゾミ']]
34
+ expect(subject.split(' 上原望 ', ' ウエハラノゾミ ')).to eq [%w[上原 望], %w[ウエハラ ノゾミ]]
43
35
  end
44
36
 
45
37
  it 'should return nil for nil input' do
46
- subject.split(nil, 'ウエハラノゾミ').should be_nil
47
- subject.split('上原望', nil).should be_nil
38
+ expect(subject.split(nil, 'ウエハラノゾミ')).to eq nil
39
+ expect(subject.split('上原望', nil)).to eq nil
48
40
  end
49
41
  end
50
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: japanese_names
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johnny Shields
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-12 00:00:00.000000000 Z
11
+ date: 2017-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: moji
@@ -76,13 +76,14 @@ files:
76
76
  - README.md
77
77
  - bin/enamdict.min
78
78
  - lib/japanese_names.rb
79
- - lib/japanese_names/backend/memory/finder.rb
80
79
  - lib/japanese_names/backend/memory/store.rb
81
80
  - lib/japanese_names/enamdict.rb
82
81
  - lib/japanese_names/finder.rb
83
82
  - lib/japanese_names/splitter.rb
83
+ - lib/japanese_names/util/kernel.rb
84
84
  - lib/japanese_names/util/ngram.rb
85
85
  - lib/japanese_names/version.rb
86
+ - spec/config.yml
86
87
  - spec/spec_helper.rb
87
88
  - spec/unit/finder_spec.rb
88
89
  - spec/unit/ngram_spec.rb
@@ -107,11 +108,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
108
  version: '0'
108
109
  requirements: []
109
110
  rubyforge_project:
110
- rubygems_version: 2.4.7
111
+ rubygems_version: 2.6.11
111
112
  signing_key:
112
113
  specification_version: 4
113
114
  summary: Tools for parsing japanese names
114
115
  test_files:
116
+ - spec/config.yml
115
117
  - spec/spec_helper.rb
116
118
  - spec/unit/finder_spec.rb
117
119
  - spec/unit/ngram_spec.rb
@@ -1,55 +0,0 @@
1
- module JapaneseNames
2
- module Backend
3
- module Memory
4
- class Finder
5
-
6
- class << self
7
-
8
- # Public: Finds kanji and/or kana regex strings in the dictionary via
9
- # a structured query interface.
10
- #
11
- # opts - The Hash options used to match the dictionary (default: {}):
12
- # kanji: Regex to match kanji name (optional)
13
- # kana: Regex to match kana name (optional)
14
- # flags: Flag or Array of flags to filter the match (optional)
15
- #
16
- # Returns the dict entries as an Array of Arrays [[kanji, kana, flags], ...]
17
- def find(opts={})
18
- return [] unless opts[:kanji] || opts[:kana]
19
- kanji = name_regex opts.delete(:kanji)
20
- kana = name_regex opts.delete(:kana)
21
- flags = flags_regex opts.delete(:flags)
22
- store.select do |row|
23
- (!kanji || row[0] =~ kanji) && (!kana || row[1] =~ kana) && (!flags || row[2] =~ flags)
24
- end
25
- end
26
-
27
- private
28
-
29
- def store
30
- ::JapaneseNames::Backend::Memory::Store.store
31
- end
32
-
33
- # Internal: Builds regex criteria for name.
34
- def name_regex(name)
35
- case name
36
- when String, Symbol then /\A#{name}\z/
37
- when Array then /\A(?:#{name.join('|')})\z/
38
- else nil
39
- end
40
- end
41
-
42
- # Internal: Builds regex criteria for flags.
43
- def flags_regex(flags)
44
- case flags
45
- when ::JapaneseNames::Enamdict::NAME_ANY then nil
46
- when String, Symbol then /[#{flags}]/
47
- when Array then /[#{flags.join}]/
48
- else nil
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end