在字典列表中,如果 key/value 对的组合在另一个字典中相同,则标记一个字典

In a list of dicts, flag a dict if combination of key/value pairs is identical in another dict

我有一个字典列表,其中包含关键字 streetnumbersome_flag

我的目标是在字典中搜索键 streetnumber 中的重复项。如果对于两个或多个字典,这两个 key/value 对相同,我想将值 1 分配给它们的 some_flag 键。

请参阅下面的可重现示例。

字典起始列表:

a = [
    {'street': 'ocean drive', 'number': '1', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '3', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0}, # duplicate street / number keys
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0}, # duplicate street / number keys
    {'street': 'apple tree rd.', 'number': '3', 'some_flag': 0},
]

预期输出:

a_checked = [
    {'street': 'ocean drive', 'number': '1', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '3', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '4', 'some_flag': 1}, # duplicate street / number keys
    {'street': 'ocean drive', 'number': '4', 'some_flag': 1}, # duplicate street / number keys
    {'street': 'apple tree rd.', 'number': '3', 'some_flag': 0},
]

我的最大努力:

到目前为止我得到的代码是从 Aarons 的回答中派生出来的 (here) and the community wiki's answer (here)

from collections import defaultdict, Counter

items = defaultdict(list) # create defaultdict 

for row in a:
    items[row['street']].append(row['number'])  # make a list of 'number' values for each 'street' key


for key in items.keys():
    if checkIfDuplicates(items[key]):  #if there is more than one 'number' --> function definition see below  
        duplicate_dict = {}
        duplicate_dict['numbers'] =  [item for item, count in Counter(items[key]).items() if count > 1] # storing duplicate numbers in dict
        duplicate_dict['street'] = key # storing street name in same dict

检查给定列表是否包含任何重复项的函数(来自here):

def checkIfDuplicates(listOfElems): 
    if len(listOfElems) == len(set(listOfElems)):
        return False
    else:
        return True
        

当前输出:

print(duplicate_dict)
{'numbers': ['4'], 'street': 'ocean drive'}

使用我的方法,我现在必须将 duplicate_dict 与原始列表 a 匹配,这似乎效率不高。

有没有更直接的方法可以解决这个问题?

只需通过以下方式即可完成:

import pandas as pd
import numpy as np
# Your code
a = [
    {'street': 'ocean drive', 'number': '1', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '3', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0}, # duplicate street / number keys
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0}, # duplicate street / number keys
    {'street': 'apple tree rd.', 'number': '3', 'some_flag': 0},
]
# My code
data = pd.DataFrame(a)
data["some_flag"]= np.where(data.duplicated(keep=False), 1, data["some_flag"])
data

输出

street number some_flag
0 ocean drive 1 0
1 ocean drive 3 0
2 ocean drive 4 1
3 ocean drive 4 1
4 apple tree rd. 3 0

如果您对使用字典而不是数据框感兴趣,可以尝试使用 to_dict 函数将数据框更改为字典:

data.to_dict(orient="list")

这导致:

{'number': ['1', '3', '4', '4', '3'],
 'some_flag': [0, 0, 1, 1, 0],
 'street': ['ocean drive',
  'ocean drive',
  'ocean drive',
  'ocean drive',
  'apple tree rd.']}

说明

在数据帧上使用 duplicated 函数并将 False 分配给 keep 参数,您可以找到重复的行。然后使用 where 这是一个 numpy 函数,您可以根据逻辑语句为数组赋值。

您可以使用 dict.setdefault 首先存储列表的字典(其中键是“街道”和“数字”),然后遍历此字典的值以检查多个字典是否具有相同的值"street" 和 "number" 并修改其中多个的 "some_flag":

tmp = {}
for d in a:
    tmp.setdefault((d['street'], d['number']), []).append(d)
out = []
for v in tmp.values():
    if len(v) > 1:
        for d in v:
            d['some_flag'] = 1
    out.extend(v)

输出:

[{'street': 'ocean drive', 'number': '1', 'some_flag': 0},
 {'street': 'ocean drive', 'number': '3', 'some_flag': 0},
 {'street': 'ocean drive', 'number': '4', 'some_flag': 1},
 {'street': 'ocean drive', 'number': '4', 'some_flag': 1},
 {'street': 'apple tree rd.', 'number': '3', 'some_flag': 0}]

你也可以在没有 pandas 的情况下使用更多代码来实现这一点,例如像这样:

import copy

a = [
    {'street': 'ocean drive', 'number': '1', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '3', 'some_flag': 0},
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0}, # duplicate street / number keys
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0}, # duplicate street / number keys
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0},  # duplicate street / number keys
    {'street': 'ocean drive', 'number': '4', 'some_flag': 0},  # duplicate street / number keys
    {'street': 'apple tree rd.', 'number': '3', 'some_flag': 0},
]

result = copy.deepcopy(a) #duplicate a to not override your input

last_entry = ""

for entry in result:
    if last_entry == "": #skip first iteration to have two entries to compare
        last_entry = entry 
        continue
    if last_entry["street"] == entry["street"] and last_entry["number"] == entry["number"]:
        last_entry["some_flag"] = 1 
        entry["some_flag"] = 1
    last_entry = entry

print(result)

如果字典列表未排序,则我们必须检查每对可能的元素是否相等,这将花费 O(n^2) 时间。 但是,如果列表已排序,那么我们可以检查每个元素及其下一个元素,如果它们不相等,则没有必要使用后续元素检查该元素。另一方面,如果它们相等,我们继续检查下一个,依此类推。

def is_duplicate(d_1, d_2):
    return d_1['street'] == d_2['street'] and d_1['number'] == d_2['number']


def set_duplicate(d_1, d_2):
    d_1['some_flag'] = 1
    d_2['some_flag'] = 1

a = sorted(a, key=lambda k: (k['street'].lower(), k['number']))

cur_index = 0
while cur_index < len(a):

    next_index = cur_index + 1
    while next_index < len(a) and is_duplicate(a[cur_index], a[next_index]):
        set_duplicate(a[cur_index], a[next_index])
        next_index += 1

    cur_index += 1

print(a)
du = {}
for d in a:
    new_key = d['street'] + "_" + d['number']
    if new_key in du.keys():
        du[new_key] = du[new_key] + 1
    else:
        du[new_key] = 1

print(du) 
# {'ocean drive_1': 1, 'ocean drive_3': 1, 'ocean drive_4': 2, 'apple tree rd._3': 1}

for k, v in du.items():
    if v > 1:
        k1 = k.split("_")[0]
        k2 = k.split("_")[1]
        for d in a:
            if d['street'] == k1 and d['number'] == k2:
                d['some_flag'] = 1
print(a)
# [{'street': 'ocean drive', 'number': '1', 'some_flag': 0}, {'street': 'ocean drive', 'number': '3', 'some_flag': 0}, {'street': 'ocean drive', 'number': '4', 'some_flag': 1}, {'street': 'ocean drive', 'number': '4', 'some_flag': 1}, {'street': 'apple tree rd.', 'number': '3', 'some_flag': 0}]