Word文書を解析して,英単語の出現回数を統計出力するバッチ (英文の用語索引を自動生成)
文書の「単語索引」を,自動的に生成するプログラム。
- Word文書の文章中に出現する,全ての英単語を抽出する。
- 各英単語の出現回数をカウントし,ランキングを作成する。
- また,各単語の出現するページ番号などを一覧表で出力する。
処理内容は,簡易なもの。
英単語の変化形などは,最低限のマージしか施していない。
(大文字・小文字は区別しないようにしてある。)
統計処理された情報を手っ取り早く得るためには役立つ。
WSH/JScriptで実装してあるので,実行も処理追加も簡単。
まず,実行結果のサンプルを掲載する。
次に,そのような処理を実行するためのバッチのソースコードを掲載する。
実行結果のサンプル
解析に使うサンプルページ:
Linuxの「grep」コマンドのマニュアル(英語)
http://linux.die.net/man/1/grep
このページの内容をWordファイルにコピペして保存。
コマンドプロンプトから,次のコマンドを実行。
cscript //nologo stat_tokens.js > out.txt
解析結果が,タブ区切りのCSVファイルとして保存される。
これをExcelに張り付けて,オートフィルタで並び替え。
1行目には適当に「単語」「出現回数」など書いておく。
出現頻度の降順に並び変えると,こんな感じ。
単語 出現回数 出現箇所個数 出現箇所 the 256 16 p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16 line 88 13 p1, p2, p3, p4, p5, p6, p7, p9, p11, p12, p13, p14, p15 and 82 16 p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16 match 75 13 p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13 file 69 9 p1, p2, p3, p4, p5, p6, p7, p13, p15 for 54 12 p1, p3, p4, p5, p8, p9, p11, p12, p13, p14, p15, p16 grep 52 13 p1, p2, p3, p4, p5, p6, p7, p8, p10, p11, p14, p15, p16 option 51 10 p1, p3, p4, p5, p6, p7, p11, p12, p13, p15 this 43 12 p1, p2, p3, p4, p5, p6, p7, p11, p12, p13, p14, p15 that 41 14 p1, p2, p3, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15 character 40 10 p2, p5, p6, p7, p8, p9, p10, p11, p14, p15 expression 39 8 p2, p7, p8, p9, p10, p11, p14, p16 default 37 11 p1, p2, p4, p6, p7, p8, p11, p12, p13, p14, p15 are 36 13 p1, p2, p3, p6, p7, p8, p9, p11, p12, p13, p14, p15, p16 regular 28 8 p2, p3, p7, p8, p9, p10, p11, p16 not 25 10 p3, p4, p6, p8, p9, p10, p11, p14, p15, p16 specified 25 7 p2, p3, p4, p11, p12, p13, p14 when 25 7 p3, p4, p11, p12, p13, p14, p15 output 24 7 p1, p3, p4, p5, p6, p7, p12 with 24 10 p2, p3, p4, p5, p7, p8, p11, p12, p14, p15 color 23 4 p3, p11, p12, p14 name 22 9 p1, p3, p4, p5, p6, p7, p9, p13, p15 matches 21 8 p2, p3, p5, p6, p7, p8, p9, p10 input 20 6 p1, p2, p3, p4, p6, p7 any 19 9 p2, p3, p4, p6, p8, p10, p11, p12, p13 pattern 19 5 p1, p2, p6, p7, p10 posix 19 6 p2, p3, p4, p11, p15, p16 text 19 7 p5, p6, p7, p11, p12, p13, p14 only 17 8 p2, p3, p4, p5, p7, p11, p13, p15 print 17 4 p1, p3, p4, p5 binary 16 3 p6, p7, p11 command 16 9 p1, p5, p6, p7, p11, p12, p13, p15, p16 locale 16 4 p8, p9, p11, p14 used 16 9 p2, p3, p5, p7, p11, p12, p13, p14, p15 context 15 5 p3, p5, p11, p12, p13 non 15 6 p2, p3, p4, p11, p12, p13 which 15 7 p2, p3, p6, p10, p11, p14, p15 ...
回数が多い物は,英語の基礎単語に加えて,
この文書のメインテーマであるとみなせる。
タグクラウドみたいなものを生成するアルゴリズムだと思えばよい。
逆に,回数が少ない物に注目すれば,マイナーで難易度の高い英単語だけを重点的に暗記する助けになるかもしれない。
アルファベットの昇順に並び変えると,こんな感じ。
単語 出現回数 出現箇所個数 出現箇所 abbccdd 1 1 p8 abcd 2 1 p8 about 1 1 p4 access 1 1 p16 action 9 1 p6 active 2 1 p13 actual 2 1 p5 actually 1 1 p4 addition 3 3 p1, p9, p16 additional 1 1 p8 address 1 1 p1 adjacent 1 1 p13 advisable 1 1 p15 affect 2 2 p11, p14 after 7 2 p3, p5 afterwards 1 1 p8 against 1 1 p15 alignment 1 1 p5 all 6 5 p1, p5, p6, p7, p8 allow 2 2 p1, p11 also 9 4 p3, p4, p5, p15 alternate 1 1 p10 alternation 2 1 p10 always 1 1 p3 american 1 1 p14 analogously 1 1 p7 anchoring 1 1 p9 and 82 16 p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16 any 19 9 p2, p3, p4, p6, p8, p10, p11, p12, p13 anything 1 1 p4 anywhere 1 1 p9 appears 1 1 p15 applications 1 1 p1 applies 3 2 p8, p12 apply 1 1 p14 arbitrary 2 2 p5, p7 are 36 13 p1, p2, p3, p6, p7, p8, p9, p11, p12, p13, p14, p15, p16 arithmetic 1 1 p7 ascii 3 3 p5, p7, p9 assembling 1 1 p14 assume 2 1 p6 assuming 1 1 p11 attempts 1 1 p11 attributes 2 2 p12, p14 auto 1 1 p3 available 4 3 p1, p8, p15 avoid 3 3 p4, p10, p11 awk 1 1 p16 back 3 2 p10, p16 background 13 4 p11, p12, p13, p14 backslash 6 5 p6, p8, p9, p10, p11 ...
これで,どの言葉が何ページ目に出てくるのか,一目瞭然である。
※applyとappliesみたいな変化形が統一されてないが,そういう事情は周りをサッと見ればわかるし,もし必要ならプログラムに修正を加えればよい。
ソースコード
上記のような処理を可能にするバッチのソースコード:
stat_tokens.js
/* Word文書を解析して,英単語の出現回数を統計出力するバッチ */ var cwd = WScript.CreateObject("WScript.Shell").CurrentDirectory; var doc_file_path = cwd + "\\text.docx"; // ファイル名 var wdActiveEndPageNumber = 3; function log(s){ WScript.Echo( s ); } Array.prototype.includes = function( key ){ for( var i = 0; i < this.length; i ++ ) { if( this[ i ] == key ) return true; } return false; }; // ------------- 記録オブジェクト ------------- var TokenRecords = function(){ this._token_list = []; this._dic = {}; }; TokenRecords.prototype = { // 全トークンを単語として一次元で保持 _token_list : null, // 全トークンの詳細情報をハッシュで保持 _dic : null, add : function( token, page_num ) { // 出現個所情報 var str_spot = "p" + page_num; // 登録 if( ! this.hasToken( token ) ) { // このトークンを初回登録 this._dic[ token ] = ({ spots : [ str_spot ], cnt : 1 }); this._token_list.push( token ); //log("[DEBUG]token = " + token + "を新規登録。"); } else { // トークンの存在箇所情報を更新 if( ! this.tokenAlreadyInSameSpot( token, str_spot ) ) { //log( "[DEBUG]token = " + token + ", str_spot = " + str_spot ); this._dic[ token ][ "spots" ].push( str_spot ); } // トークンの出現回数情報を更新 this._dic[ token ][ "cnt" ] ++; //log("[DEBUG]token = " + token + "を追加登録。"); } //log("[DEBUG]this._token_list.length = " + this._token_list.length); } , // 特定の箇所にトークンが既に存在するか tokenAlreadyInSameSpot : function( token, str_spot ) { var arr = this._dic[ token ][ "spots" ]; if( ! arr ) { return false; } if( arr.includes( str_spot ) ) { return true; } return false; } , // トークンの存在判定 hasToken : function( token ) { return !! ( this._dic[ token ] ); } , // 保持する全トークンを調整 adjust : function() { var token = ""; var spots = ""; var cnt = 0; for( var i = this._token_list.length - 1; i > -1; i -- ) { // 元 token = this._token_list[ i ]; // 変化形をマージ if( ( token.match( /^(.+)ed$/ ) || token.match( /^(.+)ing$/ ) || token.match( /^(.+)s$/ ) ) && this.hasToken( RegExp.$1 ) ) { // 登録済みの原形の方を優先 var dest_token = RegExp.$1; this.merge_tokens( token, dest_token, i ); } /* NOTE: この処理順だとApplesとappleが分離してしまうので, とりあえず最初から全部小文字で登録 else // 大文字・小文字をマージ if( ( token != token.toLowerCase() ) && this.hasToken( token.toLowerCase() ) ) { // オール小文字のトークンを優先して登録する var dest_token = token.toLowerCase(); this.merge_tokens( token, dest_token, i ); } */ } } , // 2つのトークン保持情報をマージする merge_tokens : function( token, dest_token, i ) { var spots = this._dic[ token ][ "spots" ]; var cnt = this._dic[ token ][ "cnt" ] // 新情報を書き換え for( var j = 0; j < spots.length; j ++ ) { if( ! this._dic[ dest_token ][ "spots" ].includes( spots[ j ] ) ) { this._dic[ dest_token ][ "spots" ].push( spots[ j ] ); } } this._dic[ dest_token ][ "spots" ].sort(function(sp1, sp2){ sp1.match(/^p([0-9]+)$/); var page1 = parseInt( RegExp.$1, 10 ); sp2.match(/^p([0-9]+)$/); var page2 = parseInt( RegExp.$1, 10 ); if( page1 < page2 ) { return -1; } else { return 1; } // http://crocro.com/write/manga_javascript/wiki.cgi?p=%C7%DB%CE%F3%A4%CE%A5%BD%A1%BC%A5%C8%A4%C8%CC%B5%CC%BE%B4%D8%BF%F4 }); this._dic[ dest_token ][ "cnt" ] += cnt; // 元情報を削除 this._dic[ token ] = null; this._token_list.splice(i, 1); // http://javascript-memo.seesaa.net/article/24832361.html } , // 出力 dump : function() { //log("[DEBUG]ダンプします。"); //log("[DEBUG]this._token_list.length = " + this._token_list.length); var token = ""; var str_spot = ""; var cnt = 0; // 全トークン for( var i = 0; i < this._token_list.length; i ++ ) { token = this._token_list[ i ]; str_spot = this._dic[ token ][ "spots" ].join(", "); cnt = this._dic[ token ][ "cnt" ] //log( "・" + token + " (" + cnt + ") " + str_spot ); // タブ区切りのCSVとして log( "" + token + "\t" + cnt + "\t" + this._dic[ token ][ "spots" ].length + "\t" + str_spot ); // NOTE:出現箇所の表示上個数も出力する。 // この情報はマージ済みの個数なので実際の出現回数ではないが,Excel上でソートするために必要。 } } }; var recorder = new TokenRecords(); // ------------- メイン処理 ------------- // Wordを起動する var word = WScript.CreateObject("Word.Application"); word.Visible = true; // NOTE: visibleにしておかないと,途中でエラー発生時に // ファイルが見えないまま開きっぱなしになり,閉じることができず後片付けが大変 // 指定したWordファイルを開く var doc = word.Documents.Open( doc_file_path ); // 全ての段落についてループ var num_paras = doc.Paragraphs.Count; for( var i = 1; i <= num_paras; i ++ ) { // この段落を取得 var para = doc.Paragraphs( i ); // この段落内の文字列を取得 var txt_in_para = para.Range.Text; // この段落に関する情報を表示 //log( "[para " + i + "] '" + txt_in_para + "'"); // この段落の終了するページ番号を取得 var page_num = para.Range.Information(wdActiveEndPageNumber); // http://www.vbalab.net/vbaqa/c-board.cgi?cmd=ntr;tree=475;id=word // この段落を解析,記録 analyzeParaText( txt_in_para, page_num ); } // http://language-and-engineering.hatenablog.jp/entry/20101105/p1 // 全記録内容の調整 recorder.adjust(); // 全記録を出力 recorder.dump(); // ファイルを閉じる doc.Close(); // ワードを終了する word.Quit(); // 1パラを解析する function analyzeParaText( txt, page_num ) { // 事前に加工 var txt_arr = txt // 余計な文字を置換 .replace( /[\.,!\?\(\)\[\]‘''“”’\-]/g, " " ) // NOTE: シンタックスハイライトが乱れないように'を2回入れている // 半角スペースで分解 .split(" ") ; // 全トークンを処理 var token = ""; for( var i = 0; i < txt_arr.length; i ++ ) { token = txt_arr[ i ]; // 有効なトークンか if( // アルファベットだけ ( token.match( /^[A-Za-z]+$/ ) ) && // 2文字以下は無視 ( token.length > 2 ) ) { // 全部小文字にして登録 recorder.add( token.toLowerCase(), page_num ); } } }
突貫工事で作ったため,少々粗いソースだがご勘弁を。
発展
こういうツールがあれば,下記みたいなまとめ情報を比較的容易に作成できる。
原文をスラスラ読みたい!「MSDNライブラリによく出る英単語 100選」
http://codezine.jp/article/detail/4951
- 単語を原形・単数形に統一
- 頻度順にソート
- 中学校で習う簡単な単語を除外
- 開発者であれば当然知っているであろう単語を除外