Как получить набор словарей? - PullRequest
4 голосов
/ 09 мая 2019

У меня есть список словарей, но некоторые из них являются дубликатами, и я хочу удалить их (дубликаты).

Ключи dict - это порядковый номер.

Пример выглядит следующим образом:

[{1: {a:[1,2,3], b: 4}},
{2: {a:[4,5,6], d: 5}},
{3: {a:[1,2,3], b: 4}},
.....,
{1000: {a:[2,5,1], b: 99}},
]

Учитывая предыдущий пример, я хотел бы получить:

[{1: {a:[1,2,3], b: 4}},
{2: {a:[4,5,6], d: 5}},
.....,
{1000: {a:[2,5,1], b: 99}},
]

Фактически словарис ключами 1 и 3 одинаково по своим значениям.

Я пробовал с набором, но так как dict не является хэш-типом, я не могу это сделать.

Как я могу исправитьпроблема?

РЕДАКТИРОВАТЬ

В моем случае количество элементов внутри dict не является фиксированным, поэтому я могу иметь:

[{1: {a:[1,2,3], b: 4}},
{2: {a:[4,5,6], d: 5}},
.....,
{1000: {a:[2,5,1], b: 99, c:["a","v"]}},
]

гдеДиктофон с ключами 100 состоит из трех элементов внутри двух, как показано на рисунке.

Ответы [ 5 ]

4 голосов
/ 09 мая 2019

Чтобы обойти ограничение решения @ jdehesa, где [1, 2] будет считаться дубликатом как (1, 2), вы можете сохранить типы данных, используя pprint.pformat вместо того, чтобы сериализовать структуру данных. Так как pprint.pformat сортирует диктанты по ключам и устанавливает по элементам, {1: 2, 3: 4} будет правильно рассматриваться как {3: 4, 1: 2}, но [1, 2] не будет считаться дубликатом (1, 2):

from pprint import pformat
lst = [
    {1: {'a': [1, 2, 3], 'b': 4}},
    {2: {'a': [4, 5, 6], 'd': 5}},
    {3: {'b': 4, 'a': [1, 2, 3]}},
    {4: {'a': (4, 5, 6), 'd': 5}},
]
seen = set()
output = []
for d in lst:
    for k, v in d.items():
        signature = pformat(v)
        if signature not in seen:
            seen.add(signature)
            output.append({k: v})

output становится:

[{1: {'a': [1, 2, 3], 'b': 4}},
 {2: {'a': [4, 5, 6], 'd': 5}},
 {4: {'a': (4, 5, 6), 'd': 5}}]
3 голосов
/ 09 мая 2019

Вы можете использовать функцию, подобную этой, чтобы превратить ваши объекты во что-то хешируемое:

def make_hashable(o):
    if isinstance(o, dict):
        return frozenset((k, make_hashable(v)) for k, v in o.items())
    elif isinstance(o, list):
        return tuple(make_hashable(elem) for elem in o)
    elif isinstance(o, set):
        return frozenset(make_hashable(elem) for elem in o)
    else:
        return o

Затем вы сохраняете набор видимых объектов и сохраняете только ключи каждого словаря, содержащие объекты, которые вы не видели ранее:

lst = [
    {1: {'a':[1,2,3], 'b': 4}},
    {2: {'a':[4,5,6], 'd': 5}},
    {3: {'a':[1,2,3], 'b': 4}},
]

seen = set()
result_keys = []
for elem in lst:
    keep_keys = []
    for k, v in elem.items():
        v_hashable = make_hashable(v)
        if v_hashable not in seen:
            seen.add(v_hashable)
            keep_keys.append(k)
    result_keys.append(keep_keys)
result = [{k: elem[k] for k in keys} for elem, keys in zip(lst, result_keys) if keys]
print(result)
# [{1: {'a': [1, 2, 3], 'b': 4}}, {2: {'a': [4, 5, 6], 'd': 5}}]

Обратите внимание, что, как отмечает blhsing в комментариях, это имеет некоторые ограничения, такие как учет (1, 2) и [1, 2] равных, а также {1: 2} и {(1, 2)}. Кроме того, некоторые типы не могут быть преобразованы в эквивалентный тип hashable.

РЕДАКТИРОВАТЬ: Как подсказывает a_guest , вы можете обойти неопределенность типа, возвращая сам тип вместе с хешируемым объектом в make_hashable:

