Edited at

【Python】ソート

More than 3 years have passed since last update.


はじめに

久しぶりにCodeIQで問題を覗いていたら、Python3でのソートについての問題があり、その時に初めてPython3のソートについて勉強したのでここにメモとして記録ておきます。


*何か間違いや、こうした方がいいなどご指摘がありましたらご教授していただければ幸いです。


要素のソート


要素を昇順に並び替える

まず、リストに含まれる要素を、昇順に並び替えてみようと思います。

そこで利用するのはsort()というメソッドです。

文字列の場合は文字コードの並び順に、数値であれば数値が小さい順に並び替えられます。


exSort01.py

wordList = ["F","A","X"]  #文字列のリスト

numberList = [4,6,2] #数値のリスト

wordList.sort()
print (wordList) #出力結果:["A","F","X"]

numberList.sort()
print (numberList) #出力結果:[2,4,6]


要素を逆に並び替える

今度は、現在要素が並んでいる順番を逆に並び替える事をしようと思います。これはreverse()というメソッドを利用します。


exSort02.py

wordList = ["F","A","X"]  #文字列のリスト

numberList = [4,6,2] #数値のリスト

wordList.reverse()
print (wordList) #出力結果:["X","A","F"]

numberList.reverse()
print (numberList) #出力結果:[2,6,4]

このように、これは降順にソートされるわけではなく、逆の順番に並び替えられるだけです。では、降順にソートするにはどうすればいいか...


要素を降順に並び替える

ということで、今回は降順に並べるようにしたいと思います。今までの学習を応用すれば簡単にできます。

手順として考えられるのは、まずsortメソッドを実行して、降順に並び替える。そして、それをreverseメソッドを実行すれば降順にできると思います。

ということで、このように作ってみました。


exSort03_01.py

wordList = ["F","A","X"]  #文字列のリスト

numberList = [4,6,2] #数値のリスト

wordList.sort()
wordList.reverse()
print (wordList) #出力結果:["X","F","A"]

numberList.sort()
numberList.reverse()
print (numberList) #出力結果:[6,4,2]

できた!が、しかし。

2行だとなんかダサい。1行にできないか...。ということで、こうゆう風に書き換えました。


exSort03_01.py

wordList = ["F","A","X"]  #文字列のリスト

numberList = [4,6,2] #数値のリスト

wordList.sort(reverse=True)
print (wordList) #出力結果:["X","F","A"]

numberList.sort(reverse=True)
print (numberList) #出力結果:[6,4,2]

おぉ!できたし、1行にまとめられてすっきり!


多次元リストのソート

1次元のソートはわかった。ということで、次は多次元ソートへ。例えば、次のようなリストがあったとします。

list = [[10,4],[3,6],[4,6],[5,0],[4,9],[2,0]]

では、このリストをまず1番目の要素で昇順にして、次に2番目の要素で昇順にして

[[2,0], [5,0], [10,4], [3,6], [4,6], [4,9]]

という結果を求めたい場合はどうすればいいのでしょうか?

ということで、調べたら『多重キーでのソート』というタイトルの記事があり、そちらでは生徒の成績リストを国語と算数の点数で高い順に並べたいという題材で書かれていました(記事では降順について書かれています)。そこで、そちらを参考にさせていただいて、次のように書いたらできました!


exSort04.py

list = [[10,4],[3,6],[4,6],[5,0],[4,9],[2,0]]

list.sort(key=lambda x:x[0])
list.sort(key=lambda x:x[1])
print (list)

これは何をしているかというと、まず、昇順のソートをしようとしています。その中で、lambda式というものを使って1番目の要素をkey選択をさせて昇順させて、次に同じくlambda式というものを使って今度は2番目の要素をkey選択をさせて昇順させています。たぶん...。

lambda式というのdefステートメントのように関数を作成する際に使用するもので、Lispから来た名前らしいです...(『[Python][お勉強] Python入門(27) - lambda式』より)

lambda 引数1,引数2,...,引数N : 返したい答えを求める計算式

みたいな感じに書くらしい。

ちなみに、1番目の要素を昇順にして、1番目の同じ値が同じものは2番目の値で比較し、そこだけをまた昇順したい場合は次にように書いたらできました。


exSort04.py

list = [[10,4],[3,6],[4,6],[5,0],[4,9],[2,0]]

list.sort(key=lambda x:(x[0],x[1]))
print (list)

これは『標準の比較関数であるcmp()に秘密』があるらしいです。そちらについては、『多重キーでのソート』の記事でご確認ください。

他にもソートにはsorted()というメソッドもあるらしいのですが、今回はおあずけで。


フィードバック

早くも問題提供者の方からフィードバックが来たので追記。とても早い!

問題の回答としては正解だったのですが、

operatorモジュールのitemgetterメソッドを利用する方法がある』とのこと。

なんだそれ?!っと思って調べました。

すると、『9.9. operator — 関数形式の標準演算子』と『ソート HOW TO』というのが見つかりました。話の内容はどちらともに為になるので、もし一読されたことがない方は読んでみるのもいいかもしれないです。

さて、話を戻して。

結論から話すと、次のように記述すれば一行で今までの作業が終わりました!


exSort04.py

from operator import itemgetter

list = [[10,4],[3,6],[4,6],[5,0],[4,9],[2,0]]
list.sort(key=itemgetter(1,0))
print (list)

綺麗だ。

今回は手順が2回しかない例をあげていたので、operatorモジュールのitemgetterメソッドを呼び出す『from operator import itemgetter 』という一文が含まれているので行数が減ってないように感じますが、これが手順が3回以上になると確実にこちらのほうが記述量が減ります。

今までの流れを見ていれば、なんでlist.sort(key=itemgetter(1,0))と書けばこうなるかはわかると思うので説明ははしょります。わからない方は次のitemgetterメソッドの中身を見ていただければわかるかも...?

10.3. operator — 関数形式の標準演算子』によると、itemgetterメソッドは次のようになっているらいいです。


itemgetterメソッド

def itemgetter(*items):

if len(items) == 1:
item = items[0]
def g(obj):
return obj[item]
else:
def g(obj):
return tuple(obj[item] for item in items)
return g

なるほどー。


参考サイト