86040


pythonのosライブラリのos.listdir()で格納した以下のようなファイルリストを、sortした後に連続した数字になるようにファイル名を変更したいです。

[code]

    sample1.png 
    sample15.png    
    sample21.png    
    sample27.png    
    sample31.png    
    sample35.gif    
    sample41.jpg    
    sample8.png
    sample13.png    
    sample18.png
    
[/code]

この例では全て数字の前をsampleとしているため単純なsort()でも順番を整えることができますが、実際にはいろんなファイル名が考えられるため、数字の方でソートしなければなりません。（ファイル名には必ず数字がつきます）つまり、上のリストを：

[code]

    sample1.png
    sample8.png 
    sample13.png
    sample15.png  
    sample18.png  
    sample21.png    
    sample27.png    
    sample31.png    
    sample35.gif    
    
[/code]

のように数字順にした後、連続した番号にするため最終的に以下のようなファイル名に変更したいです。

[code]

    sample1.png
    sample2.png 
    sample3.png
    sample4.png  
    sample5.png  
    sample6.png    
    sample7.png    
    sample8.png    
    sample9.gif
    
[/code]

幸い、ファイル名の変更はos.rename()で可能ですが、その前にsortしないといけません。  
以下の二つを試していますが、どちらもIndexError: list index out of rangeになってしまいます。  
どなたか良い方法をご存知ないですか？

[code]

    files = os.listdir(path)
    def sort_by_num(test_string):
            return list(map(int, re.findall(r'\d+', test_string)))[0]
    files.sort(key=sort_by_num)
    
[/code]

[code]

    files.sort(key=lambda file : list(
            map(int, re.findall(r'\d+', file)))[0])
    
[/code]


CC BY-SA 4.0
2022-01-31T08:18:21.917
86034
2022-01-31T19:45:35.203
35503
question
python python3 sort
文字列の数字でソートした後、連続した数字になるように文字列を書き直すには。


ファイル名（正確には拡張子を除く
basename）の数字以外の部分は何であれ，後ろの数字の部分だけを取り出してソートして再度1から順番に採番して，元の名前に新たに採番した番号を付けた名前にリネームするということでよいのでしょうか．

基本的に「ファイル名から数字部分を取り出す」「ソートする」「リネームする」を同時にやろうとせずに，順番にやった方がいいのではないかと思います．その方針で実装したコード例を以下に示します．

[code]

    import os
    import re
    
    
    def parse_filename(fn):
        """Parse a filename into (original, base, num, ext)"""
        m = re.match(r'(\D*)(\d*)\.(.*)$', fn)
        return (m[0], m[1], int(m[2]), m[3])
    
    
    def main():
        path = 'path/to/somewhere'
        filenames = os.listdir(path)
    
        parsed_filenames = [parse_filename(fn) for fn in filenames]
    
        for i, m in enumerate(sorted(parsed_filenames, key=lambda x: x[2])):
            os.rename(path + '/' + m[0],
                      path + '/' + m[1] + str(i + 1) + '.' + m[3])
    
    
    if __name__ == '__main__':
        main()
    
[/code]

このコードでやっていることを念のため簡単に説明しておくと，まず `os.listdir(path)` で取得した各ファイル名を
`parse_filename()` 関数で以下の要素を持つタプルに分解します．

  * オリジナルのファイル名 (str)
  * basename の非数字部分 (str)
  * basename の数字部分 (int)
  * 拡張子 (str)

その上で，basename の数字部分を用いて普通にソートし，最後に for 文の中で目的のリネームを実行しています．


CC BY-SA 4.0
2022-01-31T10:10:26.520
86040
2022-01-31T10:17:46.223
2022-01-31T10:17:46.223
27047
27047
86034
answer


> ファイル名には必ず数字がつきます
>
> 以下の二つを試していますが、どちらもIndexError: list index out of rangeになってしまいます。

`IndexError: list index out of range`
が発生するということは、ファイル名に数字を含まないものが存在する、という事になります(`re.findall(r'\d+', file)` が empty
list を返している)。

[code]

    import re
    
    files = [
      'foo3.png',
      'bar2.jpg',
      'baz1.gif',
      'bitmap.bmp',
    ]
    
    files.sort(key=lambda file : list(
            map(int, re.findall(r'\d+', file)))[0])
    
[/code]

**実行結果**

[code]

    $ python test.py
    Traceback (most recent call last):
      File "test.py", line 10, in <module>
        files.sort(key=lambda file : list(
      File "test.py", line 10, in <lambda>
        files.sort(key=lambda file : list(
    IndexError: list index out of range
    
[/code]

なので、ファイル名に数字を含まないものはリストの最後に配置する様にします。  
※ `Walrus operator`(セイウチ演算子)を使用していますので Python 3.8 よりも以前のバージョンでは動作しません

[code]

    import re
    import math
    
    files = [
      'dismays1.png',
      'pellet15.png',
      'emulsifies21.png',
      'classicist27.png',
      'dysfunctions31.png',
      'quadrangle35.gif',
      'bitmap.bmp',
      'cargos41.jpg',
      'wheels8.png',
      'heartlessly13.png',
      'attains18.png',
    ]
    
    files.sort(key=lambda x: int(n[0]) if (n:=re.findall(r'\d+', x)) else math.inf)
    
    from pprint import pprint
    pprint(files)
    
    #
    ['dismays1.png',
     'wheels8.png',
     'heartlessly13.png',
     'pellet15.png',
     'attains18.png',
     'emulsifies21.png',
     'classicist27.png',
     'dysfunctions31.png',
     'quadrangle35.gif',
     'cargos41.jpg',
     'bitmap.bmp']
    
[/code]


CC BY-SA 4.0
2022-01-31T10:14:45.797
86042
2022-01-31T19:45:35.203
2022-01-31T19:45:35.203
47127
47127
86034
answer