ลบ dict ที่ซ้ำกันในรายการใน Python


153

ฉันมีรายการ dicts และฉันต้องการลบ dicts ที่มีคู่คีย์และค่าที่เหมือนกัน

สำหรับรายการนี้: [{'a': 123}, {'b': 123}, {'a': 123}]

ฉันต้องการคืนสิ่งนี้: [{'a': 123}, {'b': 123}]

ตัวอย่างอื่น:

สำหรับรายการนี้: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

ฉันต้องการคืนสิ่งนี้: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]


คุณช่วยบอกเราเพิ่มเติมเกี่ยวกับปัญหาจริงที่คุณพยายามแก้ไขได้หรือไม่ ดูเหมือนว่าจะมีปัญหาแปลก ๆ
gfortune

ฉันกำลังรวมรายการ dicts สองสามรายการและมีรายการซ้ำกัน ดังนั้นฉันต้องลบรายการที่ซ้ำกันเหล่านั้น
Brenden

ฉันพบวิธีแก้ปัญหาในstackoverflow.com/questions/480214/…ในคำตอบโดยไม่ต้องใช้set()
Sebastian Wagner

คำตอบ:


242

ลองสิ่งนี้:

[dict(t) for t in {tuple(d.items()) for d in l}]

กลยุทธ์คือการแปลงรายการพจนานุกรมเป็นรายการของสิ่งอันดับที่สิ่งอันดับมีรายการของพจนานุกรม ตั้งแต่อันดับที่สามารถถกคุณสามารถลบรายการที่ซ้ำกันใช้set(โดยใช้ความเข้าใจชุดที่นี่ทางเลือกหลามที่มีอายุมากกว่าจะเป็นset(tuple(d.items()) for d in l)) และหลังจากที่สร้างใหม่อีกครั้งจากพจนานุกรม tuples dictกับ

ที่อยู่:

  • l เป็นรายการดั้งเดิม
  • d เป็นหนึ่งในพจนานุกรมในรายการ
  • t เป็นหนึ่งในสิ่งอันดับที่สร้างขึ้นจากพจนานุกรม

แก้ไข: หากคุณต้องการรักษาการสั่งซื้อหนึ่งซับด้านบนจะไม่ทำงานเนื่องจากsetจะไม่ทำเช่นนั้น อย่างไรก็ตามด้วยโค้ดสองสามบรรทัดคุณสามารถทำสิ่งต่อไปนี้:

l = [{'a': 123, 'b': 1234},
        {'a': 3222, 'b': 1234},
        {'a': 123, 'b': 1234}]

seen = set()
new_l = []
for d in l:
    t = tuple(d.items())
    if t not in seen:
        seen.add(t)
        new_l.append(d)

print new_l

ตัวอย่างผลลัพธ์:

