🙆‍♀️

ファイル名に数字が含まれている場合のソート方法

2021/10/30に公開

概要

python を利用してファイルの一覧を取得した後にファイル名に含まれる数値を使ってソートする方法です。

整理されているファイルであれば特別なことはせずにsortedを使えば良いのですが、今回は整理されていないファイル名を想定してみました。

ファイルの一覧を取得する方法については別扱いにして、取得後のソートについてのみ焦点を当てています。

対象にするファイル名の一覧を以下に記載します。

'sample_1_dummy_1_sub.txt'
'sample_1_dummy_1.txt'
'sample_2_dummy.txt'
'sample_3_dummy.txt'
'sample_11_dummy.txt'
'sample_5_dummy.txt'
'sample_5_dummy_2.txt'
'sample_10_dummy.txt'
'sample_12_dummy_2021-02-11.txt'

単純にsorted(file_names)を実行した場合は以下のようになってしまうため、これを解決します。

sample_10_dummy.txt
sample_11_dummy.txt
sample_12_dummy_2021-02-11.txt
sample_1_dummy_1.txt
sample_1_dummy_1_sub.txt
sample_2_dummy.txt
sample_3_dummy.txt
sample_5_dummy.txt
sample_5_dummy_2.txt

環境

  • macOS: 11.2.3
  • python: 3.9.4

結論

基本的にはsortedを利用することでソートできるのですが、テキスト内に数字が含まれている場合は注意が必要です。

例えば、1.txt11.txtでは後者が前の方に表示されてしまいます。

これを解決するために、文字列内に含まれる数値をゼロパディングして解決しました。

サンプルのコード内では自分でコードを書きましたが、zfill()を使ったほうが簡単にコードを書けます。

今回作成したコードを利用して、ファイル名を一括で変更することも可能になるはずです。
(例では単一責任等を全く考えずに勢いで書いてしまっているのが残念です)

ただ、こんなコードを作るよりもファイルのフォーマットを決めてから運用するのをおすすめします。

コード例

※ とても雑に書いています。

import glob
import os.path
import re

pattern = re.compile(r'\d+')


def find_max_digit(texts):
    result = 0
    for text in texts:
        numbers = pattern.findall(text)
        sorted_digits = sorted([str(number) for number in numbers], key=lambda text: len(text))
        result = len(sorted_digits[-1]) if result < len(sorted_digits[-1]) else result
    return result


def sort_filenames(file_names):
    # 1. 数字の最大桁数を取得する
    max_digit = find_max_digit(file_names)
    # 2. 最大桁数に合わせて数字を置換する
    zero_pad_file_names = []
    for file_name in file_names:
        zero_pad_file_name = file_name
        matches = pattern.finditer(zero_pad_file_name)
        for match in reversed(list(matches)):
            start_pos = match.span()[0]
            end_pos = match.span()[1]
            sub_target = match.string[start_pos:end_pos]
            zero_pad_text = '0' * (max_digit - len(sub_target)) + sub_target
            zero_pad_file_name = zero_pad_file_name[:start_pos] + zero_pad_text + zero_pad_file_name[end_pos:]
        zero_pad_file_names.append({
            'original': file_name,
            'zero_pad': zero_pad_file_name,
        })
    # 3. すべての数字を連結した値を使ってソートする
    s = sorted(zero_pad_file_names, key=lambda x: x.get('zero_pad'))
    return s


if __name__ == '__main__':
    file_names = [
        'sample_1_dummy_1_sub.txt',
        'sample_1_dummy_1.txt',
        'sample_2_dummy.txt',
        'sample_3_dummy.txt',
        'sample_11_dummy.txt',
        'sample_5_dummy.txt',
        'sample_5_dummy_2.txt',
        'sample_10_dummy.txt',
        'sample_12_dummy_2021-02-11.txt',
    ]
    sorted_file_names = sort_filenames(file_names)
    for file_name in sorted_file_names:
        print(file_name)

結果

{'original': 'sample_1_dummy_1.txt', 'zero_pad': 'sample_0001_dummy_0001.txt'}
{'original': 'sample_1_dummy_1_sub.txt', 'zero_pad': 'sample_0001_dummy_0001_sub.txt'}
{'original': 'sample_2_dummy.txt', 'zero_pad': 'sample_0002_dummy.txt'}
{'original': 'sample_3_dummy.txt', 'zero_pad': 'sample_0003_dummy.txt'}
{'original': 'sample_5_dummy.txt', 'zero_pad': 'sample_0005_dummy.txt'}
{'original': 'sample_5_dummy_2.txt', 'zero_pad': 'sample_0005_dummy_0002.txt'}
{'original': 'sample_10_dummy.txt', 'zero_pad': 'sample_0010_dummy.txt'}
{'original': 'sample_11_dummy.txt', 'zero_pad': 'sample_0011_dummy.txt'}
{'original': 'sample_12_dummy_2021-02-11.txt', 'zero_pad': 'sample_0012_dummy_2021-0002-0011.txt'}

これで期待通りの並びでファイル操作ができるようになりました。

Discussion