一文掌握如何在 Python 列表中查找重复项

liftword1个月前 (03-28)技术文章15

在 Python 列表中处理重复项是数据清理、分析和一般编程中的一项常见任务。无论您是处理用户数据、分析文本还是清理数据库记录,了解如何有效地查找和处理重复值都是必不可少的。让我们通过清晰的示例和实际应用来探索不同的方法。

查找重复项的快速解决方案

在深入了解细节之前,让我们先了解一些快速、实用的解决方案。以下是在列表中查找重复项的三种常见方法:

# Using a set to find duplicates
def find_duplicates_set(lst):
    seen = set()
    duplicates = set()
    for item in lst:
        if item in seen:
            duplicates.add(item)
        seen.add(item)
    return list(duplicates)

# Using a dictionary to count occurrences
def find_duplicates_dict(lst):
    count_dict = {}
    for item in lst:
        count_dict[item] = count_dict.get(item, 0) + 1
    return [item for item, count in count_dict.items() if count > 1]

# Using list comprehension with count()
def find_duplicates_count(lst):
    return list(set(x for x in lst if lst.count(x) > 1))

每种方法都有自己的优点 — 让我们探索何时使用每种方法。

了解基于集的方法

基于集合的方法通常是查找重复项的最直接且节省内存的方法:

numbers = [1, 2, 2, 3, 4, 4, 5]
seen = set()
duplicates = set()

for num in numbers:
    if num in seen:
        duplicates.add(num)
    seen.add(num)

print(list(duplicates))  # Output: [2, 4]

此方法的工作原理是:
1. 创建一个空集来跟踪看到的项目
2. 为重复项创建另一个集
3. 将每个项目添加到“已看到”中,并检查我们以前是否遇到过它
4. 如果我们以前见过它,它是重复的

主要优点是集合查找是 O(1),这使得这种方法对于大型列表非常快速。但是,它需要额外的内存来存储 Set。

使用字典了解更多信息

当您需要知道每个项目出现多少次时,字典方法大放异彩:

def find_duplicates_with_counts(lst):
    count_dict = {}
    # Count occurrences
    for item in lst:
        count_dict[item] = count_dict.get(item, 0) + 1
    
    # Filter and format results
    duplicates_info = {
        item: count for item, count in count_dict.items() 
        if count > 1
    }
    return duplicates_info

# Example usage
items = ['apple', 'banana', 'apple', 'cherry', 'banana', 'banana']
result = find_duplicates_with_counts(items)
print(result)  # Output: {'apple': 2, 'banana': 3}

此方法可提供有关重复项的更多详细信息:
- 每个项目出现的次数
- 如果需要,易于修改以跟踪位置
- 适用于数据分析任务

实际示例:清理客户数据

以下是查找重复客户记录的实际示例:

class Customer:
    def __init__(self, id, name, email):
        self.id = id
        self.name = name
        self.email = email
    
    def __eq__(self, other):
        return self.email.lower() == other.email.lower()
    
    def __hash__(self):
        return hash(self.email.lower())

def find_duplicate_customers(customers):
    seen = set()
    duplicates = []
    
    for customer in customers:
        if customer in seen:
            duplicates.append(customer)
        seen.add(customer)
    
    return duplicates

# Example usage
customers = [
    Customer(1, "John Doe", "john@example.com"),
    Customer(2, "Jane Smith", "jane@example.com"),
    Customer(3, "John Doe", "JOHN@EXAMPLE.COM"),  # Duplicate email
]

duplicates = find_duplicate_customers(customers)
for dup in duplicates:
    print(f"Duplicate customer: {dup.name} ({dup.email})")

此示例说明如何:
- 处理不区分大小写的匹配
- 使用自定义对象
- 实施适当的 “__eq__” 和 “__hash__” 方法
- 处理真实世界的数据场景

在嵌套结构中查找重复项

有时您需要在更复杂的数据结构中查找重复项:

def find_nested_duplicates(data):
    from collections import defaultdict
    
    # Helper function to convert lists/tuples to hashable format
    def make_hashable(obj):
        if isinstance(obj, (list, tuple)):
            return tuple(make_hashable(item) for item in obj)
        elif isinstance(obj, dict):
            return tuple(sorted((k, make_hashable(v)) for k, v in obj.items()))
        return obj

    # Track items and their positions
    positions = defaultdict(list)
    
    def process_item(item, path=[]):
        hashable_item = make_hashable(item)
        positions[hashable_item].append(path[:])
        
        if isinstance(item, dict):
            for key, value in item.items():
                process_item(value, path + [key])
        elif isinstance(item, (list, tuple)):
            for i, value in enumerate(item):
                process_item(value, path + [i])
    
    process_item(data)
    return {k: v for k, v in positions.items() if len(v) > 1}

