【问题标题】:remove redundant entries from list of paths从路径列表中删除冗余条目
【发布时间】:2018-03-25 16:54:10
【问题描述】:

我有一个文件和目录的列表。我正在尝试编写一个函数来删除条目,其中还有一个祖先目录的条目。到目前为止我所拥有的似乎有效,但我认为它效率低下,因为它会测试每个文件的完整目录列表。

也许有一个图书馆可以做到这一点,但我找不到它。目的是允许用户选择要上传的文件和目录列表。

从示例中可以看出,目录是条目的子集。我宁愿只提供条目。

import os

def remove_redundant_entries(entries, directories):
    result = []
    for entry in entries:
        # make a copy and successively get the dirname and test it
        partial_path = entry
        found = False
        while partial_path != os.sep:
            partial_path = os.path.dirname(partial_path)
            if partial_path in directories:
                found = True
                break
        if not found:
            result.append(entry)
    return result


entries = [
    "/home/fred/work/f1.txt",
    "/home/fred/work/f2.txt",
    "/home/fred/play/f3.txt",
    "/home/fred/play",
    "/home/jane/dev/f1.txt",
    "/home/jane"]

directories = [
    "/home/fred/play",
    "/home/jane"]

print remove_redundant_entries(entries, directories)

# result:
['/home/fred/work/f1.txt', '/home/fred/work/f2.txt', '/home/fred/play', '/home/jane']

如果您知道某个库或可以提供更好算法的线索,我将不胜感激。同时,我将尝试基于条目排序的方法,因为在列表中祖先应该总是在他们的孩子之前。

编辑:-结果

我通过带有测试集的分析器运行了所有解决方案 10,000 次 - 并添加了一个文件 /home/fred/work/f2.txt.bak 以测试确保常规文件名确实会导致另一个文件被丢弃。

我的原码:1060004 function calls in 0.394 seconds

Stephen Rauch 的回答 - 第一次工作:3250004 function calls in 2.089 seconds

carrdelling 的回答 - 不适用于类似的文件名:480004 function calls in 0.146 seconds

carrdelling 编辑的答案 - 适用于所有情况:680004 function calls in 0.231 seconds

感谢所有贡献的人!

【问题讨论】:

  • 使用可用于字符串对象的startswith 方法会不会更简单。这样,对于每个条目,您可以根据它以任何目录字符串开头来决定是否保留它?作为一种算法并不真正简单,但它删除了 while 循环。
  • 我确实尝试过startswith,但可能存在一个常规文件以另一个常规文件的名称开头的情况:/somepath/foo.txt.bak , /somepath/foo.txt。话虽如此,它可能是一个正则表达式可以做到吗?
  • 您不需要正则表达式 - 请参阅我更新的答案,如果您正确排序输入列表,那么您应该能够同时保留 /somepath/foo.txt.bak/somepath/foo.txt,即使使用 startswith

标签: python algorithm path


【解决方案1】:

如果您对输入的条目列表进行排序,那么问题就更容易了:

def remove_redundant_entries(entries):

    split_entries = sorted(entries)

    valid_entries = []

    for entry in split_entries:

        if any(entry.startswith(p) for p in valid_entries):
            continue
        valid_entries.append(entry)

    return valid_entries

请注意anyshort-circuits 一旦比较为真(除非绝对必要,否则您不会与整个列表进行比较)。此外,由于列表已排序,因此可以保证输出将具有最少数量(和最高级别)的路径。

编辑:

如果您还需要在列表中保留同一文件夹中的多个文件(即使某些文件名是其他文件名的子集),您只需修改排序标准:

split_entries = sorted(entries, key=lambda x: (x.count(os.sep), -len(x)))

这样,树中较高的文件夹会更早出现(因此您最终会得到最少的路径数),但在文件夹中名称较长的文件会更早出现 - 因此它们不会被丢弃因为文件名称较短(类似前缀)。

【讨论】:

  • 是的,长度二次排序解决了文件名相似的问题。我通过分析器运行了我的原始答案和所有答案。我将使用结果编辑问题。谢谢。
【解决方案2】:

您可以使用集合来更有效地查找已经存在的内容,例如:

代码:

def remove_redundant_entries(entries):
    present = set()
    result = []
    for entry in sorted(entries):
        path = os.path.abspath(entry).split(os.sep)
        found = any(
            tuple(path[:i+1]) in present for i in range(len(path)))
        if not found:
            result.append(entry)
            present.add(tuple(path))
    return result

测试代码:

import os

entries = [
    "/home/fred/work/f1.txt",
    "/home/fred/work/f2.txt",
    "/home/fred/play/f3.txt",
    "/home/fred/play",
    "/home/jane/dev/f1.txt",
    "/home/jane"]

result = remove_redundant_entries(entries)
expected = ['/home/fred/work/f1.txt', '/home/fred/work/f2.txt',
            '/home/fred/play', '/home/jane']
assert set(result) == set(expected)

【讨论】:

  • 谢谢!我正准备将其标记为已接受,然后@carrdeling 编辑了他的答案,使其运行良好但速度更快。
猜你喜欢
  • 1970-01-01
  • 2012-07-23
  • 2018-01-19
  • 1970-01-01
  • 2023-01-31
  • 1970-01-01
  • 2016-04-02
  • 2021-03-20
  • 2016-07-25
相关资源
最近更新 更多