Module: VivlioStarter::CLI::Metrics::KanjiLevels
- Defined in:
- lib/vivlio_starter/cli/metrics/kanji_levels.rb
Constant Summary collapse
- DATA_PATH =
File.('data/kyoiku_joyo_kanji.tsv', __dir__)
- HAN =
/\p{Han}/- CHUGAKU =
同梱データで L1(中学で習う常用漢字)を表す値。これ以外の値は配当学年(1〜6)。
'中学'- GRADES =
教育漢字(L0)の配当学年。
(1..6).freeze
- LABELS =
表示ラベルと内訳の並び順。
{ kyoiku: '教育', chugaku: '中学', ippan: '一般(L2)', senmon: '専門(L3)', gaiji: 'JIS外' }.freeze
- ORDER =
%i[kyoiku chugaku ippan senmon gaiji].freeze
- LIST_LABELS =
一覧・出現箇所の表示上限。
{ chugaku: '中学漢字', ippan: '一般漢字(L2)', senmon: '専門漢字(L3)' }.freeze
- LOCATION_CHAR_LIMIT =
15- LOCATION_PER_CHAR =
5
Class Method Summary collapse
-
.build_report(sentences) ⇒ Object
位置つきの文の列から、漢字レベルの集計レポートを作る。漢字が無ければ nil。.
- .candidate_lists(chars) ⇒ Object
-
.grade_of(char) ⇒ Object
教育漢字(L0)の配当学年 1〜6 を返す。教育漢字でなければ nil。.
-
.jis_level(char) ⇒ Object
JIS X 0208 の区(EUC-JP の第 1 バイト)で第一/第二水準を分ける。.
-
.level_of(char) ⇒ Object
漢字 1 文字のレベルを返す。教育・中学は同梱データ、それ以外は符号位置で判定。 :kyoiku を返すのは grade_of が学年を返す文字と過不足なく一致する (判定を grade_of に委ねているため、両者がずれることが構造的に起こらない)。.
- .load_table ⇒ Object
-
.locations(chars) ⇒ Object
一般・専門漢字(稀でルビを振りたい)を位置つきで返す。 挙げる漢字は出現の少ない順に選ぶ(稀なものほどルビが要る)が、 並べる順は初出の章・行にする——著者は原稿を頭から開いてルビを書き足すため、 出現順に並んでいないとファイルを行き来することになる。.
- .ratios(totals) ⇒ Object
- .scan_kanji(sentence, totals, chars) ⇒ Object
-
.table ⇒ Object
--- 内部ヘルパー ---.
Class Method Details
.build_report(sentences) ⇒ Object
位置つきの文の列から、漢字レベルの集計レポートを作る。漢字が無ければ nil。
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 72 def build_report(sentences) totals = Hash.new(0) chars = {} sentences.each do |sentence| scan_kanji(sentence, totals, chars) end return nil if chars.empty? KanjiLevelReport.new(ratios: ratios(totals), lists: candidate_lists(chars), locations: locations(chars)) end |
.candidate_lists(chars) ⇒ Object
129 130 131 132 133 134 135 136 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 129 def candidate_lists(chars) LIST_LABELS.keys.to_h do |level| entries = chars.select { |_char, rec| rec[:level] == level } .sort_by { |char, rec| [-rec[:count], char] } .map { |char, rec| [char, rec[:count]] } [level, entries] end end |
.grade_of(char) ⇒ Object
教育漢字(L0)の配当学年 1〜6 を返す。教育漢字でなければ nil。
レベル(L0〜L4)だけでは「小4向けの本なら小5以上にルビ」という学年基準の 判定ができないため、同梱データが持っている学年をそのまま取り出せるようにする (furigana-level-spec.md §3.2)。level_of の戻り値は変えないので、 vs metrics の集計・表示には影響しない。
64 65 66 67 68 69 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 64 def grade_of(char) grade = table[char] return nil if grade.nil? || grade == CHUGAKU grade.to_i end |
.jis_level(char) ⇒ Object
JIS X 0208 の区(EUC-JP の第 1 バイト)で第一/第二水準を分ける。
98 99 100 101 102 103 104 105 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 98 def jis_level(char) bytes = char.encode('EUC-JP').bytes return :gaiji unless bytes.size == 2 (bytes[0] - 0xA0) <= 47 ? :ippan : :senmon rescue Encoding::UndefinedConversionError :gaiji end |
.level_of(char) ⇒ Object
漢字 1 文字のレベルを返す。教育・中学は同梱データ、それ以外は符号位置で判定。 :kyoiku を返すのは grade_of が学年を返す文字と過不足なく一致する (判定を grade_of に委ねているため、両者がずれることが構造的に起こらない)。
51 52 53 54 55 56 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 51 def level_of(char) return :chugaku if table[char] == CHUGAKU return :kyoiku if grade_of(char) jis_level(char) end |
.load_table ⇒ Object
88 89 90 91 92 93 94 95 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 88 def load_table File.foreach(DATA_PATH, encoding: 'UTF-8').each_with_object({}) do |line, map| next if line.start_with?('#') || line.strip.empty? char, grade = line.chomp.split("\t") map[char] = grade end end |
.locations(chars) ⇒ Object
一般・専門漢字(稀でルビを振りたい)を位置つきで返す。 挙げる漢字は出現の少ない順に選ぶ(稀なものほどルビが要る)が、 並べる順は初出の章・行にする——著者は原稿を頭から開いてルビを書き足すため、 出現順に並んでいないとファイルを行き来することになる。
142 143 144 145 146 147 148 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 142 def locations(chars) chars.select { |_char, rec| %i[ippan senmon].include?(rec[:level]) } .sort_by { |char, rec| [rec[:count], char] } .first(LOCATION_CHAR_LIMIT) .map { |char, rec| [char, rec[:locations].uniq.first(LOCATION_PER_CHAR)] } .sort_by { |_char, places| places.first } end |
.ratios(totals) ⇒ Object
120 121 122 123 124 125 126 127 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 120 def ratios(totals) grand = totals.values.sum.to_f ORDER.filter_map do |level| next if totals[level].zero? [LABELS[level], (totals[level] / grand * 100).round] end end |
.scan_kanji(sentence, totals, chars) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 107 def scan_kanji(sentence, totals, chars) sentence.text.each_char do |char| next unless char.match?(HAN) level = level_of(char) totals[level] += 1 record = (chars[char] ||= { level:, count: 0, locations: [] }) record[:count] += 1 # 出現箇所は [章番号, 行] のまま持ち、表示側で章ごとにまとめる。 record[:locations] << [sentence.chapter_num, sentence.line] end end |
.table ⇒ Object
--- 内部ヘルパー ---
86 |
# File 'lib/vivlio_starter/cli/metrics/kanji_levels.rb', line 86 def table = @table ||= load_table |