如何用 Python 精确提取每行中“扩展名去除后唯一出现”的文件名

本文讲解如何正确处理按行分割的管道分隔文件列表,对每行独立去扩展名后统计原始文件名出现频次,仅保留频次为 1 的原始完整文件名(含扩展名),并用管道符重新拼接输出。

要解决该问题,关键在于理解题意中的“unique per row, ignoring the file extensions”——它不是指去重(distinct),而是指:在去除扩展名后,若某个基础名(如 wub、quux)在整个当前行中只出现一次,则保留其原始带扩展名的完整形式;若出现两次及以上,则整行中所有该基础名的文件均被剔除

原代码错误地使用了 set,导致只记录了去重后的基础名,完全丢失了频次信息和原始文件名映射关系。正确做法是:

  1. 按行处理;
  2. 将每行按 | 分割为文件名列表;
  3. 对每个文件名提取基础名(filename.split('.')[0]);
  4. 使用 collections.Counter 统计每行中各基础名的出现次数;
  5. 遍历原始文件名列表,仅保留那些对应基础名计数为 1 的完整文件名;
  6. 将筛选后的文件名用 | 拼接成新行。

以下是修正后的完整 Python 实现:

from collections import Counter

def find_unique_filenames(text):
    result = []
    for line in text.strip().split('\n'):
        if not line.strip():  # 跳过空行
            continue
        filenames = line.strip().split('|')
        # 提取每文件的基础名(不含扩展名)
        basenames = [f.split('.')[0] for f in filenames]
        # 统计基础名频次
        basename_counts = Counter(basenames)
        # 仅保留基础名出现恰好 1 次的原始文件名
        unique_files = [
            fname for fname in filenames
            if basename_counts[fname.split('.')[0]] == 1
        ]
        result.append('|'.join(unique_files))
    return '\n'.join(result)

# 测试示例
test_input = """foo.mp3|bar.txt|baz.mp3
wub.mp3|wub.mp3|wub.mp3|wub.txt|wub.png
quux.mp3|quux.txt|thud.mp3"""

print(find_unique_filenames(test_input))

✅ 输出结果:

foo.mp3|bar.txt|baz.mp3
thud.mp3

注意事项:

  • 文件名严格为“纯字母数字 + 单个点 + 扩展名”,因此 split('.')[0] 安全可靠,无需正则;
  • 每行独立处理,无状态依赖,适合流式读取大文件(可改用 for line in open(...) 迭代);
  • 若某行所有基础名都重复(如第二行全为 wub),则输出为空行(符合题目逻辑);
  • 保持原始顺序:我们遍历 filenames 列表而非 Counter 的键,确保输出顺序与输入一致。

该方案时间复杂度为 O(n),空间复杂度为 O(m)(m 为单行文件数),完全满足题目约束(≤500 行,≤100 字符/行)。