[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

หมายเหตุ: ตามที่ระบุโดย @alex อาจเป็นไปได้ว่าพจนานุกรมสองเล่มที่มีคีย์และค่าเหมือนกันไม่ส่งผลให้เกิด tuple เดียวกัน ที่อาจเกิดขึ้นหากพวกเขาผ่านการเพิ่ม / ลบคีย์ประวัติอื่น หากเป็นกรณีของปัญหาของคุณให้ลองจัดเรียงd.items()ตามที่เขาแนะนำ


35
วิธีแก้ปัญหาที่ดี แต่มีข้อผิดพลาด: d.items()ไม่รับประกันว่าจะส่งคืนองค์ประกอบตามลำดับเฉพาะ คุณควรทำtuple(sorted(d.items()))เพื่อให้แน่ใจว่าคุณจะไม่ได้รับสิ่งอันดับที่แตกต่างกันสำหรับคู่คีย์ - ค่าเดียวกัน
alexis

@alexis ฉันทำการทดสอบสองสามครั้งและคุณพูดถูก หากมีการเพิ่มคีย์จำนวนมากในระหว่างและลบในภายหลังนั่นอาจเป็นกรณี ขอบคุณมากสำหรับความคิดเห็นของคุณ
jcollado

เย็น. ฉันได้เพิ่มการแก้ไขในคำตอบของคุณเพื่อประโยชน์ของผู้อ่านในอนาคตที่อาจไม่ได้อ่านบทสนทนาทั้งหมด
alexis

2
หมายเหตุสิ่งนี้จะไม่ทำงานหากคุณโหลดรายการ dicts จากjsonโมดูลที่ฉันทำ
Dhruv Ghulati

2
นี่เป็นวิธีแก้ปัญหาที่ถูกต้องในกรณีนี้ แต่จะไม่ทำงานในกรณีของพจนานุกรมที่ซ้อนกัน
Lorenzo Belli

51

อีกหนึ่งซับตามรายการความเข้าใจ:

>>> d = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> [i for n, i in enumerate(d) if i not in d[n + 1:]]
[{'b': 123}, {'a': 123}]

ที่นี่เนื่องจากเราสามารถใช้dictการเปรียบเทียบเราเก็บองค์ประกอบที่ไม่ได้อยู่ในรายการเริ่มต้นเท่านั้น (ความคิดนี้สามารถเข้าถึงได้ผ่านดัชนีnเท่านั้นดังนั้นการใช้งานenumerate)


2
นอกจากนี้ยังใช้กับรายการพจนานุกรมที่ประกอบด้วยรายการเมื่อเปรียบเทียบกับคำตอบแรก
gbozee

1
สิ่งนี้จะทำงานเมื่อคุณอาจมีประเภท unhashable เป็นค่าในพจนานุกรมของคุณซึ่งแตกต่างจากคำตอบด้านบน
Steve Rossiter

1
ที่นี่มีวัตถุประสงค์เพื่อลบค่าที่ซ้ำกันไม่สำคัญดูรหัสของคำตอบนี้
Jamil Noyda

นี่เป็นรหัสที่ไม่มีประสิทธิภาพมาก if i not in d[n + 1:]วนซ้ำทั้งรายการ dicts (จากnแต่เพียงครึ่งหนึ่งของจำนวนการดำเนินการทั้งหมด) และคุณกำลังทำการตรวจสอบทุกองค์ประกอบในพจนานุกรมของคุณเพื่อให้รหัสนี้มีความซับซ้อนของเวลา O (n ^ 2)
Boris

ไม่ทำงานสำหรับพจนานุกรมที่มีพจนานุกรมเป็นค่า
Roko Mijic

22

คำตอบอื่น ๆ จะไม่ทำงานหากคุณใช้งานพจนานุกรมที่ซ้อนกันเช่นวัตถุ JSON ที่ถูกทำการ deserialized สำหรับกรณีนี้คุณสามารถใช้:

import json
set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
X = [json.loads(t) for t in set_of_jsons]

1
ที่ดี! เคล็ดลับคือวัตถุ dict ไม่สามารถเพิ่มโดยตรงไปยังชุดมันจะต้องถูกแปลงเป็นวัตถุ json โดยการถ่ายโอนข้อมูล ()
Reihan_amn

18

หากใช้แพ็คเกจของบุคคลที่สามจะไม่เป็นไรคุณสามารถใช้iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> l = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> list(unique_everseen(l))
[{'a': 123}, {'b': 123}]

มันรักษาลำดับของรายการต้นฉบับและ ut ยังสามารถจัดการรายการ unhashable เช่นพจนานุกรมโดยการย้อนกลับบนอัลกอริทึมช้าลง ( O(n*m)ซึ่งnเป็นองค์ประกอบในรายการต้นฉบับและmองค์ประกอบที่ไม่ซ้ำกันในรายการต้นฉบับแทนO(n)) ในกรณีที่ทั้งคีย์และค่าสามารถแฮชได้คุณสามารถใช้keyอาร์กิวเมนต์ของฟังก์ชันนั้นเพื่อสร้างรายการที่แฮชได้สำหรับ "uniqueness-test" (เพื่อให้ทำงานได้O(n))

ในกรณีของพจนานุกรม (ซึ่งเปรียบเทียบโดยไม่ขึ้นกับลำดับ) คุณจำเป็นต้องแมปมันกับโครงสร้างข้อมูลอื่นที่เปรียบเทียบเช่นนั้นตัวอย่างเช่นfrozenset:

>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]

โปรดทราบว่าคุณไม่ควรใช้tupleวิธีการง่ายๆ(โดยไม่ต้องเรียงลำดับ) เพราะพจนานุกรมที่เท่ากันไม่จำเป็นต้องมีคำสั่งเดียวกัน (แม้ใน Python 3.7 ซึ่งคำสั่งแทรก - ไม่ใช่คำสั่งที่แน่นอน - รับประกัน):

>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False

และแม้กระทั่งการเรียงลำดับ tuple อาจไม่ทำงานหากคีย์ไม่สามารถจัดเรียงได้:

>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'

เกณฑ์มาตรฐาน

