1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Tips] 連番と辞書順とパディング

Last updated at Posted at 2021-05-19

課題のありか

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.jpgabc_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.jpg20200401-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

終わりに

よりリーダビリティの高い方法があれば教えてください。他のユースケース・お題もあれば提案してくれるとうれしいです。

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?