個人的にはaの配列をソートして、新しい配列を作り、その配列の順番をaの配列に反映させるプログラムを作ってみたいと思っています。
これを実現すると以下になります.
Python
1a = [12, 43, 7, 15, 9]
2new_list = sorted(a) # aの配列をソートして、新しい配列を作り
3
4rank = [0] * len(a)
5for i in range(len(new_list)): # その配列の順番を
6 for j in range(len(a)): # aの配列に
7 if new_list[i] == a[j]:
8 rank[j] = i # 反映させる
9
10print(rank) # [2, 4, 0, 3, 1]
配列の要素とその順番が一致しているかどうか判定するのに2重のループになっているため,要素数N
に対して時間計算量O(N^2)
のアルゴリズムとなっており,非常に低速な実装です.また,要素に重複がある場合正しく動作しないので,もっとコードを書く必要があります.
この問題を解決すべく,ソートする際に,元の配列のインデックス情報の方を並び替えてしまえば良い,という発想が必要になります.ここではソートにかかる時間計算量そのままO(N log N)
で済みます.
これは一般に,argsort()
と呼ばれる動作になり,順位を求める実装と合わせて次のようになります.
Python
1def argsort(arr, reverse = False): # ソート前のindexを返す
2 return sorted(range(len(arr)), key = arr.__getitem__, reverse = reverse)
3
4a = [12, 43, 7, 15, 9]
5rank = [0] * len(a)
6for r, idx in enumerate(argsort(a)): # r: 順位, idx: argsortで求めたソート前のindex
7 rank[idx] = r # ソート前のindexであるidxを使ってrank配列のidx番目の順位をrにする
8print(rank) # [2, 4, 0, 3, 1]
また,argsort()
の実装方法はこれに限りません.あくまで一例です.他にも
Python
1def argsort(arr, reverse = False):
2 return [x for x, y in sorted(enumerate(arr), key = lambda x: x[1], reverse = reverse)]
などがあります.なお基本構造はどれも同じで,ソートの対象key
の指定を,key = arr.__getitem__
として値を指定するか,enumerate()
で列挙される要素の1番目に対象の値があることを利用したkey = lambda x: x[1]
とするなど,複数のテクニックが存在します.ソートの対象はリストの値,実際に並び替えられたのはリストのインデックス.といった具合です.
stack overflow - How to get indices of a sorted array in Python
今回はこれを使ってindexを保持したソートをすることで,O(N log N)
の時間計算量で順位を反映させることができました.
このままだと同じ値でも別々の順位を割り振られる実装になっていますので,次のように変更することもできます.
Python
1rank = [0] * len(a)
2rank_no = set()
3for idx in argsort(a):
4 rank_no.add(a[idx])
5 rank[idx] = len(rank_no)
余談ですが「たまたま」例示いただいたデータ[12, 43, 7, 15, 9]
においてargsort()
の結果と求めた順位rank
が同じになっています(非常にややこしいです).argsort()
の結果を例示しなかったのは混乱するからです...
結論
Python
1import random
2
3def argsort(arr, reverse = False):
4 return sorted(range(len(arr)), key = arr.__getitem__, reverse = reverse)
5
6def calc_rank1(arr, reverse = False): # 重複があっても左から順に順位付け
7 rank = [0] * len(arr)
8 for r, idx in enumerate(argsort(arr, reverse)):
9 rank[idx] = r
10 return rank
11
12def calc_rank2(arr, reverse = False): # 重複は同じ順位
13 rank = [0] * len(arr)
14 rank_no = set()
15 for idx in argsort(arr, reverse):
16 rank_no.add(arr[idx])
17 rank[idx] = len(rank_no) - 1
18 return rank
19
20a = [random.randint(0, 20) for _ in range(10)]
21
22print("original:", a) # [9, 6, 9, 19, 9, 9, 15, 3, 10, 12]
23print("argsort :", argsort(a)) # [7, 1, 0, 2, 4, 5, 8, 9, 6, 3]
24print("rank1 :", calc_rank1(a)) # [2, 1, 3, 9, 4, 5, 8, 0, 6, 7]
25print("rank2 :", calc_rank2(a)) # [2, 1, 2, 6, 2, 2, 5, 0, 3, 4]