ฉันคิดว่ามันอาจมีประโยชน์ที่จะเห็นว่าประสิทธิภาพของวิธีการเหล่านี้เปรียบเทียบกันอย่างไรฉันจึงทำเกณฑ์มาตรฐานเล็กน้อย กราฟเปรียบเทียบเป็นเวลาเทียบกับขนาดรายการตามรายการที่ไม่มีการทำซ้ำ (ซึ่งถูกเลือกโดยพลการรันไทม์จะไม่เปลี่ยนแปลงอย่างมีนัยสำคัญหากฉันเพิ่มบางส่วนที่ซ้ำกัน) มันเป็นพล็อตบันทึกการใช้งานเพื่อให้ครอบคลุมช่วงทั้งหมด

เวลาที่แน่นอน:

ป้อนคำอธิบายรูปภาพที่นี่

เวลาที่สัมพันธ์กับวิธีที่เร็วที่สุด:

ป้อนคำอธิบายรูปภาพที่นี่

วิธีที่สองจากthefourtheyeเร็วที่สุดที่นี่ unique_everseenวิธีการที่มีkeyฟังก์ชั่นที่อยู่ในสถานที่ที่สอง แต่มันเป็นวิธีที่เร็วที่สุดที่รักษาสั่งซื้อ วิธีอื่น ๆ จากjcolladoและthefourtheyeนั้นเกือบจะเร็วเหมือนกัน วิธีการที่ใช้unique_everseenโดยไม่ต้องคีย์และการแก้ปัญหาจากเอ็มมานูและScorpilช้ามากสำหรับรายการอีกต่อไปและมีพฤติกรรมเลวร้ายมากแทนO(n*n) stpk s approach ด้วยไม่ใช่แต่ช้ากว่าวิธีที่คล้ายกันมากO(n)jsonO(n*n)O(n)

รหัสในการทำซ้ำมาตรฐาน:

from simple_benchmark import benchmark
import json
from collections import OrderedDict
from iteration_utilities import unique_everseen

def jcollado_1(l):
    return [dict(t) for t in {tuple(d.items()) for d in l}]

def jcollado_2(l):
    seen = set()
    new_l = []
    for d in l:
        t = tuple(d.items())
        if t not in seen:
            seen.add(t)
            new_l.append(d)
    return new_l

def Emmanuel(d):
    return [i for n, i in enumerate(d) if i not in d[n + 1:]]

def Scorpil(a):
    b = []
    for i in range(0, len(a)):
        if a[i] not in a[i+1:]:
            b.append(a[i])

def stpk(X):
    set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
    return [json.loads(t) for t in set_of_jsons]

def thefourtheye_1(data):
    return OrderedDict((frozenset(item.items()),item) for item in data).values()

def thefourtheye_2(data):
    return {frozenset(item.items()):item for item in data}.values()

def iu_1(l):
    return list(unique_everseen(l))

def iu_2(l):
    return list(unique_everseen(l, key=lambda inner_dict: frozenset(inner_dict.items())))

funcs = (jcollado_1, Emmanuel, stpk, Scorpil, thefourtheye_1, thefourtheye_2, iu_1, jcollado_2, iu_2)
arguments = {2**i: [{'a': j} for j in range(2**i)] for i in range(2, 12)}
b = benchmark(funcs, arguments, 'list size')

%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('ggplot')
mpl.rcParams['figure.figsize'] = '8, 6'

b.plot(relative_to=thefourtheye_2)

เพื่อความสมบูรณ์นี่คือเวลาสำหรับรายการที่มีรายการซ้ำเท่านั้น:

# this is the only change for the benchmark
arguments = {2**i: [{'a': 1} for j in range(2**i)] for i in range(2, 12)}

ป้อนคำอธิบายรูปภาพที่นี่

การกำหนดเวลาไม่เปลี่ยนแปลงอย่างมีนัยสำคัญยกเว้นเมื่อunique_everseenไม่มีkeyฟังก์ชั่นซึ่งในกรณีนี้เป็นวิธีแก้ปัญหาที่เร็วที่สุด แต่นั่นเป็นเพียงกรณีที่ดีที่สุด (เพื่อไม่ได้เป็นตัวแทน) สำหรับฟังก์ชั่นที่มีค่า unhashable เพราะ runtime มันขึ้นอยู่กับปริมาณของค่าที่ไม่ซ้ำกันในรายการ: O(n*m)ซึ่งในกรณีนี้เป็นเพียง 1 O(n)จึงทำงานใน


Disclaimer: iteration_utilitiesผมผู้เขียน


