1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

言語処理100本ノック俺の解答 第2章

Last updated at Posted at 2022-01-05

言語処理100本ノック第2章: UNIXコマンドの俺の解答。その他の章はこちら

10

省略

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

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('in_filename')
parser.add_argument('out_filename')
args = parser.parse_args()
f_i = open(args.in_filename)
f_o = open(args.out_filename, "w", newline='\n')

for s in f_i:
    s = s.rstrip().replace('\t', ' ')
    f_o.write(s+'\n')

print()ではなくわざわざファイルにwrite()で書き出しているのは理由がある。Windows上で実行しているので、標準出力に\nを書き出すと勝手に\r\nに書き換えられてしまう。一方でpopular-names.txtの改行コードは\nなので、sed等の結果との答え合わせ時に差分が出てしまう。それを防ぐために出力ファイルをnewline='\n'で開き、write()で書き出している。
Windows上で標準出力の改行コード書き換えを抑制する方法はこの辺に書いてあったが、かなり面倒でトリッキーそう。

12~14

省略

15. 末尾のN行を出力

言語処理などのデータ処理においては、データが巨大な場合を想定して、全データをメモリ上に保持するようなことは避けるべき。ということでFIFOのキューを使って実装。

import argparse
import sys
import queue

parser = argparse.ArgumentParser()
parser.add_argument('-n', help="number of lines")
parser.add_argument('-o', help="output filename")
args = parser.parse_args()

n_int = int(args.n)
q = queue.SimpleQueue()
for l in sys.stdin:
    q.put(l.rstrip())
    if q.qsize() > n_int:
        q.get()

f_o = open(args.o, "w", newline='\n')
for i in range(0, n_int):
    f_o.write(q.get()+'\n')

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

この問題は曖昧ですね。普通「行単位でN分割せよ」と言われれば、なるべく行数が均等になるように分割するという意味かなと思う。なので、そのつもりで解答した。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-i', help="input filename")
parser.add_argument('-n', help="number of files")
parser.add_argument('-o', help="output filename prefix")
args = parser.parse_args()

lines = 0
with open(args.i) as f_i:
    for l in f_i:
        lines += 1    

n_int = int(args.n)

i = 0
current_line = 0
f_i = open(args.i)
while i < n_int:
    remaining_lines = lines - current_line
    i_lines = remaining_lines // (n_int - i)
    if remaining_lines % (n_int - i) > 0:
        i_lines += 1
    with open("{}{:02d}".format(args.o, i), "w", newline='\n') as f_o:
        for j in range(0, i_lines):
            s = f_i.readline().rstrip()
            f_o.write(s + '\n')
    i += 1
    current_line += i_lines

さっきの問題のところで「全データをメモリ上に保持するようなことは避けるべき」と言った手前、それを頑なに守るために、まず全体の行数を数えるために1回、次にファイルを分割するために1回、計2回入力ファイルを開く羽目になっている。
さて、「同様の処理をsplitコマンドで実現せよ」とあるが、splitって、なるべく行数が均等になるように分割するという機能は無くないですか?普通にsplit -n l/Nってやると、ファイルサイズがなるべく均等になるように分割される。とりあえず、Nが2780の約数の場合に限定して、

$ split -l `expr 2780 / N` -d popular-names.txt popular-names-split/

なんてやってみましたけど…
ファイルサイズをなるべく均等にするプログラムを作ろうとすると、ちょっと面倒そう。

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

1列目を取り出すのにsplitして最初のを取り出すのがコード行数的には楽だけど、2列目以降も切るところが無駄なので正規表現で1列目だけ取り出す。律儀すぎるよな(笑)

import argparse
import sys
import re

parser = argparse.ArgumentParser()
parser.add_argument('-o', help="output filename")
args = parser.parse_args()

words = set()
regex = re.compile(r'^([^\t]+)\t')
for s in sys.stdin:
    words.add(regex.match(s).group(1))

f_o = open(args.o, "w", newline='\n')
for w in sorted(words):
    f_o.write(w+'\n')

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

ここはPandasに頼ることにした。

import argparse
import sys
import pandas as pd

parser = argparse.ArgumentParser()
parser.add_argument('-o', help="output filename")
args = parser.parse_args()

df = pd.read_csv(sys.stdin, delimiter='\t',
                 names=["name", "sex", "number", "year"])
df_sorted = df.sort_values(by="number")

df_sorted.to_csv(args.o, sep='\t', header=None, index=None, 
                 line_terminator='\n')

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

先にUNIXでの作り方から。最初、

$ cut -f 1 popular-names.txt | sort | uniq -c | sort -nr

というのを考えたが、これだと名前も降順になってしまう。名前は昇順になるように以下のようにしてみた:

$ cut -f 1 popular-names.txt | sort | uniq -c | sort -k 1nr

これなら、以下のようになる:

(略)
     26 Emily
     26 Jennifer
     26 Linda
     26 Sarah
     25 Jacob
     25 Jessica
     24 Betty
     24 Mildred
     24 Susan
(略)

この出力と同じになるように作成:

import argparse
import sys
import re

parser = argparse.ArgumentParser()
parser.add_argument('-o', help="output filename")
args = parser.parse_args()

d = {}
regex = re.compile(r'^([^\t]+)\t')
for s in sys.stdin:
    w = regex.match(s).group(1)
    if not w in d:
        d[w] = 0
    d[w] += 1

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

f_o = open(args.o, "w", newline='\n')
for w in sortedD:
    f_o.write(f"{w[1]:>7d} {w[0]}\n")
1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?