LoginSignup
8
5

言語処理100本ノックで MATLAB 入門!第2章: UNIX コマンド 10-19

Last updated at Posted at 2020-05-11

はじめに

もしかして 「え?MATLAB で言語処理やるの??」と思いました・・?(2回目)

先日 言語処理 100 本ノック 2020 が公開されましたが、これは言語処理という切り口で MATLAB の練習をするすごく良い題材じゃないか・・ということで第 1 章: 準備運動 に引き続き第 2 章の MATLAB コード例を公開します。

ノックを一緒にやってくれる MATLAB 芸人は引き続き募集中です!GitHub: NLP100-MATLAB

他章へのリンク

UNIX コマンドの章ではありながら・・

第 2 章は本来 UNIX コマンドの課題ではありますが、すでによい解説付きの投稿が複数見られるので、MATLAB での処理に注力しています。

Windows 上で環境構築するのが大変そうだからではないです。

環境

MATLAB R2020a (Windows 10)

この記事の Livescript 版(MATLAB)は GitHub: NLP100-MATLAB1 に置いてあります。同じ課題を実現する方法は沢山ある思います。もしもっといい(効率がいい、面白い)方法があれば是非コメントください。

第2章: UNIX コマンド

popular-names.txtは,アメリカで生まれた赤ちゃんの「名前」「性別」「人数」「年」をタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,popular-names.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

以下では popular-names.txt は本スクリプトと同じフォルダに保存されているとします。

10. 行数のカウント

行数をカウントせよ.確認にはwcコマンドを用いよ.

readtable 関数で読み込むのが手っ取り早い。念のためヘッダー情報は変数名に使用しない設定にしておく。

Code
tabletxt = readtable('popular-names.txt','ReadVariableNames',false);
height(tabletxt)
Output
ans = 2780

2780 行ですね。もう少し low-level な関数だと fileread 関数でも行ける?全部読み込んで、splitlines 関数で改行箇所で分割します。

Code
tmp = fileread('popular-names.txt');
txt = splitlines(tmp);
length(txt)
Output
ans = 2781

一行多い・・これは最後に空の文字列が入ってしまうため。

Code
txt(end)
Output
ans = 
    {0x0 char}

Unix コマンドを使えるようにする設定準備するのがあれだったので、Windows OSのコマンドプロンプト上でできるもので代用すると・・

Code
!find /v /c "" < popular-names.txt
Output
2780 

11. タブをスペースに置換

タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.

string 型にして replace 関数を使うのが簡単そう。

Code
tmp = fileread('popular-names.txt');
txt = string(splitlines(tmp));
newtxt = replace(txt,"\t"," ");
newtxt(1:2)
Output
ans = 2x1 string    
"Mary→F→7065→1880"    
"Anna→F→2604→1880"    

あれ、タブ?が残っている・・・。なんだこの矢印は。文字コード確認してみます。

まず char 型にして5文字目。

Code
tmp = char(txt(1));
char5 = tmp(5)
Output
char5 = '	'
Code
double(char5)
Output
ans = 9

文字コード 9 はタブですね。char(9) string 型にして使ってみます。

Code
newtxt = replace(txt,string(char(9))," ");	
newtxt(1:2)
Output
ans = 2x1 string    
"Mary F 7065 1880"    
"Anna F 2604 1880"    

今度はうまくいきました。タブを他からコピペしてもうまくいきますが、MATLAB 上でタブキーを押すとスペース4つ分(自分の環境では)がタイプされるのでうまくいきません・・。

Code
newtxt = replace(txt,"	"," ");
newtxt(1:2)
Output
ans = 2x1 string    
"Mary F 7065 1880"    
"Anna F 2604 1880"    

要調査項目。

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.

table 型で読み込めば(readtable)すぐですが、あえて fileread 関数でやります。

Code
tmp = fileread('popular-names.txt');
txt = string(splitlines(tmp));

最後に残る空文字列を決して、split 関数で分割する。

Code
txt(strlength(txt)==0) = [];
newtxt = split(txt);
newtxt(1:2,:)
Output
ans = 2x4 string    
"Mary"       "F"          "7065"       "1880"       
"Anna"       "F"          "2604"       "1880"       

各文字が要素として入った 2780 x 4 の string 配列となります。

1 列目を col1.txt に、2 列目を col2.txt に保存します。

Code
writematrix(newtxt(:,1), 'col1.txt');
writematrix(newtxt(:,2), 'col2.txt');

13. col1.txtとcol2.txtをマージ

12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ.

readcell 関数で読み込んで、string 型に変換。

Code
col1 = string(readcell('col1.txt'));
col2 = string(readcell('col2.txt'));

結合は join 関数で。デフォルトでスペース1つ空けて結合されるので、ここはタブを指定します。"\t" が使えないのは要調査。

Code
col12 = join([col1,col2],char(9));
writematrix(col12, 'col12.txt');

14. 先頭からN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.

