Module: VivlioStarter::CLI::Metrics::Readability

Defined in:
lib/vivlio_starter/cli/metrics/readability.rb

Overview

建石式リーダビリティーの算出ロジック。

Constant Summary collapse

COEFFICIENTS =

建石・小野・山田 (1988) の確定係数。

{ ls: -0.12, la: -1.37, lh: 7.4, lc: -23.18, lk: -5.4, cp: -4.67 }.freeze
CONSTANT =
115.79
SENTENCE_DELIMITER =
/[。!?!?]+/
PERIOD =
''
COMMA =
''

Class Method Summary collapse

Class Method Details

.aggregate(features_list) ⇒ ReadabilityFeatures

章ごとの特徴量を合算する(全体 RS 算出用)。

Parameters:

Returns:



109
110
111
112
113
114
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 109

def aggregate(features_list)
  return ReadabilityFeatures.zero if features_list.empty?

  ReadabilityFeatures.members.to_h { |field| [field, features_list.sum { it.public_send(field) }] }
                     .then { ReadabilityFeatures.new(**it) }
end

.avg(sum, count) ⇒ Object



153
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 153

def avg(sum, count) = count.positive? ? sum.to_f / count : 0.0

.character_class(char) ⇒ Object

1 文字の文字種を判定する(対象外は nil で連を区切る)。 長音符(ー)はカタカナ語に付くため、カタカナ連として扱う。



144
145
146
147
148
149
150
151
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 144

def character_class(char)
  case char
  when /\p{Han}/ then :kanji
  when /\p{Hiragana}/ then :hira
  when /\p{Katakana}/, '' then :kata
  when /[A-Za-z]/ then :alpha
  end
end

.count_character_runs(text) ⇒ Object

文字種ごとに「連の数」と「連に含まれる文字数」を数える。



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 126

def count_character_runs(text)
  acc = %i[alpha hira kanji kata].to_h { [it, { runs: 0, chars: 0 }] }
  previous = nil

  text.each_char do |char|
    klass = character_class(char)
    if klass
      acc[klass][:chars] += 1
      acc[klass][:runs] += 1 if klass != previous
    end
    previous = klass
  end

  acc
end

.extract_features(text) ⇒ ReadabilityFeatures

テキストから RS 算出用の特徴量を抽出する。

Parameters:

  • text (String)

    コード除去後の本文(prose)

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 65

def extract_features(text)
  sentences = sentence_lengths(text)
  runs = count_character_runs(text)

  ReadabilityFeatures.new(
    sentence_count: sentences.size,
    sentence_char_total: sentences.sum,
    alpha_run_count: runs[:alpha][:runs], alpha_char_total: runs[:alpha][:chars],
    hira_run_count: runs[:hira][:runs], hira_char_total: runs[:hira][:chars],
    kanji_run_count: runs[:kanji][:runs], kanji_char_total: runs[:kanji][:chars],
    kata_run_count: runs[:kata][:runs], kata_char_total: runs[:kata][:chars],
    period_count: text.count(PERIOD),
    comma_count: text.count(COMMA)
  )
end

.label(score, thresholds) ⇒ Object

RS をしきい値で難易度ラベルに変換する(大きいほど易しい)。

Parameters:

  • thresholds (Hash)

    { easy:, standard: } いずれもバンド下限



99
100
101
102
103
104
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 99

def label(score, thresholds)
  return 'Easy' if score >= thresholds[:easy]
  return 'Standard' if score >= thresholds[:standard]

  'Professional'
end

.score(features) ⇒ Float

特徴量から RS を算出する。値が大きいほど読みやすい。

Parameters:

Returns:

  • (Float)


84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 84

def score(features)
  ls = avg(features.sentence_char_total, features.sentence_count)
  la = avg(features.alpha_char_total, features.alpha_run_count)
  lh = avg(features.hira_char_total, features.hira_run_count)
  lc = avg(features.kanji_char_total, features.kanji_run_count)
  lk = avg(features.kata_char_total, features.kata_run_count)
  cp = avg(features.comma_count, features.period_count)

  CONSTANT +
    (COEFFICIENTS[:ls] * ls) + (COEFFICIENTS[:la] * la) + (COEFFICIENTS[:lh] * lh) +
    (COEFFICIENTS[:lc] * lc) + (COEFFICIENTS[:lk] * lk) + (COEFFICIENTS[:cp] * cp)
end

.sentence_lengths(text) ⇒ Object

文ごとの文字数(空白除く)。



119
120
121
122
123
# File 'lib/vivlio_starter/cli/metrics/readability.rb', line 119

def sentence_lengths(text)
  text.split(SENTENCE_DELIMITER)
      .map { it.gsub(/\s/, '').length }
      .reject(&:zero?)
end