Python3で複数の配列を同時にソート

## やりたいこと
Python3で複数の配列を同時にソートしたい！
...とは言っても、うまく伝わらないと思うので、具体例を挙げます。

### 対応関係
下記の対応（<->）は、辞書のようなもの。
ex.「私」を数字で表現すると「0」、「1」に対応する言葉は「は」

```
0 <-> 私
1 <-> は
2 <-> 学生
3 <-> です
4 <-> 。
5 <-> 宇宙
6 <-> が
7 <-> 好き
8 <-> でした
```

この辞書を使って、文章を数字列に変換（数字列を文章に変換）すると、

```
私 は 学生 です 。        <-> [0 1 2 3 4]
私 は 学生 でした 。      <-> [0 1 2 8 4]
私 は 宇宙 が 好き です 。 <-> [0 1 5 6 7 3 4]
```

### ソート前のデータ
- 上記の辞書変換によって、文章と数字列が対応。
- 1行の数字列に対して、1つのラベルが割り当てられている。（1対1に対応しているとは限らない）<br>
ex. 1行目の[0 1 2 3 4] <-> a<br>
ex. 4行目の[0 1 2 3 4] <-> d

| 並び順 | 数字列            | 文章              | ラベル |
|:-----:|:-----------------|:-----------------|:--:|
|   1   | [0 1 2 3 4]      | 私は学生です。      | a |
|   2   | [0 1 5 6 7 3 4]  | 私は宇宙が好きです。 | b |
|   3   | [0 1 2 8 4]      | 私は学生でした。　　 | c |
|   4   | [0 1 2 3 4]      | 私は学生です。      | d |


### ソート後のデータ
- **数字列と文章、ラベルの対応関係が保たれている**
- 重複した数字列が連続で出てくる
- 数字列の「1つ目の要素」でソート → 「2つ目の要素」でソート → ... → 「5つ目の要素」でソート

| 並び順 | 数字列               | 文章                  | ラベル |
|:-----:|:--------------------|:---------------------|:-----:|
|   1   | [0 1 2 3 4]         | 私は学生です。          |   a   |
| **2** | **[0 1 2 3 4]**     | **私は学生です。**      | **d** |
|   3   | [0 1 2 8 4]         | 私は学生でした。　　     |   c   |
| **4** | **[0 1 5 6 7 3 4]** | **私は宇宙が好きです。** | **b** |


## 実行環境
- macOS High Sierra (ver: 10.13.5)
- Anaconda (ver: 4.5.6)
- Python 3.6.5 :: Anaconda, Inc.
- Python対話モード