# Example usage
nested_data = {
    'a': [1, 2, [3, 4]],
    'b': [3, 4],
    'c': [1, 2, [3, 4]]
}

duplicates = find_nested_duplicates(nested_data)
for item, locations in duplicates.items():
    print(f"Item {item} found at paths: {locations}")

此高级示例演示如何:
- 处理嵌套结构(列表、字典、元组)
- 跟踪重复项的位置
- 将复杂结构转换为可哈希格式
- 处理递归数据结构

性能注意事项

不同的方法具有不同的性能特征:

import time
import random

def benchmark_duplicate_methods(size):
    # Generate test data
    test_list = [random.randint(1, size//2) for _ in range(size)]
    
    # Test set-based method
    start = time.time()
    duplicates_set = find_duplicates_set(test_list)
    set_time = time.time() - start
    
    # Test dictionary method
    start = time.time()
    duplicates_dict = find_duplicates_dict(test_list)
    dict_time = time.time() - start
    
    # Test count method
    start = time.time()
    duplicates_count = find_duplicates_count(test_list)
    count_time = time.time() - start
    
    return {
        'set_method': set_time,
        'dict_method': dict_time,
        'count_method': count_time
    }

# Run benchmark with different sizes
sizes = [1000, 10000, 100000]
for size in sizes:
    print(f"\nBenchmark with {size} items:")
    results = benchmark_duplicate_methods(size)
    for method, time_taken in results.items():
        print(f"{method}: {time_taken:.4f} seconds")

关键性能洞察:
- 基于集合的方法:最适合重复项较少的大型列表
- 字典方法:当您需要计数或位置时很好
- List.count() 方法:简单但对于大型列表来说速度较慢
- 内存使用量随着 set/dict 方法的唯一项的增加而增加

使用重复项的提示

1. 根据您的需要选择正确的方法:
— 使用集进行简单的重复检测
— 当您需要计数或位置时使用字典
— 对小列表或一次性作使用列表推导

2. 考虑内存使用情况:

# Memory-efficient approach for large lists
def find_duplicates_generator(lst):
    seen = set()
    for item in lst:
        if item in seen:
            yield item
        seen.add(item)

# Usage with large lists
large_list = list(range(1000000)) + [1, 2, 3]
duplicates = list(find_duplicates_generator(large_list))

3. 特殊情况的处理:

def find_duplicates_robust(lst):
    from collections import defaultdict
    counts = defaultdict(list)
    
    # Track both values and positions
    for i, item in enumerate(lst):
        counts[item].append(i)
    
    # Return items with positions where they appear more than once
    return {
        item: positions 
        for item, positions in counts.items() 
        if len(positions) > 1
    }

# Handles special cases like None, float('nan'), etc.
mixed_list = [1, None, 1.0, None, float('nan'), float('nan')]
print(find_duplicates_robust(mixed_list))

本指南介绍了在 Python 列表中查找重复项的基本方面,从基本方法到高级场景。这些示例旨在实用并适应实际情况,而说明不仅可以帮助您了解如何使用这些方法,还可以帮助您了解为什么以及何时使用每种方法。

相关文章

Python 算法 01--二分查找

猜数游戏在程序中预设一个 0-9 之间的整数,让用户通过键盘输入所猜的数,如果大于预设的数,显示 “遗憾,太大了”;小于预设的数,显示 “遗憾,太小了”如此循环,直至猜中该数,显示 “预测 N 次,你...

如何用Python实现二分搜索算法

如何用Python实现二分搜索算法二分搜索(Binary Search)是一种高效的查找算法,适用于在有序数组中快速定位目标值。其核心思想是通过不断缩小搜索范围,每次将问题规模减半,时间复杂度为 (O...

如何对日志文件进行二分查找?二分查找工具timecat介绍

今天我要分享一个头条用于对日志文件进行二分查找的工具:timecat。项目地址是:https://github.com/fanfank/timecat安装方式很简单,只要你装了Python 2,那么可...

Python中如何使用二分法快速查找数据

平时我们会经常找东西,东西少,随便找下就可以找到了,当东西很多很多的时候,找起来就要花大量的时间。假如你平时摆放的东西很整齐、很有规律,那就方便多了,如果你对所有的东西都编号标记,那可能一下就找出来了...

【程序员常用十算法】二分查找法—5分钟掌握

【上期《ChatGPT写的vs我写的——快速排序算法》出来以后,有不少朋友都在感慨未来怎么办啊,是不是初级程序员这些岗位都可以被取代了?我觉得这是一体两面,可以理解为危机(被取代)、也可以理解为机遇(...

Python 常用算法:排序与查找

在 Python 编程的领域中,算法是解决各类问题的核心步骤与方法。排序和查找算法作为基础且常用的算法类型,在数据处理、搜索应用等诸多场景中发挥着关键作用。接下来,让我们深入了解几种常见的排序算法和查...