読者です 読者をやめる 読者になる 読者になる

VB.NETでも形態素解析っぽいことをしてExcel方眼紙から漢字を抽出したい

VB.NET

はじめに

好きなMSILオペコードはldloca.s。どうも弊社です。 いやまぁローカル変数256個越えとか普通しないじゃん。ふつう

皆さんは漢字は得意ですか? 弊社はクッソ苦手です。

パソコンに頼り切っちゃってるのでまぁ当たり前っちゃー当たり前だし、普段パソコンで文章を書くなら別に困りもしないのですが、打ち合わせの時に書記でホワイトボードに書くときに漢字が書けなくて困るんですよね。

そこで思ったのですが、似たような打ち合わせなら似たような漢字が出てくるのでは? つまり議事録から漢字を引っこ抜けば似たような打ち合わせに適用できるのでは?

そして議事録といえばExcel表計算からDB設計からポスター作製までをこなすオールラウンドアプリケーション。

つまり

  • Excel議事録から文字列を抜き出して
  • 形態素解析を行い各要素にばらして
  • 漢字を抜き出し
  • 集計

すればその打ち合わせで使われる漢字の傾向が掴めるって寸法です。

この時点で自分でも思っています。

素直に漢字を勉強しろよ

Excelから文字列の取り出し

とりあえずExcelからデータを抜かないといけません。

ここではEPPlusを使ってデータを取り出します。

ライブラリのインストールはNuGetからサクッとインストールしてください。

今回は特にエクセルの見た目なんてものには興味がなく、ただ文字を引っこ抜ければいいので

  • ディレクトリ直下のエクセルファイルをひたすら列挙し
  • すべてのシートのすべてのセルから文字列を取り出す

という感じでやってます。

Imports OfficeOpenXml
Imports System.IO

Public Class ExcelContentExtractor

    Private Sub New()
        ' Nothing to do.
    End Sub

    Public Shared Function ExtractContent(targetDirectory As String) As IEnumerable(Of String)
        Dim contents = New List(Of String)

        For Each it In Directory.GetFiles(targetDirectory, "*.xlsx")
            Using exc = New ExcelPackage(New FileInfo(it))
                For Each sheet In exc.Workbook.Worksheets
                    For Each cell In sheet.Cells()
                        Dim text = TryCast(cell.Value, String)

                        If Not text Is Nothing Then
                            contents.Add(text)
                        End If
                    Next
                Next
            End Using
        Next

        Return contents
    End Function

End Class

形態素解析

形態素解析MeCabの.NETの移植版であるNMeCabを使用しています。

インストールとか使い方は完全にフィーリングでやったので解説出来ないです。ごめんね

Imports NMeCab

Public Class SentenceSplitter

    Private Sub New()
        ' Nothing to do.
    End Sub

    Public Shared Function Split(sentence As IEnumerable(Of String)) As IEnumerable(Of String)
        Dim param = New MeCabParam() With {.DicDir = ".\ipadic"}
        Dim words = New List(Of String)

        Using mecab = MeCabTagger.Create(param)
            For Each it In sentence
                Dim node = mecab.ParseToNode(it)

                While Not node Is Nothing
                    If node.Feature Is Nothing Then
                        node = node.Next
                        Continue While
                    End If

                    words.Add(node.Surface)
                    
                    node = node.Next
                End While
            Next
        End Using
        
        return words
    End Function

End Class

このあたりを試行錯誤してる時も常に頭の片隅にありました。

素直に漢字を勉強しろよ

漢字のフィルタリング

次に漢字を含む単語をフィルタリングします。

ヤフーでググるAscWUnicodeスカラ値を取り出しIfで判断するというとっても原始的素朴でステキな方法が紹介されていました。*1

.NETの正規表現は名前付きブロックをサポートしているのでありがたくそれを使いましょう。

正規表現での文字クラス

特に下調べしてないんで確証は無いんですがまぁCJK統合漢字を含んでれば大丈夫でしょうという感じで行きました。 好みで拡張Aもあわせてどうぞ。

Imports System.Text.RegularExpressions

Public Class HanFilter

    Private Sub New()
        ' Nothing to do.
    End Sub

    Public Shared Function HasHan(word As String) As Boolean
        Return Regex.IsMatch(word, ".*[\p{IsCJKUnifiedIdeographs}]+.*")
    End Function

End Class

集計

あとはLINQで一発なのでもういいや。

Module Program

    Sub Main(args as String())
        If args.Length <> 1 Then
            Console.WriteLine("AppName <path of directory that contains the Excel file>")
            Environment.Exit(0)
        End If

        Dim contents = ExcelContentExtractor.ExtractContent(args(0))
        Dim words = SentenceSplitter.Split(contents)

        Dim result = words.
            Where(AddressOf HanFilter.HasHan).
            GroupBy(Function(it) it, Function(it) 1).
            Select(Function(it) New With {.Count = it.Sum(), .Word = it.Key}).
            OrderByDescending(Function(it) it.Count)

        for each it in result
            Console.WriteLine("「{0}」: {1}", it.Word, it.Count)
        Next
    End Sub

End Module
「漢字」: 12
「文字」: 5
「素」: 4
「形態」: 4
「解析」: 4
「打ち合わせ」: 4
「似」: 3
「議事」: 3
「録」: 3
「列」: 3
「取り出し」: 3
「弊社」: 2
「当たり前」: 2
「書く」: 2
「時」: 2
「思っ」: 2
「引っこ抜け」: 2
「抜き出し」: 2
「集計」: 2
「素直」: 2
「勉強」: 2
「特に」: 2
「感じ」: 2
「正規」: 2
「表現」: 2
「方眼」: 1
「紙」: 1
「抽出」: 1
「好き」: 1
「変数」: 1
「個」: 1
「越え」: 1
「普通」: 1
「皆さん」: 1
「得意」: 1
「苦手」: 1
「頼り」: 1
「切っ」: 1
「普段」: 1
「文章」: 1
「別に」: 1
「困り」: 1
「書記」: 1
「書け」: 1
「困る」: 1
「出」: 1
「適用」: 1
「表」: 1
「計算」: 1
「設計」: 1
「作製」: 1
「行い」: 1
「各」: 1
「要素」: 1
「使わ」: 1
「傾向」: 1
「掴める」: 1
「寸法」: 1
「時点」: 1
「自分」: 1
「抜か」: 1
「使っ」: 1
「今回」: 1
「見た目」: 1
「興味」: 1
「直下」: 1
「列挙」: 1
「取り出す」: 1
「移植」: 1
「版」: 1
「使用」: 1
「使い方」: 1
「完全」: 1
「解説」: 1
「出来」: 1
「試行」: 1
「錯誤」: 1
「常に」: 1
「頭」: 1
「片隅」: 1
「次に」: 1
「含む」: 1
「単語」: 1
「値」: 1
「判断」: 1
「原始」: 1
「的」: 1
「素朴」: 1
「方法」: 1
「紹介」: 1
「名前」: 1
「付き」: 1
「使い」: 1
「下調べ」: 1
「確証」: 1
「無い」: 1
「統合」: 1
「含ん」: 1
「大丈夫」: 1
「行き」: 1
「好み」: 1
「拡張」: 1
「一」: 1
「発」: 1

おわりに

github.com

素直に漢字を勉強しろよ

*1:しょうもないバグを作りこみそうだし、テストも面倒そう