課題のありか
abc_1.jpg
, abc_10.jpg
, abc_2.jpg
という順序にソートされますが、自然な感覚では abc_1.jpg
, abc_2.jpg
, abc_10.jpg
となってほしいところです。
例えば、Windowsのエクスプローラーは「自然な」順序で並べてくれます。細かく配慮してます。
このあたりのTipsをまとめます。参考になれば幸いです。また、こんなのもあるよという意見があればコメントください。
言語はPython3を使います。
Tips1: パディングして生成
自分で連番をつくってよいなら、最初からゼロパディングして作ってしまえばいいので簡単です。つまり abc_001.jpg
, abc_002.jpg
, abc_010.jpg
という風につくるわけですね:
names = [f"abc_{i:03}.jpg" for i in range(10)]
何桁パディングするかを事前に抑えておく必要はあります。そこは要注意です。
Tips2: 自然なソートキー関数
連番が既に与えられている場合に「自然に」ソートする方法を考えます。sort
関数にはkey引数があり、これが使えます。ソート対象――この例では画像ファイル名――を入力としてソートキーなる文字列を作ります。
ソートキーはゼロパディングした文字列でよいでしょう。つまり abc_1.jpg
のソートキーは abc_0001.jpg
で abc_10.jpg
のソートキーは abc_0010.jpg
です。これを実現する手順のひとつは「文字列内の全ての(\d+)
をゼロパディングする」です。re.subn
の第二引数に関数を渡すとコールバックしてくれます:
import re
# 全ての(\d+)をゼロパディングするキー関数
mykey = lambda s: re.subn(r"\d+", lambda mo: f"{mo.group(0):>04}", s)[0]
# 使用例
sorted(["a_1.jpg","a_2.jpg","a_10.jpg"], key=mykey)
上記の手順はひとつの例ですが実装を変えると色々ハマります。例えば「最初に登場する\d+
を抽出して整数値に変換」という整数値をキーとする手順では 1224-2.jpg
, 1224-10.jpg
が意図通りに並ばなくなってしまうでしょう。「登場する数字だけに注目する」という手順では first_ver2.txt
second_ver1.txt
が意図通り並ばなくなってしまうでしょう。などなど。
Tips3: パディング修正
ゼロパディングするように気をつけていたけど、ファイル数が多くなってパディングの桁があふれてしまうことがあります。つまり 01.jpg
, .... 99.jpg
, 100.jpg
という感じですね。こうなってしまうとせっかくパディングしていた意味がなくなってしまいます。07.jpg
, 1001.jpg
, 200.jpg
という順序になりますから。
このように「パディングをあとから修正したい」というユースケースはあります。これを実現してみます。
修正したい時、我々は修正対象について色々知っています。そこで「何桁にパディングしたいのか」「どの場所の数字をパディングしたいのか」という情報を既知とします。例えば 20200401-1.jpg
を 20200401-001.jpg
にしたいので「(0から数えて)1番目の数字を3桁バディングしてね」といった具合です。Tips2で紹介したre.subn
の第二引数に渡す関数を少し変更して「今まで何回書き換えたか?」を覚えておくクロージャにすることで若干の修正ですみます:
# 呼ばれた回数≒書き換え回数を覚えてるクロージャ
def _rewriter(pos, padnum):
fmt = "{:>0" + str(padnum) + "}"
call_count = 0
def ret_f (mo):
nonlocal call_count
call_count += 1
if (call_count - 1) != pos:
return mo.group(0)
return fmt.format(mo.group(0))
return ret_f
def new_name(s, pos=0, padnum=4):
return re.subn(r"\d+", _rewriter(pos, padnum), s)[0]
#使用例
new_name("abc123-34_5.jpg", 1, 6) # abc123-000034_5.jpg
終わりに
よりリーダビリティの高い方法があれば教えてください。他のユースケース・お題もあれば提案してくれるとうれしいです。