japanese_names 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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