Code
prompt = 'What is your N value? ';
N = input(prompt);
if isnumeric(N) && N == round(N) && N > 0
    tmp = fileread('popular-names.txt');
    txt = string(splitlines(tmp));
    txt(1:N)
else
    disp('N must be a natural number.')
end
Output
ans = 5x1 string    
"Mary→F→7065→1880"         
"Anna→F→2604→1880"         
"Emma→F→2003→1880"         
"Elizabeth→F→1939→1880"    
"Minnie→F→1746→1880"       

(出力はコマンド入力で 5 を渡した場合)

15. 末尾のN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.

Code
prompt = 'What is your N value? ';
N = input(prompt);
if isnumeric(N) && N == round(N) && N > 0
    tmp = fileread('popular-names.txt');
    txt = string(splitlines(tmp));
    txt(end-N-1:end-1)
else
    disp('N must be a natural number.')
end
Output
ans = 6x1 string    
"Oliver→M→13389→2018"      
"Benjamin→M→13381→2018"    
"Elijah→M→12886→2018"      
"Lucas→M→12585→2018"       
"Mason→M→12435→2018"       
"Logan→M→12352→2018"       

出力はコマンド入力で 5 を渡した場合。最後の空文字の取り扱いが苦しい・・。

16. ファイルをN分割する

自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.

N 分割・・。割り切れない分は最後で吸収します。

Code
prompt = 'What is your N value? ';
N = input(prompt);
if isnumeric(N) && N == round(N) && N > 0 && N < 2780
    tmp = fileread('popular-names.txt');
    txt = string(splitlines(tmp));
    
    Ndata = ceil(2780/N); % number of rows on each file (except for the last)
    
    idx = reshape(1:(Nfiles-1)*Ndata, Ndata, []);
    for ii=1:N-1
        writematrix(txt(idx(:,ii)), "data"+ii+".txt");
    end
    writematrix(txt(idx(end)+1:end), "data"+N+".txt");
    
else
    disp('N must be a natural number and smaller than 2780.')
end

上の idx は各ファイルに収まる行番号が入るようになっています。

  • 1列目:1つ目のファイルに収まる行数(N = 100 であれば 1 から 100)
  • 2列目:2つ目のファイルに収まる行数(N = 100 であれば 101 から 200)
  • 3列目:3つ目のファイルに収まる行数(N = 100 であれば 201 から 300)

といった具合。その行番号が入った配列で元データの中身を参照しながら各ファイルへ出力しています。

17. 1列目の文字列の異なり

1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはcut, sort, uniqコマンドを用いよ.

そろそろ MATLAB らしく処理していいよね・・。

Code
tabletxt = readtable('popular-names.txt','ReadVariableNames',false);

せっかくなので変数名を付けておきましょう。日本語文字が使えるのは R2019b から。

Code
tabletxt.Properties.VariableNames = ["名前","性別","人数","年"];
uniqueNames = unique(tabletxt(:,1));
head(uniqueNames)
名前
1 'Abigail'
2 'Aiden'
3 'Alexander'
4 'Alexis'
5 'Alice'
6 'Amanda'
7 'Amelia'
8 'Amy'

18. 各行を3コラム目の数値の降順にソート

各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).

3コラム目ということは「人数」順ですね。逆順ということは多い順(descent)かな?

Code
tabletxt = readtable('popular-names.txt','ReadVariableNames',false);
tabletxt.Properties.VariableNames = ["名前","性別","人数","年"];

sort 関数で並べ替えた順番を表すインデックス idx を使います。

Code
[~,idx] = sort(tabletxt{:,3},'descend');
tabletxt = tabletxt(idx,:);
head(tabletxt)
名前 性別 人数
1 'Linda' 'F' 99689 1947
2 'Linda' 'F' 96211 1948
3 'James' 'M' 94757 1947
4 'Michael' 'M' 92704 1957
5 'Robert' 'M' 91640 1947
6 'Linda' 'F' 91016 1949
7 'Michael' 'M' 90656 1956
8 'Michael' 'M' 90517 1958

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.

ここは素直に histcounts 関数を使う。ただ数値かカテゴリ型でないとだめな点に注意。

Code
tabletxt = readtable('popular-names.txt','ReadVariableNames',false);
tabletxt.Properties.VariableNames = ["名前","性別","人数","年"];

tabletxt.("名前") = categorical(tabletxt.("名前"));
[freq, names] = histcounts(tabletxt.("名前"));

表示のためだけですがテーブル型に整形して、さらに頻度順に整列させます。

Code
freqNames = table(names', freq', 'VariableNames', ["名前","出現頻度"]);
freqNames = sortrows(freqNames,'出現頻度','descend');
head(freqNames)
名前 出現頻度
1 'James' 118
2 'William' 111
3 'John' 108
4 'Robert' 108
5 'Mary' 92
6 'Charles' 75
7 'Michael' 74
8 'Elizabeth' 73
  1. Livescript から markdown への変換は livescript2markdown​: MATLAB's live scripts to markdown を使っています。

8
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5