15

บางครั้งลูปแบบเก่าก็ยังมีประโยชน์ รหัสนี้ยาวกว่า jcollado เล็กน้อย แต่อ่านง่ายมาก:

a = [{'a': 123}, {'b': 123}, {'a': 123}]
b = []
for i in range(0, len(a)):
    if a[i] not in a[i+1:]:
        b.append(a[i])

0ในrange(0, len(a))ไม่จำเป็น
Juan Antonio

12

หากคุณต้องการรักษาลำดับไว้คุณก็สามารถทำได้

from collections import OrderedDict
print OrderedDict((frozenset(item.items()),item) for item in data).values()
# [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

หากคำสั่งซื้อไม่สำคัญคุณสามารถทำได้

print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

หมายเหตุ: ใน python 3 แนวทางที่สองของคุณให้dict_valuesเอาต์พุตที่ไม่สามารถจัดลำดับได้แทนรายการ คุณต้องโยนทุกสิ่งในรายการอีกครั้ง list(frozen.....)
saran3h

12

หากคุณกำลังใช้หมีแพนด้าในเวิร์กโฟลว์ของคุณตัวเลือกหนึ่งคือการป้อนรายการพจนานุกรมโดยตรงไปยังตัวpd.DataFrameสร้าง จากนั้นใช้drop_duplicatesและto_dictวิธีการสำหรับผลลัพธ์ที่ต้องการ

import pandas as pd

d = [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

d_unique = pd.DataFrame(d).drop_duplicates().to_dict('records')

print(d_unique)

[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

3

ไม่ใช่คำตอบที่เป็นสากลแต่ถ้ารายการของคุณถูกเรียงลำดับด้วยคีย์เช่นนี้:

l=[{'a': {'b': 31}, 't': 1},
   {'a': {'b': 31}, 't': 1},
 {'a': {'b': 145}, 't': 2},
 {'a': {'b': 25231}, 't': 2},
 {'a': {'b': 25231}, 't': 2}, 
 {'a': {'b': 25231}, 't': 2}, 
 {'a': {'b': 112}, 't': 3}]

วิธีแก้ปัญหาก็ง่ายเหมือน:

import itertools
result = [a[0] for a in itertools.groupby(l)]

ผลลัพธ์:

[{'a': {'b': 31}, 't': 1},
{'a': {'b': 145}, 't': 2},
{'a': {'b': 25231}, 't': 2},
{'a': {'b': 112}, 't': 3}]

ทำงานร่วมกับพจนานุกรมที่ซ้อนกันและ (แน่นอน) รักษาความสงบเรียบร้อย


1

คุณสามารถใช้ชุด แต่คุณจำเป็นต้องเปลี่ยน dicts เป็นประเภท hashable

seq = [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
unique = set()
for d in seq:
    t = tuple(d.iteritems())
    unique.add(t)

ตอนนี้ไม่ซ้ำกันเท่ากับ

set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])

ในการรับ dicts กลับ:

[dict(x) for x in unique]

คำสั่งของd.iteritems()ไม่รับประกัน - ดังนั้นคุณอาจจะจบลงด้วย unique'ซ้ำกันใน
danodonovan

-1

ต่อไปนี้เป็นวิธีแก้ปัญหาแบบบรรทัดเดียวที่รวดเร็วพร้อมความเข้าใจในรายการซ้อนซ้อน (ขึ้นอยู่กับโซลูชันของ @Emmanuel)

ใช้คีย์เดียว (ตัวอย่างa) ในแต่ละ dict เป็นคีย์หลักแทนที่จะตรวจสอบว่า dict ทั้งหมดตรงกันหรือไม่

[i for n, i in enumerate(list_of_dicts) if i.get(primary_key) not in [y.get(primary_key) for y in list_of_dicts[n + 1:]]]

มันไม่ใช่สิ่งที่ OP ขอมา แต่เป็นสิ่งที่นำฉันมาที่หัวข้อนี้ดังนั้นฉันจึงคิดว่าฉันจะโพสต์วิธีการแก้ปัญหาที่ลงท้ายด้วย


-1

ไม่สั้น แต่อ่านง่าย:

list_of_data = [{'a': 123}, {'b': 123}, {'a': 123}]

list_of_data_uniq = []
for data in list_of_data:
    if data not in list_of_data_uniq:
        list_of_data_uniq.append(data)

ตอนนี้รายการlist_of_data_uniqจะมี dicts ที่ไม่ซ้ำกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.