def make_hashable(o):
    t = type(o)
    if isinstance(o, dict):
        o = frozenset((k, make_hashable(v)) for k, v in o.items())
    elif isinstance(o, list):
        o = tuple(make_hashable(elem) for elem in o)
    elif isinstance(o, set):
        o = frozenset(make_hashable(elem) for elem in o)
    return t, o

Если вам не нужно смотреть на хешируемый объект, это легко обеспечит строгое сравнение типов. Обратите внимание, что в этом случае даже такие вещи, как {1, 2} и frozenset({1, 2}) будут отличаться.

1 голос
/ 09 мая 2019

Вы можете определить пользовательский хэш своих словарей, создав подклассы dict:

class MyData(dict):

    def __hash__(self):
        return hash((k, repr(v)) for k, v in self.items())

l = [
    {1: {'a': [1, 2, 3], 'b': 4}},
    {2: {'a': [4, 5, 6], 'd': 5}},
    {3: {'b': 4, 'a': [1, 2, 3]}},
    {4: {'a': (4, 5, 6), 'd': 5}},
]

s = set([MyData(*d.values()) for d in l])

Это предполагает, что все словари в списке имеют только одну пару ключ-значение.

0 голосов
/ 09 мая 2019

Это самое простое решение, которое мне удалось придумать, если использовать вложенный словарь, например

{1: {'a': [1,2,3,5,79], 'b': 234 ...}}

, пока единственным контейнером внутри словаря является список типа {'a': [1,2,3..]}, тогда это будет работать. Или вы можете просто добавить простую проверку, как показано ниже.


def serialize(dct):  # this is the sub {'a': [1,2,3]} dictionary
    tmp = []
    for value in dct.values():
        if type(value) == list:
            tmp.append(tuple(value))
        else:
            tmp.append(value)
    return tuple(tmp)

def clean_up(lst):
    seen = set()
    clean = []
    for dct in lst:
        # grabs the 1..1000 key inside the primary dictionary
        # assuming there is only 1 key being the "id" or the counter etc...
        key = list(dct.keys())[0] 
        serialized = serialize(dct[key])
        if serialized not in seen:
            seen.add(serialized)
            clean.append(dct)
    return clean

Таким образом, функция serialize захватывает вложенный словарь и создает простой кортеж из содержимого. Затем проверяется, есть ли в set «увиденное», чтобы проверить его уникальность.

1012 * тесты * генерирует набор данных, используя некоторые случайные значения только потому, что lst = [] for i in range(1,1000): dct = { i: { random.choice(string.ascii_letters): [n for n in range(random.randint(0,i))], random.choice(string.ascii_letters): random.randint(0,i) } } lst.append(dct) Запуск тестов: %timeit clean_up(lst) 3.25 ms ± 17.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit jdhesa(lst) 126 ms ± 606 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) Как видно, функция clean_up значительно быстрее, но проще (не обязательно хорошая вещь) в проверках реализации.

0 голосов
/ 09 мая 2019

Я не знаю, насколько велик ваш список и сколько в нем дубликатов, но, на всякий случай, вот решение basic .
Это может быть неэффективно, но вам не нужно беспокоиться о типе элементов:

import datetime as dt

data = [
    {1: {"b": 4, "a":[1,2,3]}},
    {2: {"a":[4,5,6], "d": 5}},
    {3: {"a":[1,2,3], "b": 4}},
    {4: {'a': dt.datetime(2019, 5, 10), 'd': set([4])}},
    {5: {'a': dt.datetime(2019, 5, 10), 'd': set([4])}},
    {6: {"a":[2,5,1], "b": 99}},
    {7: {"a":[5,2,1], "b": 99}},
    {8: {"a":(5,2,1), "b": 99}}
]



seen = []
output = []
for d in data:
    for k, v in d.items():
        if v not in seen:
            seen.append(v)
            output.append({k:v})

>>> print(output)
[{1: {'a': [1, 2, 3], 'b': 4}},
 {2: {'a': [4, 5, 6], 'd': 5}},
 {4: {'a': datetime.datetime(2019, 5, 10, 0, 0), 'd': {4}}},
 {6: {'a': [2, 5, 1], 'b': 99}},
 {7: {'a': [5, 2, 1], 'b': 99}},
 {8: {'a': (5, 2, 1), 'b': 99}}]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...