言語処理100本ノック 2020 (Rev2) No.10~19

言語処理100本ノック

言語処理100本ノックの2020年版があったのでやっていく。

https://nlp100.github.io/ja/

準備

お手軽にGoogle Colab( https://colab.research.google.com/?hl=ja )でやっていく。まずは必要なデータをダウンロードする。

!wget https://nlp100.github.io/data/popular-names.txt
!head popular-names.txt

# 出力
Mary	F	7065	1880
Anna	F	2604	1880
Emma	F	2003	1880
Elizabeth	F	1939	1880
Minnie	F	1746	1880
Margaret	F	1578	1880
Ida	F	1472	1880
Alice	F	1414	1880
Bertha	F	1320	1880
Sarah	F	1288	1880

10. 行数のカウント

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

with open("popular-names.txt") as f:
    lines = f.readlines()
    print(len(lines))

!echo ---
!cat popular-names.txt | wc -l

# 出力
2780
---
2780

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

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

with open("popular-names.txt") as fin, open('output11.txt', 'w') as fout:
    while True:
        line = fin.readline()
        if line == "":
            break
        l = line.replace("\t", " ")
        fout.write(l)

!head output11.txt
!echo ---

# sed
!cat popular-names.txt | sed 's/\t/ /g' | head
!echo ---
# tr
!cat popular-names.txt | tr '\t' ' ' | head
!echo ---
# expand
!cat popular-names.txt | expand -t 1 | head

# 出力
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
Elizabeth F 1939 1880
Minnie F 1746 1880
Margaret F 1578 1880
Ida F 1472 1880
Alice F 1414 1880
Bertha F 1320 1880
Sarah F 1288 1880
---
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
Elizabeth F 1939 1880
Minnie F 1746 1880
Margaret F 1578 1880
Ida F 1472 1880
Alice F 1414 1880
Bertha F 1320 1880
Sarah F 1288 1880
---
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
Elizabeth F 1939 1880
Minnie F 1746 1880
Margaret F 1578 1880
Ida F 1472 1880
Alice F 1414 1880
Bertha F 1320 1880
Sarah F 1288 1880
---
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
Elizabeth F 1939 1880
Minnie F 1746 1880
Margaret F 1578 1880
Ida F 1472 1880
Alice F 1414 1880
Bertha F 1320 1880
Sarah F 1288 1880

データの前処理ぐらいはコマンドでやってもいいかもと思えるぐらい、コマンド便利

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

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

with open("popular-names.txt") as fin, open("col1.txt", "w") as fout1, open("col2.txt", "w") as fout2:
    while True:
        line = fin.readline()
        if line == "":
            break
        tmp = line.split("\t")
        fout1.write(tmp[0]+"\n")
        fout2.write(tmp[1]+"\n")

!head col1.txt
!head col2.txt

!echo ---
!cat output-expand.txt | cut -f 1 -d " " | head
!cat output-expand.txt | cut -f 2 -d " " | head

# 出力
Mary
Anna
Emma
Elizabeth
Minnie
Margaret
Ida
Alice
Bertha
Sarah
F
F
F
F
F
F
F
F
F
F
---
Mary
Anna
Emma
Elizabeth
Minnie
Margaret
Ida
Alice
Bertha
Sarah
F
F
F
F
F
F
F
F
F
F

with句でファイルを複数開くことができる

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

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

with open("col1.txt") as fin1, open("col2.txt") as fin2, open("output12.txt", "w") as fout:
    while True:
        line1 = fin1.readline().strip()
        line2 = fin2.readline()
        if line1 == "" or line2 == "":
            break
        fout.write(line1 + "\t" + line2)

!cat output12.txt | head
!echo ---
!paste col1.txt col2.txt | head

# 出力
Mary	F
Anna	F
Emma	F
Elizabeth	F
Minnie	F
Margaret	F
Ida	F
Alice	F
Bertha	F
Sarah	F
---
Mary	F
Anna	F
Emma	F
Elizabeth	F
Minnie	F
Margaret	F
Ida	F
Alice	F
Bertha	F
Sarah	F

14. 先頭からN行を出力

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

N = 10
with open("popular-names.txt") as f:
    n = 0
    while True:
        line = f.readline().strip()
        if n >= N or line == "":
            break
        print(line)
        n += 1

!echo ---
!n=10; head -n $n popular-names.txt

# 出力
Mary	F	7065	1880
Anna	F	2604	1880
Emma	F	2003	1880
Elizabeth	F	1939	1880
Minnie	F	1746	1880
Margaret	F	1578	1880
Ida	F	1472	1880
Alice	F	1414	1880
Bertha	F	1320	1880
Sarah	F	1288	1880
---
Mary	F	7065	1880
Anna	F	2604	1880
Emma	F	2003	1880
Elizabeth	F	1939	1880
Minnie	F	1746	1880
Margaret	F	1578	1880
Ida	F	1472	1880
Alice	F	1414	1880
Bertha	F	1320	1880
Sarah	F	1288	1880

15. 末尾のN行を出力

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

N = 10
with open("popular-names.txt") as f:
    lines = f.readlines()
    total = len(lines)
    n = 0
    while True:
        if n >= N or total - 1 - n < 0:
            break
        line = lines[total - 1 - n].strip()
        print(line)
        n += 1

!echo ---
!n=10; tail -n $n popular-names.txt

# 出力
Logan	M	12352	2018
Mason	M	12435	2018
Lucas	M	12585	2018
Elijah	M	12886	2018
Benjamin	M	13381	2018
Oliver	M	13389	2018
James	M	13525	2018
William	M	14516	2018
Noah	M	18267	2018
Liam	M	19837	2018
---
Liam	M	19837	2018
Noah	M	18267	2018
William	M	14516	2018
James	M	13525	2018
Oliver	M	13389	2018
Benjamin	M	13381	2018
Elijah	M	12886	2018
Lucas	M	12585	2018
Mason	M	12435	2018
Logan	M	12352	2018

全部読み込んで後ろから表示すれば良いが、効率を考えると後ろからファイルを読みたくなる。次のリンクに実装例があるので、必要になったときは参考にしたい。

Pythonでcsvファイルの最終行を読む方法いろいろ - Qiita
Linuxではファイルの後ろからn行取得することのできるtailというコマンドがある. 結構便利なのでPythonでも同じことができるようにしたい.tail(file_name, n)でファイルの…

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

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

N = 3
with open("popular-names.txt") as f:
    lines = f.readlines()

total = len(lines)
a = -(-total // N) # 切り上げ
n = 0
f = open("output16-0.txt", "w")
fcnt = 0
for line in lines:
    if n >= a:
        n = 0
        f.close()
        fcnt += 1
        f = open("output16-" + str(fcnt) + ".txt", "w")
    f.write(line)
    n += 1
f.close()

!N=3; cnt=`cat popular-names.txt | wc -l`; a=$((cnt/N+1)); split -l $a popular-names.txt output16-chk
!ls -l

# 出力
-rw-r--r-- 1 root root 17739 Feb 26 11:13 output16-0.txt
-rw-r--r-- 1 root root 18428 Feb 26 11:13 output16-1.txt
-rw-r--r-- 1 root root 18859 Feb 26 11:13 output16-2.txt
-rw-r--r-- 1 root root 17739 Feb 26 11:13 output16-chkaa
-rw-r--r-- 1 root root 18428 Feb 26 11:13 output16-chkab
-rw-r--r-- 1 root root 18859 Feb 26 11:13 output16-chkac

切り上げを実装する際にmath.ceilを利用すると思うが、整数除算を利用することでも行ける。ついでに計算効率がいいらしい。

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

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

with open("popular-names.txt") as f:
    lines = f.readlines()
    m = {}
    for line in lines:
        tmp = line.split("\t")
        m[tmp[0]] = 1
    for key in sorted(m.keys()):
        print(key)

!echo ---
!cat popular-names.txt | cut -f 1 | sort | uniq

# 出力
Abigail
Aiden
Alexander
Alexis
Alice
Amanda
Amelia
Amy
~~省略~~
---
Abigail
Aiden
Alexander
Alexis
Alice
Amanda
Amelia
Amy

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

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

with open("popular-names.txt") as f:
    lines = f.readlines()

sortsecond = lambda val: int(val[0])
m = []
for line in lines:
    v = line.split("\t")[2]
    m.append([v, line.strip()])

m.sort(key=sortsecond, reverse=True)
for e in m:
    print(e[1])

!echo ---
!cat popular-names.txt | sort -n -r -t $'\t' -k 3 | head 

# 出力
Linda	F	99689	1947
Linda	F	96211	1948
James	M	94757	1947
Michael	M	92704	1957
Robert	M	91640	1947
Linda	F	91016	1949
Michael	M	90656	1956
Michael	M	90517	1958
James	M	88584	1948
Michael	M	88528	1954
~~省略~~
---
Linda	F	99689	1947
Linda	F	96211	1948
James	M	94757	1947
Michael	M	92704	1957
Robert	M	91640	1947
Linda	F	91016	1949
Michael	M	90656	1956
Michael	M	90517	1958
James	M	88584	1948
Michael	M	88528	1954

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

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

with open("popular-names.txt") as f:
    lines = f.readlines()

sortsecond = lambda val: int(val[0])
m = {}
for line in lines:
    n = line.split("\t")[0]
    if n not in m:
        m[n] = 0
    m[n] += 1

sortedList = sorted(m.items(), key=lambda x:x[1], reverse=True)

for e in sortedList:
    print(e)

!echo ---
!cat popular-names.txt | cut -f 1 | sort | uniq -c | sort -n -r

# 出力
('James', 118)
('William', 111)
('John', 108)
('Robert', 108)
('Mary', 92)
('Charles', 75)
('Michael', 74)
('Elizabeth', 73)
('Joseph', 70)
('Margaret', 60)
('George', 58)
('Thomas', 58)
('David', 57)
('Richard', 51)
('Helen', 45)
('Frank', 43)
('Christopher', 43)
('Anna', 41)
('Edward', 40)
('Ruth', 39)
('Patricia', 38)
('Matthew', 37)
('Dorothy', 36)
('Emma', 35)
('Barbara', 32)
('Daniel', 31)
('Joshua', 31)
('Sarah', 26)
('Linda', 26)
('Jennifer', 26)
('Emily', 26)
('Jessica', 25)
('Jacob', 25)
('Mildred', 24)
('Betty', 24)
('Susan', 24)
('Henry', 23)
('Ashley', 23)
('Nancy', 22)
('Andrew', 21)
('Florence', 20)
('Marie', 20)
('Donald', 20)
('Amanda', 20)
('Samantha', 19)
('Karen', 18)
('Lisa', 18)
('Melissa', 18)
('Madison', 18)
('Olivia', 18)
('Stephanie', 17)
('Abigail', 17)
('Ethel', 16)
('Sandra', 16)
('Mark', 16)
('Frances', 15)
('Carol', 15)
('Angela', 15)
('Michelle', 15)
('Heather', 15)
('Ethan', 15)
('Isabella', 15)
('Shirley', 14)
('Kimberly', 14)
('Amy', 14)
('Ava', 14)
('Virginia', 13)
('Deborah', 13)
('Brian', 13)
('Jason', 13)
('Nicole', 13)
('Hannah', 13)
('Sophia', 13)
('Minnie', 12)
('Bertha', 12)
('Donna', 12)
('Cynthia', 11)
('Alice', 10)
('Doris', 10)
('Ronald', 10)
('Brittany', 10)
('Nicholas', 10)
('Mia', 10)
('Noah', 10)
('Joan', 9)
('Debra', 9)
('Tyler', 9)
('Ida', 8)
('Clara', 8)
('Judith', 8)
('Taylor', 8)
('Alexis', 8)
('Alexander', 8)
('Mason', 8)
('Harry', 7)
('Sharon', 7)
('Steven', 7)
('Tammy', 7)
('Brandon', 7)
('Liam', 7)
('Anthony', 6)
('Annie', 5)
('Gary', 5)
('Jeffrey', 5)
('Jayden', 5)
('Charlotte', 5)
('Lillian', 4)
('Kathleen', 4)
('Justin', 4)
('Austin', 4)
('Chloe', 4)
('Benjamin', 4)
('Evelyn', 3)
('Megan', 3)
('Aiden', 3)
('Harper', 3)
('Elijah', 3)
('Bessie', 2)
('Larry', 2)
('Rebecca', 2)
('Lauren', 2)
('Amelia', 2)
('Logan', 2)
('Oliver', 2)
('Walter', 1)
('Carolyn', 1)
('Pamela', 1)
('Lori', 1)
('Laura', 1)
('Tracy', 1)
('Julie', 1)
('Scott', 1)
('Kelly', 1)
('Crystal', 1)
('Rachel', 1)
('Lucas', 1)
---
    118 James
    111 William
    108 Robert
    108 John
     92 Mary
     75 Charles
     74 Michael
     73 Elizabeth
     70 Joseph
     60 Margaret
     58 Thomas
     58 George
     57 David
     51 Richard
     45 Helen
     43 Frank
     43 Christopher
     41 Anna
     40 Edward
     39 Ruth
     38 Patricia
     37 Matthew
     36 Dorothy
     35 Emma
     32 Barbara
     31 Joshua
     31 Daniel
     26 Sarah
     26 Linda
     26 Jennifer
     26 Emily
     25 Jessica
     25 Jacob
     24 Susan
     24 Mildred
     24 Betty
     23 Henry
     23 Ashley
     22 Nancy
     21 Andrew
     20 Marie
     20 Florence
     20 Donald
     20 Amanda
     19 Samantha
     18 Olivia
     18 Melissa
     18 Madison
     18 Lisa
     18 Karen
     17 Stephanie
     17 Abigail
     16 Sandra
     16 Mark
     16 Ethel
     15 Michelle
     15 Isabella
     15 Heather
     15 Frances
     15 Ethan
     15 Carol
     15 Angela
     14 Shirley
     14 Kimberly
     14 Ava
     14 Amy
     13 Virginia
     13 Sophia
     13 Nicole
     13 Jason
     13 Hannah
     13 Deborah
     13 Brian
     12 Minnie
     12 Donna
     12 Bertha
     11 Cynthia
     10 Ronald
     10 Noah
     10 Nicholas
     10 Mia
     10 Doris
     10 Brittany
     10 Alice
      9 Tyler
      9 Joan
      9 Debra
      8 Taylor
      8 Mason
      8 Judith
      8 Ida
      8 Clara
      8 Alexis
      8 Alexander
      7 Tammy
      7 Steven
      7 Sharon
      7 Liam
      7 Harry
      7 Brandon
      6 Anthony
      5 Jeffrey
      5 Jayden
      5 Gary
      5 Charlotte
      5 Annie
      4 Lillian
      4 Kathleen
      4 Justin
      4 Chloe
      4 Benjamin
      4 Austin
      3 Megan
      3 Harper
      3 Evelyn
      3 Elijah
      3 Aiden
      2 Rebecca
      2 Oliver
      2 Logan
      2 Lauren
      2 Larry
      2 Bessie
      2 Amelia
      1 Walter
      1 Tracy
      1 Scott
      1 Rachel
      1 Pamela
      1 Lucas
      1 Lori
      1 Laura
      1 Kelly
      1 Julie
      1 Crystal
      1 Carolyn

まとめ

UnixコマンドをPythonで実装してみるという課題でした。Pythonの実習だと思うが、Unixコマンドが意外と短い記述でファイル処理できることを発見してしまった。コマンドの方が計算効率もいいと思うので、使い分けできるようになりたい。

コメント