## 参考記事に沿ってやってみた
複数の配列（ここでは数字列とラベル）の関係性を保ったまま、ソートができないか調べた。
...[この記事](http://xvideos.hatenablog.com/entry/python_sort_multi_array)がドンピシャっぽいので実行してみた。

### 対話モード開始
```
$ python
```

### データの作成
```
>>> a = []; b = []; c = [] # 初期化（＊ c = []まで入力した後、エンターキーを押すと、次の入力を受け付けることを示す「>>>」が出てくる）

>>> a.append(list(range(5))) # a.append([0,1,2,3,4])と同じこと
>>> a.append([0 1 5 6 7 3 4]) # [0 1 5 6 7 3 4] を 配列aの1要素として追加
>>> a.append([0 1 2 8 4])
>>> a.append(a[0])
>>> a  # 配列aの中身を見る
[[0, 1, 2, 3, 4], [0, 1, 5, 6, 7, 3, 4], [0, 1, 2, 8, 4], [0, 1, 2, 3, 4]]

>>> b.append(['私', 'は', '学生', 'です', '。'])
>>> b.append(['私', 'は', '宇宙', 'が', '好き', 'です', '。'])
>>> b.append(['私', 'は', '学生', 'でした', '。'])
>>> b.append(['私', 'は', '学生', 'です', '。'])
>>> b
[['私', 'は', '学生', 'です', '。'], ['私', 'は', '宇宙', 'が', '好き', 'です', '。'], ['私', 'は', '学生', 'でした', '。'], ['私', 'は', '学生', 'です', '。']]

>>> e = ['a','b','c','d']
>>> e
['a', 'b', 'c', 'd']
```

### ソート
```
>>> k = zip(a,b,e) # zipで3つの配列をセットにする
>>> k
<zip object at 0x10ee02048>

>>> k.sort() # セットにしたものをソートして、kをソートした結果のセットに置き換える
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'zip' object has no attribute 'sort'
```
んんん！？
zip()とsort()は相性が悪いらしい。困った。

## 「ソート」部分の問題を解決
sort()の代わりに、sorted()を使う！

- `a.sort()` : <br>配列aを小さい順に並び替えて、**その並び替えた結果を配列aとして置き換える**

- `sorted(a)` : <br>配列aを小さい順に並び替えるが、**配列aの値は変わらない**

```
>>> k = zip(a,b,e) # zipで3つの配列をセットにする
>>> q = sorted(k) # 3つの配列をセットにしたものをソート（入力した「ソート前のセット」をソート結果に置き換えない）
>>> q
[([0, 1, 2, 3, 4], ['私', 'は', '学生', 'です', '。'], 'a'), ([0, 1, 2, 3, 4], ['私', 'は', '学生', 'です', '。'], 'd'), ([0, 1, 2, 8, 4], ['私', 'は', '学生', 'でした', '。'], 'c'), ([0, 1, 5, 6, 7, 3, 4], ['私', 'は', '宇宙', 'が', '好き', 'です', '。'], 'b')]


>>> aaa = []; bbb = []; eee = [] # aaa, bbb, eeeに空配列（list型）を代入して初期化（＊これらは、配列a,b,eのソートした結果を格納するための配列）

# 配列q（listやnumpyなど型を問わない）の一番初めの要素「([0, 1, 2, 3, 4], ['私', 'は', '学生', 'です', '。'], 'a')」から順々にデータを取り出して、aa,bb,eeへ代入していく。配列を読み切ったらループ終了。
>>> for aa,bb,ee in q:
...     aaa.append(aa) # とりあえず「tab」キーでインデントをずらしてから入力
...     bbb.append(bb) # 1周目では、bbb.append(['私', 'は', '学生', 'です', '。'])と同じことをしている
...     eee.append(ee)
...                    # もうfor文は終わりだということで、エンターキーを押す

>>> aaa
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 8, 4], [0, 1, 5, 6, 7, 3, 4]]
>>> bbb
[['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'でした', '。'], ['私', 'は', '宇宙', 'が', '好き', 'です', '。']]
>>> eee
['a', 'd', 'c', 'b']
```

<br>配列a（ソート前） と 配列aaa（ソート後）、配列b と 配列bbb、配列c と 配列cccを見比べると、

```
>>> a
[[0, 1, 2, 3, 4], [0, 1, 5, 6, 7, 3, 4], [0, 1, 2, 8, 4], [0, 1, 2, 3, 4]]
>>> aaa
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 8, 4], [0, 1, 5, 6, 7, 3, 4]]

>>> b
[['私', 'は', '学生', 'です', '。'], ['私', 'は', '宇宙', 'が', '好き', 'です', '。'], ['私', 'は', '学生', 'でした', '。'], ['私', 'は', '学生', 'です', '。']]
>>> bbb
[['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'でした', '。'], ['私', 'は', '宇宙', 'が', '好き', 'です', '。']]

>>> e
['a', 'b', 'c', 'd']
>>> eee
['a', 'd', 'c', 'b']
```

配列a,b,eの関係性を保ちながら（何番目の要素にきているかを揃えながら）、配列a（数字列）でソートされていることが確認できた！

ちなみに、kやqで置き換えなかったとしても、同じ結果が得られる。

``` 
>>> aaa = []; bbb = []; eee = [] # 初期化

>>> for aa,bb,ee in sorted(zip(a,b,e)):
...     aaa.append(aa)
...     bbb.append(bb)
...     eee.append(ee)
...

>>> aaa
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 8, 4], [0, 1, 5, 6, 7, 3, 4]]
>>> bbb
[['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'でした', '。'], ['私', 'は', '宇宙', 'が', '好き', 'です', '。']]
>>> eee
['a', 'd', 'c', 'b']
```


## おまけ
このままだとみづらいので、文章（数字列）ごとにラベルを表示するようにした。

```
>>> ta = [] # 1つ前の数字列を格納（最初は空行列）
>>> s_list = [] # 出力する文字列をlist型で並べたもの

>>> for aa,bb,ee in sorted(zip(a,b,e)):
...     if ta != aa: # 重複してなかったら
...             tb = ''.join(bb) # 配列bbの要素を、''でくっつける
...             s_list += ['{}(={})'.format(tb,aa)] # 最初の{}にはtb, 次の{}にはaaが代入される。s_listの1要素として、文字列（配列ではない）を追加。
...             ta = aa # 1つ前の数字列を更新
...     s_list += ['-> ' + ee] # s_listの1要素として、ラベルを追加。
... 
>>> s = '\n'.join(s_list) # 配列s_listの1要素ごとに改行したものを、文字列sとする

>>> print(s) # 出力
私は学生です。(=[0, 1, 2, 3, 4])
-> a
-> d
私は学生でした。(=[0, 1, 2, 8, 4])
-> c
私は宇宙が好きです。(=[0, 1, 5, 6, 7, 3, 4])
-> b
```

## ソースコード一式
対話モードでない形式（ファイル形式）で、一式。
＊ @shiracamus 様より、より簡潔なプログラムを提示頂いたので差し替えました。
＊ 半角スペース4個でインデントを作成。

``` python:sort_keep_relation_ex.py
# データの作成
a = [
    list(range(5)),        # a.append([0,1,2,3,4])と同じこと
    [0, 1, 5, 6, 7, 3, 4], # [0 1 5 6 7 3 4] を 配列aの1要素として追加
    [0, 1, 2, 8, 4],
    [0, 1, 2, 3, 4],
]
b = [
    ['私', 'は', '学生', 'です', '。'],
    ['私', 'は', '宇宙', 'が', '好き', 'です', '。'],
    ['私', 'は', '学生', 'でした', '。'],
    ['私', 'は', '学生', 'です', '。'],
]
e = ['a', 'b', 'c', 'd']


# ソート
aaa, bbb, eee = zip(*sorted(zip(a, b, e)))

# aaa = [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 8, 4], [0, 1, 5, 6, 7, 3, 4]]
# bbb = [['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'です', '。'], ['私', 'は', '学生', 'でした', '。'], ['私', 'は', '宇宙', 'が', '好き', 'です', '。']]
# eee = ['a', 'd', 'c', 'b']


# 文章（数字列）ごとにラベルを表示
duplicate = None
for aa, bb, ee in zip(aaa, bbb, eee):
    if aa != duplicate: # 重複してなかったら
        print('{}(={})'.format(''.join(bb), aa))
        duplicate = aa
    print('->', ee)
```