python去重列表中相同的字典元素
文章目录
- python去重列表中相同的字典元素
- 一.知识点
- 二.代码
- |代码1
- |问题
- |代码2
- 三.分析总结
- 1、分析
- 2、总结
- 四.后续
- 代码
- 知识点
- 代码流程
- 问题
- 总结
- 总结
一.知识点
data_list = [{“a”: 1, “b”: 2}, {“a”: 2, “b”: 3}, {“a”: 1, “b”: 2}, {“a”: 3, “b”: 4}, {“a”: 2, “b”: 3}, {“a”: 1, “b”: 2}]
萌新会犯的错误:使用集合来去重
print(set(data_list))
会报错,因为字典是不可哈希的类型,不能直接用于集合的创建。如果要将字典元素去重,可以先将字典转化为元组再使用集合去重。
在Python中,集合是可变的数据类型,因此不是可哈希的。集合的元素必须是可哈希的类型。如果集合中有不可哈希的元素,例如列表或字典,会引发TypeError错误。
在 Python 中,可哈希的类型包括:
不可变类型:例如数字、字符串、元组等,它们一旦创建,其值就不能被修改;
可变类型但是不包含可变类型的元素:例如 frozenset 等;
用户自定义的对象,只要实现了 hash 和 eq 方法,使得对象在比较时可哈希即可。
需要注意的是,可哈希的类型必须实现了 hash 方法,使得相同的值具有相同的哈希值,这是哈希表能够正确工作的前提。
二.代码
|代码1
那就创建一个新列表,循环遍历嵌套着字典数据的列表,判断字典元素是否存在,若不在,就添加进去
def list_dict_duplicate_removal1(data_list):
unique_list = []
for data in data_list:
if data not in unique_list:
unique_list.append(data)
return unique_list
|问题
当我在编写项目时,数据集比较大,有19万多行,如下图,元素即字典的键值对数量有14对
|代码2
当数据集较大时,每次都需要遍历整个结果列表,查看当前字典是否已经存在于结果列表中,因此时间复杂度很高,导致程序运行变慢。一个更好的解决方案是使用哈希表来快速判断字典是否已经存在于结果列表中,使用哈希表实现去重的示例代码:
def list_dict_duplicate_removal2(data_list):
result = []
seen = set()
for d in data_list:
t = tuple(sorted(d.items()))
if t not in seen:
seen.add(t)
result.append(d)
return result
处理数据所用的时间明显很短,如下图
在这个实现中,我们使用一个集合 seen 来记录已经出现过的字典。每个字典都被转换为一个元组,元组中的元素是字典项的键值对,使用 sorted() 函数可以确保元组中的键值对按照字典序排序,这样即使字典中的键值对顺序不同,元组也能够保持一致。然后将这个元组作为哈希表的键,将字典本身作为值,存入结果列表中。
三.分析总结
1、分析
待去重的字典中的键值对数量较小,那么两段代码的差异可能不太明显。则使用第一段代码的处理速度可能会更快,因为它仅仅是简单地遍历了列表并将不重复的元素添加到另一个列表中。
而第二段代码中,每个字典都被转换成一个元组并排序,这会比第一段代码中的简单遍历更加复杂和耗时。此外,第二段代码中还需要维护一个额外的集合来跟踪已经出现过的元素,这也会增加代码的复杂性和开销。不过,如果待去重的字典中的键值对数量较小,那么两段代码的差异可能不太明显。
在实际使用中,应该根据具体情况选择合适的方法,对于复杂度比较高的数据结构,考虑使用第二段代码进行去重。
2、总结
其它知识点:
(1)哈希表相关知识
哈希表是一种非常常用和高效的数据结构,它可以帮助我们快速地在大量数据中查找、插入或删除元素。为了更好地理解哈希表的实现原理,可以将它比喻成一个大的盒子,盒子里面有很多抽屉,每个抽屉可以放置一个元素。在哈希表中,我们通常使用关键字来访问和操作元素,就像是在盒子中寻找某一个物品一样。为了确定一个元素的位置,我们需要将其关键字通过一个函数进行映射,从而得到该元素所在的抽屉。这个函数就是哈希函数。
哈希表是一种数据结构,可以通过哈希函数将元素映射到数组的一个位置。哈希函数将任意长度的输入(例如字典对象)映射到固定长度的输出(例如整数),输出通常称为哈希值。哈希表通过将元素的哈希值作为下标来访问数组中的元素。因此,在哈希表中查找元素的时间复杂度是O(1),也就是说,它具有非常高的查找效率。
哈希函数将关键字映射到一个整数值,然后通过将该整数值与数组的长度取余数,得到该元素在数组中的位置。如果不同的关键字映射到了同一个位置,就称为哈希冲突。为了解决哈希冲突,通常的做法是使用链表等数据结构来存储同一个位置上的多个元素。这就相当于在同一个抽屉里面放置了多个物品,通过链表的方式可以将它们区分开来。
使用哈希表可以实现O(1)的查找、插入和删除操作,也就是说,不论哈希表中有多少个元素,操作所需要的时间都是固定的,这样就大大提高了程序的效率。但是需要注意的是,哈希函数的设计需要具有良好的性质,这样才能避免哈希冲突的发生,从而保证哈希表的效率和准确性。
当我们需要在一个很大的数据集合中查找某个元素时,传统的做法是遍历整个集合,一个一个地比较元素,这种做法的时间复杂度是O(n),其中n是集合中元素的数量。当集合很大时,这种做法就会非常耗时。
(2)第二段代码在哈希表的应用
哈希表是 seen,它是一个 Python 内置的 set 类型,用于存储不重复的元素。
哈希函数是 tuple(sorted(d.items())),它将字典对象 d 转换为一个元组,并按照元素顺序排序,以确保对于相同的键值对,生成的元组是相同的。
哈希值是 t,它是由哈希函数生成的元组。哈希函数将任意长度的字典对象映射到固定长度的元组。
这里的哈希函数使用了 Python 内置的 tuple 函数和 sorted 函数。函数中的第 4 行可以体现哈希函数将任意长度的输入映射到固定长度的输出。
四.后续
代码
分析下面的代码(当时着急,粘贴了一份网上代码来处理数据),回顾一下redece()函数和lambda表达式
from functools import reduce
def list_dict_duplicate_removal3(data_list):
run_function = lambda x, y: x if y in x else x + [y]
return reduce(run_function, [[],] + data_list)
知识点
(1)reduce()函数
reduce()函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
(2)lambda表达式:匿名函数
匿名函数lambda x: x * x实际上就是:
def f(x):return x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数。
代码流程
该函数引用了 reduce() 函数和 lambda 表达式:
from functools import reduce
reduce() 函数是 Python 2.x 中的内置函数,在 Python 3.x 中被移至 functools 模块。它可以对序列类型(如列表、元组)中的元素进行迭代处理,并返回一个最终结果。该函数接收两个参数,第一个参数是函数对象,第二个参数是迭代器(可迭代对象)。
lambda 表达式是一种匿名函数,即没有名称的函数。它可以接收任意多个参数,但只能有一个表达式,且返回值就是该表达式的结果。lambda 表达式通常用于定义简单的函数,并将其作为参数传递给其他函数。
函数中定义了一个 run_function 的 lambda 函数,用来对比和过滤列表中的元素:
run_function = lambda x, y: x if y in x else x + [y]
该 lambda 函数接收两个参数 x 和 y,其中 x 是经过比对后已经保留的元素,y 是需要比对的新元素。该函数的主要功能是:如果 y 在 x 中,则直接返回 x,否则将 y 添加到 x 中。这个过程通过条件表达式 x if y in x else x + [y] 来实现。
该 lambda 函数接收两个参数 x 和 y,其中 x 是经过比对后已经保留的元素,y 是需要比对的新元素。该函数的主要功能是:如果 y 在 x 中,则直接返回 x,否则将 y 添加到 x 中。这个过程通过条件表达式 x if y in x else x + [y] 来实现。
函数中利用 reduce() 函数和 lambda 表达式对列表中的元素进行迭代处理,保留不重复的元素:
return reduce(run_function, [[],] + data_list)
reduce() 函数的第一个参数是一个函数对象,第二个参数是一个迭代器对象。迭代器对象的第一个元素是一个空列表,表示初始状态。列表 data_list 中的每个元素都会依次作为参数传递给 run_function 函数进行比对和过滤。最终返回的是一个去重后的列表,其中包含了原列表中的所有元素,但不包含重复的元素。
问题
当数据量过大时,这段代码可能会运行贼慢。原因是因为它使用了两个嵌套的循环,其中包含了一些操作,这些操作在大量数据的情况下会变得非常耗时。
如下图,是这个代码处理我的数据集时所用的时间
这段代码没有显式使用两个嵌套的循环,但是使用了reduce函数,reduce函数本质上是一个迭代器,每次遍历数据集中的一个元素,而这里的数据集是一个列表,所以每次遍历的元素是一个字典。
在reduce函数的运行过程中,使用了lambda函数作为参数,这个lambda函数中又使用了in关键字来判断一个元素是否在列表中已经出现过,这里的判断操作会导致内部的嵌套循环。因此,虽然没有显式使用两个嵌套的循环,但实际上reduce函数内部实现了两层循环,所以当数据集过大时,程序的运行速度会变得非常慢。
总结
reduce函数的运行过程中,使用了lambda函数作为参数,这个lambda函数中又使用了in关键字来判断一个元素是否在列表中已经出现过,这里的判断操作会导致内部的嵌套循环。因此,虽然没有显式使用两个嵌套的循环,但实际上reduce函数内部实现了两层循环,所以当数据集过大时,程序的运行速度会变得非常慢。
总结
在编写代码时,最好避免使用嵌套循环从而造成大量数据集的笛卡儿积