Module: VivlioStarter::CLI::Metrics::ContentWords

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

Constant Summary collapse

NOUN_LABELS =

内容語として採用する名詞の細分類 → 表示ラベル。

{
  '固有名詞' => '固有名詞',
  '形容動詞語幹' => '形容動詞',
  'ナイ形容詞語幹' => '形容動詞',
  '一般' => '名詞',
  'サ変接続' => '名詞',
  '副詞可能' => '名詞'
}.freeze
STOPWORDS =

ほぼ機能語として頻出し、内容語の一覧を埋めてしまう一般動詞は除く。

%w[する なる ある いる できる].freeze
WORD_CHAR =

表の区切り(|)や記号など、単語文字を含まないトークンを内容語から除く。

/[\p{Han}\p{Hiragana}\p{Katakana}A-Za-z0-9]/

Class Method Summary collapse

Class Method Details

.base_form(surface, features) ⇒ Object

基本形があればそれを、無ければ(固有名詞など)表層形を使う。



83
84
85
86
# File 'lib/vivlio_starter/cli/metrics/content_words.rb', line 83

def base_form(surface, features)
  base = features[6]
  base && base != '*' ? base : surface
end

.classify(surface, features) ⇒ Object

形態素 1 つを内容語に分類する。内容語でなければ nil。

Parameters:

  • features (Array<String>)

    MeCab の素性(品詞, 細分類1, …, 基本形, …)



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/vivlio_starter/cli/metrics/content_words.rb', line 48

def classify(surface, features)
  pos = features[0]
  sub = features[1]
  word = base_form(surface, features)
  return nil if STOPWORDS.include?(word)
  return nil unless word.match?(WORD_CHAR)

  label = label_for(pos, sub)
  return nil unless label
  # 単漢字の「固有名詞」は MeCab の誤判定が大半(章・全・行 など)。
  # 正当な固有名詞(PDF・Vivliostyle 等)は 2 字以上なので、ここで弾く。
  return nil if label == '固有名詞' && word.length == 1

  ContentWord.new(word:, pos: label)
end

.label_for(pos, sub) ⇒ Object

--- 内部ヘルパー ---



74
75
76
77
78
79
80
# File 'lib/vivlio_starter/cli/metrics/content_words.rb', line 74

def label_for(pos, sub)
  case pos
  when '名詞' then NOUN_LABELS[sub]
  when '動詞' then sub == '自立' ? '動詞' : nil
  when '形容詞' then sub == '自立' ? '形容詞' : nil
  end
end

.rank(words, limit:) ⇒ Object

内容語の配列を頻度順(同数は語の昇順)に上位 limit 件へ集計する。



65
66
67
68
69
70
# File 'lib/vivlio_starter/cli/metrics/content_words.rb', line 65

def rank(words, limit:)
  words.each_with_object(Hash.new(0)) { |w, tally| tally[[w.word, w.pos]] += 1 }
       .sort_by { |(word, _pos), count| [-count, word] }
       .first(limit)
       .map { |(word, pos), count| RankedWord.new(word:, pos:, count:) }
end