やりたいこと
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に対応しているとは限らない)
ex. 1行目の[0 1 2 3 4] <-> a
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対話モード
参考記事に沿ってやってみた
複数の配列(ここでは数字列とラベル)の関係性を保ったまま、ソートができないか調べた。
...この記事がドンピシャっぽいので実行してみた。
対話モード開始
$ 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()
:
配列aを小さい順に並び替えて、その並び替えた結果を配列aとして置き換える -
sorted(a)
:
配列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']
配列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個でインデントを作成。
# データの作成
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)