【问题标题】:Find duplicate filenames, and only keep newest file using python查找重复的文件名,并且只使用 python 保留最新的文件
【发布时间】:2012-10-30 06:52:55
【问题描述】:

我有 +20 000 个文件,如下所示,都在同一个目录中:

8003825.pdf
8003825.tif
8006826.tif

如何在忽略文件扩展名的情况下找到所有重复的文件名。

澄清:我指的是重复文件是具有相同文件名的文件,而忽略了文件扩展名。我确实关心文件是否不是 100% 相同(例如 hashsize 或类似的东西)

例如:

"8003825" appears twice

然后查看每个重复文件的元数据,只保留最新的。

与这篇文章类似:

Keep latest file and delete all other

我想我必须创建一个所有文件的列表,检查文件是否已经存在。如果是这样,那么使用 os.stat 来确定修改日期?

我有点担心将所有这些文件名加载到内存中。并且想知道是否有更 Pythonic 的做事方式......

Python 2.6 视窗 7

【问题讨论】:

  • 我认为您不必担心将 20,000 甚至 200,000 个文件名加载到内存中。

标签: python list duplicates


【解决方案1】:

您可以使用O(n) 复杂性来做到这一点。带有sort 的解决方案具有O(n*log(n)) 的复杂性。

import os
from collections import namedtuple

directory = #file directory
os.chdir(directory)

newest_files = {}
Entry = namedtuple('Entry',['date','file_name'])

for file_name in os.listdir(directory):
    name,ext = os.path.splitext(file_name)
    cashed_file = newest_files.get(name)
    this_file_date = os.path.getmtime(file_name)
    if cashed_file is None:
        newest_files[name] = Entry(this_file_date,file_name)
    else:
        if this_file_date > cashed_file.date: #replace with the newer one
            newest_files[name] = Entry(this_file_date,file_name)

newest_files 是一个字典,具有不带扩展名的文件名作为键,命名元组的值包含文件完整文件名和修改日期。如果遇到的新文件在字典中,则将其日期与存储在字典中的日期进行比较,并在必要时进行替换。

最后你有一本包含最新文件的字典。

然后您可以使用此列表执行第二遍。请注意,字典中的查找复杂度为O(1)。所以在字典中查找所有n文件的总体复杂度是O(n)

例如,如果您只想保留最新的同名文件并删除其他文件,可以通过以下方式实现:

for file_name in os.listdir(directory):
    name,ext = os.path.splitext(file_name)
    cashed_file_name = newest_files.get(name).file_name
    if file_name != cashed_file_name: #it's not the newest with this name
        os.remove(file_name)

正如 cmets 中的 Blckknght 所建议的,您甚至可以避免第二遍,并在遇到新文件时立即删除旧文件,只需添加一行代码:

    else:
        if this_file_date > cashed_file.date: #replace with the newer one
            newest_files[name] = Entry(this_file_date,file_name)
            os.remove(cashed_file.file_name) #this line added

【讨论】:

  • +1 不错!它只在内存中保存它需要的文件,并且是 O(n)。
  • 你甚至可以只做一次传球。每次将文件的年龄与缓存的年龄进行比较时,立即删除较旧的文件。在单次传递结束时,您将只剩下每个名称的最新文件。
【解决方案2】:

首先,获取文件名列表并对其进行排序。这会将所有重复项放在一起。

然后,去掉文件扩展名并与邻居比较,os.path.splitext()itertools.groupby() 可能在这里有用。

将重复项分组后,使用os.stat() 选择您要保留的一个。

最后你的代码可能看起来像这样:

import os, itertools

files = os.listdir(base_directory)
files.sort()
for k, g in itertools.groupby(files, lambda f: os.path.splitext(f)[0]):
     dups = list(g)
     if len(dups) > 1:
         # figure out which file(s) to remove

您不必担心这里的内存,您正在查看大约几兆字节的内容。

【讨论】:

    【解决方案3】:

    对于文件名计数器,您可以使用 defaultdict 来存储每个文件出现的次数:

    import os
    from collections import defaultdict
    
    counter = defaultdict(int)
    for file_name in file_names:
       file_name = os.path.splitext(os.path.basename(file_name))[0]
       counter[file_name] += 1
    

    【讨论】:

    • 一个目录下的文件是要排序的,他为什么要统计呢?
    • @kreativitea 因为引用自:docs.python.org/2/library/os.html#os.listdir - Return a list containing the names of the entries in the directory given by path. The list is in arbitrary order. It does not include the special entries '.' and '..' even if they are present in the directory.
    • 是的,但我的意思是你会使用sorted(os.listdir(directory)),并避免创建一个全新的字典。遍历排序列表、创建同名文件列表、扫描组和重置链似乎效率更高。
    猜你喜欢
    • 1970-01-01
    • 2017-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多