2014年6月4日水曜日

Python: ファイル名の番号でソートする

はじめに

たとえば次のようなファイル名のリストがあったとき
track1.mp3
track2.mp3
track3.mp3
track11.mp3
track23.mp3
track30.mp3 
これをトラック番号でソートしたいと思います.

しかし,これを次のように普通にソートすると次のようになってしまいのぞましい結果とは言えません.

>>> list = [ "track1.mp3", "track2.mp3", "track3.mp3", "track11.mp3" "track23.mp3", "track30.mp3"]
>>> list.sort()
>>> list
[ "track1.mp3", "track11.mp3", "track2.mp3", "track23.mp3" "track30.mp3"]
そこで,理想通りソートできるようにします.

結論

結論から行くと次のようにしました.
ファイル名の文字列の中で,当然ながら数値の部分も文字列として扱ってソートしてしまうのが問題です.数値の部分だけを抽出して,文字列ではなく数値としてソートします.

>>> import re
>>> list = [ "track1.mp3", "track2.mp3", "track3.mp3", "track11.mp3", "track23.mp3", "track30.mp3"]
>>> list2 = [(re.search("[0-9]+", x).group(), x) for x in list]
>>> list2
[('1', 'track1.mp3'), ('2', 'track2.mp3'), ('3', 'track3.mp3'), ('11', 'track11.mp3track23.mp3'), ('30', 'track30.mp3')]

>>> list2.sort(cmp = lambda x, y: cmp(int(x[0]), int(y[0])))
>>> list2
[('1', 'track1.mp3'), ('2', 'track2.mp3'), ('3', 'track3.mp3'), ('11', 'track11.mp3track23.mp3'), ('30', 'track30.mp3')]

>>> list = [x[1] for x in list2]
>>> list
['track1.mp3', 'track2.mp3', 'track3.mp3', 'track11.mp3track23.mp3', 'track30.mp3']
できあがり(^^)v

要素を見ましょう!

文字列から数値の抽出

reモジュールをインポートしてsearch関数によって正規表現で文字列から数字の部分を抽出します.
search関数の戻り値のオブジェクトが持つgroup()を呼び出すことで一致した部分(数字)の文字列が抽出できます.この段階ではまだ文字列型です.
>>> impore re
>>> str = "track3234.mpe"
>>> m = re.search("[0-9]+", str)
>>> m.group()
3234

ファイル名とトラック番号にようるタプルの配列を作成

次のような.トラック番号とファイル名をペアにしたタプルの配列を作成しています.
[('1', 'track1.mp3'), ('2', 'track2.mp3'), ('3', 'track3.mp3'), ('11', 'track11.mp3track23.mp3'), ('30', 'track30.mp3')]

「結論」で記述したコードではまとめて書いていますが丁寧に書くとすると次のようにかけます.

import re
list2 = []
list =  [ "track1.mp3", "track2.mp3", "track3.mp3", "track11.mp3", "track23.mp3", "track30.mp3"]
for x in list:
    m = re.search("[0-9]+", x)
    tuple = (m.group(), x)
    list2.append(tuple)

これを次のようにまとめて書くことが出来ます.

import re
list = [ "track1.mp3", "track2.mp3", "track3.mp3", "track11.mp3", "track23.mp3", "track30.mp3"]
list2 = [(re.search("[0-9]+", x).group(), x) for x in list]

タプルの1つめの要素を数値型にして,比較そしてソートする

作成したタプル配列list2をソートします.
ただし単純にsort()を呼び出してもソートできないので,ソートする際の比較メソッドを独自に指定します.
 次のように引数cmpに比較関数を与えます.通常はcmp=cmp(x, y)となっており,x < yのとき-1,x == yのとき0, x > yのとき1を返す関数です.
ここではラムダ式を用いて,与えられる2つのタプルx, yの最初の要素(すなわちトラック番号)を数値型にキャストしてcmp()関数に与えています.

list2.sort(cmp = lambda x, y: cmp(int(x[0]), y[0]))

タプル配列からもとの文字列配列を作成する

最後に,タプル配列ではなく文字列配列にします.
丁寧に書けば次のように書きますね.

list = []
for x in list2:
    list.append(x[1])

これをつぎのようにまとめて書きました

list = [x[1] for x in list2]

総括

ファイル名などに番号を入れて順番を管理している際,番号の部分も文字列として扱ってしまっていることが原因で適切にソートされない場合があります.ファイル名の文字列から数字の部分だけを抽出して,数値としてソートすれば良いと言うことですね.

oO(それにしてもPythonってすごく簡単にかける・・・(^^)v...)