【第2章】言語処理100本ノックでPythonに入門

この記事は拙著[言語処理100本ノックでPythonに入門](https://qiita.com/hi-asano/items/02d82cc1e89fc663b4e6)の続きです。
[100本ノック第2章](https://nlp100.github.io/ja/ch02.html)に取り組みながらPython（とUnixコマンド）の基礎を学びたい方向けです。

12番くらいまでできればほぼPythonの基礎はほぼOKです。

あとは少し細かい知識を地道に身に着けていく感じになると思います。
まずは問題文で指示されたファイルを適当な方法でダウンロードします。


```shell-session
$ wget https://nlp100.github.io/data/popular-names.txt
```
    

# 問題文の補足
自然言語処理においては、巨大なテキストファイルを1行ずつ処理したいという場面が多く、この章の問題もそうなっています。

1行が1データになっていて、列が項目に分けられているような構造を表現する形式にはTSV (Tab-Separated Values)やCSV (Comma-Separated Values) などがよく用いられます。この章で扱うファイルはタブ区切りなのでTSVですね。

（紛らわしいのですが、このような形式をまとめてCSV (Character-Separated Values) と呼ぶこともあります。）

# 解答例の方針

この章の問題は[pandas](https://pandas.pydata.org/)や標準ライブラリの[csv](https://docs.python.org/ja/3/library/csv.html)を使って解くこともできますが、そこまで必要性を感じられないので最も素朴な方法を解説していきます。ちなみに、解答例のコーディングスタイルは[PEP8](https://pep8-ja.readthedocs.io/ja/latest)に従っているつもりです。例えば変数名・関数名は`snake_case`にする、インデントは半角スペース4つにする等です。

# Unixコマンドについて

オプション・パイプ・リダイレクト・`less`に馴染みが無い人は[こちらのQiita記事](https://qiita.com/eumesy/items/6b0eda9f604934092857)の1・3節を読んでおいてください。そして`$ less popular-names.txt`でダウンロードしたファイルの中身を確認してみましょう。

コマンド名については問題文で指定されるので、知らなくても`--help`を駆使すれば使えると思います。ただ、この章で登場するUnixのコマンドのほとんどはよく使うものなので、できれば覚えておきましょう。

# ファイル読み込み
Cではファイルポインタを駆使していましたが、Pythonではファイルオブジェクトという便利なデータ型を使います。ファイルオブジェクトはイテラブルなので、テキストファイルを1行ずつ読みたいときは次のように書きます。


```python
with open('popular-names.txt') as f:
    for line in f:
        print(line, end='')

```

with構文は、`f = open('popular-names.txt')`の実行と、ブロックを抜けるときの`f.close()`を勝手にやってくれる優れものです。公式ドキュメントでも**良い習慣**であるとされているので、ぜひ使いましょう。

for文の各ループでは`line`に各行の内容が代入されています。

ファイルを一度に複数個使うときは`with open('test1') as f1, open('test2') as f2`のようにカンマを使います。

標準入力を一行ずつ読みたいときは`sys.stdin`を使います。これもファイルオブジェクトです。この章の問題は全て上の方法でできるのですが、標準入力を使う方が何かと便利です。

```python
import sys

for line in sys.stdin:
    print(line, end='')
    
```

（標準入力は最初から`open()`されているので`with`は不要、と考えてください。）

（一部Unixコマンドは標準入力もファイル名も受け付ける仕様になってますが、Pythonでそこまでやるのはやや面倒です→[参考記事](https://qiita.com/hi-asano/items/010e7e3410ea4e1486cb)）

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

Pythonスクリプトを保存して`popular-names.txt`を標準入力から受け取って実行してみましょう。

以下、解答例です。


```q10.py
import sys

i = 0
for line in sys.stdin:
    i += 1
print(i)

```

```shell-session
$ python q10.py < popular-names.txt
```

    2780


PythonではCの`i++`は使えないので累算代入演算子`+=`を使いましょう。

（`f.read().splitlines()`, `f.readlines()`, `list(f)`はファイルサイズが大きいときや、複雑な処理をしたいときに困るので避けてます）

少しエレガントな方法にも触れておきます。`sys.stdin`はイテラブルであること、Pythonのforブロックはスコープを形成しないことを利用します。


```python
import sys


i = 0
for i, _ in enumerate(sys.stdin, start=1):
    pass

print(i)
```


ループ回数を数える組み込み関数`enumerate()`が活躍します。使わない戻り値を`_`で受けるのはPythonの慣習みたいなものです。`pass`文は何もしたくないけど文法上何かを書く必要があるときに使います。

確認は`wc`コマンド(*word count*)を使います。普通に使うといろいろ出てくるのでオプション`-l, --lines`を指定します。


```python
$ wc -l popular-names.txt
```

    2780 popular-names.txt


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

`str.replace(old, new)`を使ってみましょう。このメソッドは文字列中の部分文字列`old`を`new`に置換して返します。タブ文字はCと同じように`\t`とします。

以下、解答例です。


```q11.py
import sys


for line in sys.stdin:
    print(line.replace('\t', ' '), end='')
```


行数が多いので結果の確認は`python q11.py < popular-names.txt | less`等でやりましょう。

Unixコマンドは3つ挙げられてますが`sed -e 's/\t/ /g' popular-names.txt`が一番ポピュラーですね。Twitterでも自分の誤字をリプでこのように修正する人をたまに見かけます。`sed`はStream EDitor の略で多機能なコマンドです。

個人的には`s/\t/ /g`の部分が面倒なので`tr '\t' ' ' < popular-names.txt`を使いますかね...

とはいえ`sed`は知っておくべきコマンドで、`sed -n 10p`で10行目を抽出、`sed -n 10,20p`で10～20行目を抽出、みたいなこともできるので便利です。

# ファイル書き込み
次の問題ではファイル書き込みについて学びます。テキストファイルを書き込みモードで開くときは`open(filename, 'w)`を使います。



```python
with open('test', 'w') as fo:
    # fo.write('hoge')
    print('hoge', file=fo)
```

書き込むときは`write()`メソッドを使っても良いですが、改行を付け忘れたりする等やや不便なので、`print()`のオプション引数`file`を使うのが良いと思います。

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

以下、解答例です。



```q12.py
import sys


with open('col1.txt', 'w') as fo1,\
     open('col2.txt', 'w') as fo2:
    for line in sys.stdin:
        cols = line.rstrip('\n').split('\t')
        print(cols[0], file=fo1)
        print(cols[1], file=fo2)
```

2つの`open()`は1行にまとめて書いても良かったのですが、バックスラッシュ`\`を使うことで改行しても文が継続していると見なされます。

`popular-names.txt`の読み込みにも`open()`を使っても良いのですが、`with`文が更に長くなることをきらって標準入力から読み込む方式にしてます。

`line.rstrip('\n').split('\t')`の部分ですがこれはメソッドチェインと呼ばれるもので、左からメソッドが順々に実行されます。この問題では`rstrip()`しなくても結果は変わりませんが、`cols[-1]`に改行文字が含まれてしまうのを防ぐためのものです。テキストを読み込むときに習慣のように行う処理です。

Unixコマンドは`cut`にオプション`-f, --fields`を指定すればOKです。コマンドを2回実行しても良いですが`&&`で一気にいけます。


```python
!cut -f1 popular-names.txt > col1.txt && cut -f2 popular-names.txt > col2.txt
```

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

これは簡単ですね。以下、解答例です。


```q13.py
with open('col1.txt') as fi1,\
     open('col2.txt') as fi2:
    for col1, col2 in zip(fi1, fi2):
        col1 = col1.rstrip()
        col2 = col2.rstrip()
        print(f'{col1}\t{col2}')

```

    Writing q13.py


組み込み関数`zip()`の出番です。`rstrip()`は引数を省略すると末尾のあらゆる改行文字・空白文字を除去します。

（入力ファイルが3つ以上になることを考えると、`zip()`の戻り値を1つの変数で受け取って`join()`する方式が良いでしょう。さらに挙動を`paste`コマンドに近づけようとすると大変です。[こちらの記事](https://qiita.com/hi-asano/items/de83b18ce4365dd6f793)をお読みください。）

Unixコマンドは`paste col1.txt col2.txt`でOKです。

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

コマンドライン引数は`sys.argv`で取得できますが、`argparse`を使う方が何かと便利です。使い方は、[公式の素晴らしいチュートリアル](https://docs.python.org/ja/3/howto/argparse.html)の最初から「短いオプション」までを読んでください...

ファイルオブジェクトはシーケンス型ではないのでスライスが使えません。他の方法で行数を数えましょう。

以下、解答例です。



```q14.py
import argparse
import sys


def arg_lines():
    parser = argparse.ArgumentParser()
    parser.add_argument('-n', '--lines', default=1, type=int)
    args = parser.parse_args()
    return args.lines


def head(N):
    for i, line in enumerate(sys.stdin):
        if i < N:
            print(line, end='')
        else:
            break


if __name__ == '__main__':
    head(arg_lines())
```

```shell-session
$ python q14.py -n 10 < popular-names.txt
```

次の問題で使いまわすため、`argparse`部分を単独で関数化してます。`if __name__ == '__main__':`でメインの処理がimport時に勝手に実行されるのを防いでいます。

(うるさいことを言うと、`if __name__ == '__main__':`以下に長々と処理を書くのは良くありません。変数が全てグローバルになるからです。Pythonはグローバル変数のアクセスが遅いのでパフォーマンスが下がるというデメリットも生じます。実は今まで関数化せずにベタ書きしてたコードも関数化することでほんのわずかに速くなります。）

`break`は`for`ブロックの中で使う制御文で、ただちに`for`文を抜けます。`continue`（ただちに次のループに移動する）と併せて覚えておきましょう。

`head()`関数はもう少しエレガントに書けます。


```python
import sys
from itertools import islice

def head(N):
    for line in islice(sys.stdin, N):
        print(line, end='')
```

Unixコマンドは`head -n 5 popular-names.txt`等で良いです。オプションを省略するとデフォルトの値(おそらく10)で実行されます。

11番の解説時、行数が長いのでパイプで`less`に渡す等してくださいと書きましたが、最初の方だけ確認したいときは`head`で十分でしたね。

パイプでこれらのコマンドを渡すと終了時に`Broken Pipe Error`と出ると思います。
これを防ぎたい人は`head popular-names.txt | python q11.py`のように先に`head`するか、`python q11.py < popular-names.txt 2>/dev/null | head`のようにエラー出力を捨てるかしましょう。

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

Pythonのファイルオブジェクト(`sys.stdin`や`open()`の戻り値)は、ファイルポインタを先頭から進めていくことしかできません（原則）。1回行数を数えてからもう1回ファイルを開きなおして...とやるのは無駄が多いです。`readlines()`して後ろをスライス...とやるのはメモリの浪費です。

ここは*キュー*という先入れ後出しのデータ構造を知っていればスマートにできます。つまり、長さNのキューにファイルの中身を1行ずつどんどん入れていけば良いのです。するとキューの長さからはみ出た要素が勝手に出ていくので、最終的にキューの中には末尾N行だけが残るというわけです。

Pythonにおけるキューの実装は[collections](https://docs.python.org/ja/3/library/collections.html)モジュールの`deque`を使いましょう。というかもう公式ドキュメントの`dequeのレシピ`に`tail()`の例が書いてあるので、それがほぼこの問題の答えです。

以下、解答例です。


```q15.py
from collections import deque
import sys

from q14 import arg_lines


def tail(N):
    buf = deque(sys.stdin, N)
    print(''.join(buf))


if __name__ == '__main__':
    tail(arg_lines())
```

`deque`はリスト同様`for`文で回すこともできます。今まではリストを`for`文で回して`print()`してましたが、`join()`して一気に`print()`する方が速いです（[参考](https://qiita.com/hi-asano/items/aa2976466739f280b887#%E3%81%8A%E3%81%BE%E3%81%91-%E5%95%8F%E9%A1%8C3-print)）。14番も`print(''.join(islice(sys.stdin, N)), end='')`で十分でした。

Unixコマンドは`tail -n 5`でOKです。

以下、少し難易度が上がりますがぜひ理解してほしい話です。

# イテラブルとイテレータ
前回の記事で「for文で回せるものをイテラブルという」と説明しました。ここで今まで出てきたイテラブルなデータ型 (+α)を分類すると次のようになります。細かい用語を覚えること自体に必要性は無いですが、今後新しいデータ型と出会ったときに何と似ているのかがわかると楽になることがあります。

* Iterable
    * Iterator
        * 組み込み関数`zip()`, `enumerate()`の戻り値
        * ファイルオブジェクト
        * `itertools.islice()`の戻り値
        * Generator（後段で説明します）
    * Collection
        * Sequence
            * `list`, `tuple`, `str`, `range`, `colections.deque`
        * Set
            * `set`, `frozenset`（変更不能な集合型）
        * Mapping
            * `dict`, `collections.Counter`, `collections.defaultdict`

ここで**イテレータ** (Iterator) について説明します（これまで騙し騙し扱っていました）。リストなどのデータ型は全要素を一度にメモリにのっけるので大きなサイズになると扱いにくいです。また、`len()`やインデクスを使う必要が無くただ`for`文や、`str.join()`などの引数で利用するだけの場合は無駄が多いです。特にループを最後の要素まで回す必要が無い場合に、後ろの方の要素まで生成するのは無駄な手間です。そのような欠点を解消したのがイテレータです。イテレータは１ループで１つの要素を返すだけなので、メモリを効率的に扱えるという利点があります。スライスは使えませんが、`itertools.islice()`でそれっぽいことができます。また、一度ループを回し切ってしまうと何もできなくなります。このような制約があるので、専ら`for`文やイテラブルを引数にとる関数で使います。

`for`文だけなく`in`演算や`len()`に対応しているデータ型をコレクションと言ったりコンテナと言ったりします（公式ドキュメントではコンテナとされているが、[抽象基底クラス](https://docs.python.org/ja/3/library/collections.abc.html)の定義上はコレクションの方が厳密）。

シーケンス型のものは全てインデクスやスライスが使えます。

`collections`モジュールには`deque`の他にも`Counter`や`defaultdict`といった便利なデータ型が定義されているので、これも知っておきましょう。この後の問題で使うかもしれません。

## リスト内包表記とジェネレータ式
イテラブルオブジェクトに対して、ひとつずつ全要素に何らかの操作をしたいときや、条件に合う要素だけ抽出したいときがあります。そうした処理を簡潔に記述できるのが内包表記、およびジェネレータ式です。100本ノック03「円周率」の例で説明します。


```python
tokens = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'.split()

# リスト内包表記
stripped_list = [len(token.rstrip('.,')) for token in tokens]
print(stripped_list)
```

    [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]


まずリスト内包表記から。以前は`for`文の中で`append()`していましたが、リスト内包表記では`append()`したいものを先に書き、後ろに`for`文をくっつけて`[]`で囲みます。少しとっつきにくいかもしれませんが、こちらの書き方の方が高速に動作するので（[参考](https://qiita.com/hi-asano/items/aa2976466739f280b887#%E5%95%8F%E9%A1%8C1-%E3%83%AA%E3%82%B9%E3%83%88%E7%94%9F%E6%88%90)）積極的に使いましょう。

次はジェネレータ式です。ジェネレータ式の戻り値はジェネレータ型といい、イテレータの一種です。イテレータは改めて`for`文で回すか、別の関数に渡すという形でしか使えません。どちらかというと後者の方が普通の利用法です。


```python
# ジェネレータ式
stripped_iter = (len(token.rstrip('.,')) for token in tokens)

for token in stripped_iter:
    print(token, end=' ')
```

    3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 


```python
' '.join(str(len(token.rstrip('.,'))) for token in tokens)
```




    '3 1 4 1 5 9 2 6 5 3 5 8 9 7 9'



このように、ジェネレータ式はリスト内包表記の`[]`を`()`にしただけです。ただし関数の引数に渡すときはその`()`を省略できます。

関数にジェネレータ式を渡すと中間変数が減らせるというメリットがあります。リスト（内包表記）を渡す場合と比べると、メモリ使用量を減らせますし、多くの場合ジェネレータ式の方が高速です。

（稀にリストを渡すほうが高速な関数があり、この`join()`もその例外の一つのようです...）

## ジェネレータ関数
ジェネレータ式は複雑な処理をしたいときには書きにくいという問題点があります。そこでイテレータを返す関数を定義したい場合があります。最も簡単な方法は`yield`文を使う方法で、そのようにして定義したものをジェネレータ関数と言います。もちろんジェネレータ関数が生成するオブジェクトもジェネレータ型です。

ジェネレータ関数を定義するには、`yield 戻り値`を関数の動作の「途中」（または最後）に置きます。`return`はそこで関数の処理が終了させられ、関数内ローカル変数は消え去るという点が大きく異なります。



```python
def tokens2lengths(tokens):
    for token in tokens:
        yield len(token.rstrip('.,'))

for token in tokens2lengths(tokens):
    print(token, end=' ')
```

    3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 

何、ありがたみがわからないですって？そうかもしれないですね...。この2章でジェネレータ関数は使わないですね...。再帰関数の記述が簡単になるとは言われているが、再帰関数自体あまり書かない…。個人的には、次のような場合に使います。まず自作関数`process`があったとします。

```Python
for elem in lis:
    if elem is not None:
        outstr = process(elem)
        print(outstr)
```

このコードは`lis`の要素数が多くなってくると関数呼び出し時間が馬鹿になりません。そこでprocessをジェネレータ関数化すると若干高速になります。条件式を吸収させることもでき、メイン関数がスッキリします。

```Python
for outstr in iter_process(lis):
    print(outstr)
```

ちょっと本筋ではない話が長くなってしまいました。次の問題を解きましょう。

### とても細かい用語の揺れ
ドキュメントでは、ジェネレータとは通常ジェネレータ関数のことを指す、ジェネレータ関数が生成するオブジェクトはジェネレータイテレータと言う、という記述があります。しかし`type()`関数を使ってジェネレータ関数（およびジェネレータ式）の戻り値の型を調べると`Generator`と出てきます。このため、公式が言うジェネレータイテレータは非公式文書ではジェネレータと呼ばれることが多い気がします。


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

難問です。いろいろな方法が考えられますが、ファイルの中身を一度にメモリに載せない縛りを貫くには、最初に全体の行数を数えてから分割するしか無さそうです。ファイルオブジェクトの参照点を先頭に戻すにはもう一回ファイルを開きなおすか、`f.seek(0)`（先頭0バイトを参照するという意味）を使うとできます。


そして**できるだけ均等になるように**N分割する方法が悩ましいです。例えば14行のものを4分割する場合は`4行, 4行, 3行, 3行`という具合に分けたいです。考えてみましょう。

それができれば4行読んで書き込むだけです。`fi.readline()`という、1行だけ読むメソッドがあるのですが、それの出番かもしれません。書き込み先はおそらく別々のファイルにするべきです。

以下、解答例です。


```q16.py
import argparse
import sys


def main():
    parser = argparse.ArgumentParser(
        description='Output pieces of FILE to FILE1, FILE2, ...;')
    parser.add_argument('file')
    parser.add_argument('-n', '--number', type=int,
                        help='split FILE into n pieces')
    args = parser.parse_args()
    file_split(args.file, args.number)

    
def file_split(filename, N):
    with open(filename) as fi:
        n_lines = sum(1 for _ in fi)
        fi.seek(0)
        for nth, width in enumerate((n_lines+i)//N for i in range(N)):
            with open(f'{filename}.split{nth}', 'w') as fo:
                for _ in range(width):
                    fo.write(fi.readline())


if __name__ == '__main__':
    main()
```

```shell-session
$ python q16.py -n 3 popular-names.txt
$ wc -l popular-names.txt.split*
```

      926 popular-names.txt.split0
      927 popular-names.txt.split1
      927 popular-names.txt.split2
     2780 total


`argparse`の使い方は気にしなくて良いです。行数を数えるのに、今回はイテラブルの要素和を計算する組み込み関数`sum()`を使っています。

そして整数を均等に分ける方法です。`m`個のものを`n`人に分けるとき商は`q`で余りが`r`になったとしましょう。
このときは`(n-r)`人には普通に`q`個ずつ配って、残りの`r`人には余りを１個上乗せして`(q+1)`個ずつ配ると均等になります。

それをエレガントに書いたのが`((n_lines+i)//N for i in range(N))`の部分です。`//`で小数点切り捨て除算ができます。なぜこれで均等に分けられるのかは[こちらのQiita記事](https://qiita.com/keisuke-nakata/items/c18cda4ded06d3159109)をご覧ください。

行の順番を気にしない方針だと`itertools`の`tee()`と`islice()`を使う方法などが考えられます。メモリを気にしない方針だと`zip_longest()`を使うと簡単かもしれません。

Unixコマンドは`split -n l/5 -d popular-names.txt popular-names.txt`で良いですが、お使いの環境の`split`によっては動かないかもしれません。

後の問題は楽です。

## 17. １列目の文字列の異なり
> 1列目の文字列の種類（異なる文字列の集合）を求めよ．確認にはsort, uniqコマンドを用いよ．

集合に一列目をどんどん追加していくだけですね。リスト内包表記だけ上で説明しましたが、セット内包表記や辞書内包表記もあります。

以下、解答例です。


```q17.py
import sys


names = {line.split('\t')[0] for line in sys.stdin}
print('\n'.join(names))

```


集合型は実行の度に順序が変わることに気を付けましょう。それが嫌であれば辞書型を使いましょう（CPython実装では3.6以降、公式には3.7以降の辞書型はkeyの追加順になります）。

Unixコマンドは`cut -f1 popular-names.txt | sort | uniq`になります。`uniq`は隣り合う行の重複を取り除くものなので、このようなことをするには`sort`が必要です。

# ラムダ式とソート
次の問題で一応使うのでラムダ式についてやっておきます。ラムダ式はちょっとした関数を定義するのに使います。例えば`lambda a, b: a+b`と書けばそれはもう２つの数の和を返す関数です。普通の関数のように呼び出すことも可能ですが、`sort()`関数のオプション引数として渡す使い方が主です。一応他の関数に渡したり、自作関数の戻り値に使ったりすることはあります。

`sort()`については公式の[ソート HOW TO](https://docs.python.org/ja/3/howto/sorting.html)が良い資料です。「昇順と降順」まで読めば十分です。

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

（コラムとは...さっきまで列って言ってたのに...）以下、解答例です。


```q18.py
import sys


sorted_list = sorted(sys.stdin, key=lambda x: int(x.split('\t')[2]), reverse=True)
print(''.join(sorted_list))
```

3列目の数値は数値型にキャストしないと文字列のままであることに注意しましょう。キャストは組み込み関数でできます。

Unixコマンドは`sort -k3 -nr popular-names.txt`です。3番目の要素を数字と見なして昇順でソート、という意味です。

Unixの`sort`は大変優秀で、巨大なファイルだろうとout of memoryを起こさず実行してくれます。さらに比較的簡単に高速化できます(ロケールをいじる、分割して最後にマージする等)。

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

ここで`collections.Counter`の伏線が回収されました！[ドキュメント](https://docs.python.org/ja/3.7/library/collections.html?highlight=collections%20counter#collections.Counter)を読めば問題無いでしょう。`Counter`は`dict`のサブクラスであるということもおさえておきましょう。

以下、解答例です。

```q19.py
from collections import Counter
import sys


col1_freq = Counter(line.split('\t')[0] for line in sys.stdin)
for elem, num in col1_freq.most_common():
    print(num, elem)
```


Unixコマンドは`cut -f1 popular-names.txt | sort | uniq -c | sort -nr`です。パイプが連なるときはひとつひとつ`head`などを使って中間出力を確認してみるのがいいと思います。

# まとめ
- Unixコマンドの基礎
- ファイル読み書き
- `str.replace()`
- `argparse`
- `collections`
- イテレータとジェネレータ
- 内包表記
- ラムダ式とソート

# 次は３章
JSONファイルは[jsonモジュール](https://docs.python.org/ja/3/library/json.html)で読み込めます。正規表現については公式の[正規表現 HOW TO](https://docs.python.org/ja/3/howto/regex.html)で学びましょう。LGTMやコメントがあれば続編を執筆します。

(4/30追記)
第3章の解説を公開しました。→　https://qiita.com/hi-asano/items/8e303425052781d95f09