Вопрос по python – Сравните словари, игнорирующие определенные ключи

21

Как я могу проверить, равны ли два словаря, не принимая во внимание некоторые ключи. Например,

equal_dicts(
    {'foo':1, 'bar':2, 'x':55, 'y': 77 },
    {'foo':1, 'bar':2, 'x':66, 'z': 88 },
    ignore_keys=('x', 'y', 'z')
)

должен вернуть True.

UPD: я ищу эффективное и быстрое решение.

UPD2. Я закончил с этим кодом, который, кажется, самый быстрый:

def equal_dicts_1(a, b, ignore_keys):
    ka = set(a).difference(ignore_keys)
    kb = set(b).difference(ignore_keys)
    return ka == kb and all(a[k] == b[k] for k in ka)

Тайминги:https://gist.github.com/2651872

Ваш Ответ

6   ответов
0

Оптимальное решение для случая игнорирования только одного ключа

return all(
    (x == y or (x[1] == y[1] == 'key to ignore')) for x, y in itertools.izip(
          d1.iteritems(), d2.iteritems()))
Осторожно: это, вероятно, не работало правильно во всех случаях в более ранних версиях Python (например, хеш-таблицы разного размера и т. Д.), Но аналогичная реализация, безусловно, дольше работает в Python 3.6+, потому что методы dict.items () и т. Д. Теперь возвращают элементы при вставке. порядок, не хэш-таблица-порядок.
1
def compare_dict(d1, d2, ignore):
    for k in d1:
        if k in ignore:
            continue
        try:
            if d1[k] != d2[k]:
                return False
        except KeyError:
            return False
    return True

Редактировать комментарий: Вы можете сделать что-то вродеcompare_dict(d1, d2, ignore) and compare_dict(d2, d1, ignore) или продублируйтеfor

def compare_dict(d1, d2, ignore):
    ignore = set(ignore)
    for k in d1:
        if k in ignore:
            continue
        try:
            if d1[k] != d2[k]:
                return False
        except KeyError:
            return False

    for k in d2:
        if k in ignore:
            continue
        try:
            if d1[k] != d2[k]:
                return False
        except KeyError:
            return False
    return True

Что бы ни было быстрее и чище! Обновление: набор заклинаний (игнорировать)

Спасибо, но я не думаю, что это сработает, когдаd2 имеет дополнительные ключи. georg
19
def equal_dicts(d1, d2, ignore_keys):
    d1_filtered = dict((k, v) for k,v in d1.iteritems() if k not in ignore_keys)
    d2_filtered = dict((k, v) for k,v in d2.iteritems() if k not in ignore_keys)
    return d1_filtered == d2_filtered

РЕДАКТИРОВАТЬ: Это может быть быстрее и более эффективно использовать память:

def equal_dicts(d1, d2, ignore_keys):
    ignored = set(ignore_keys)
    for k1, v1 in d1.iteritems():
        if k1 not in ignored and (k1 not in d2 or d2[k1] != v1):
            return False
    for k2, v2 in d2.iteritems():
        if k2 not in ignored and k2 not in d1:
            return False
    return True
Просто тестируюd[k1] != v1 безk1 not in d2 проверить и пойматьKeyError возможно быстрее (избегает хешированияk1 в третий раз).
Второй, кажется, глючит:equal_dicts({'a':3,'b':5}, {'a':3,'b':6}, 'b') == False (должно быть True). georg
+1 (лучше, чем мой ответ!) Кроме того, если кто-то использует Python 3, вы можете использоватьdict comprehension (прокрутите немного вниз) вместоdict(<generator expression>) идиома.
Это простое решение, но в моей ситуации эффективность имеет значение. georg
@ thg435 - см. мой обновленный ответ.
0

если ваш словарь содержал списки или другие словари:

def equal_dicts(d1, d2, ignore_keys, equal):
    # print('got d1', d1)
    # print('got d2', d2)
    if isinstance(d1, str):
        if not isinstance(d2, str):
            return False
        return d1 == d2
    for k in d1:
        if k in ignore_keys:
            continue
        if not isinstance(d1[k], dict) and not isinstance(d1[k], list) and d2.get(k) != d1[k]:
            print(k)
            equal = False
        elif isinstance(d1[k], list):
            if not isinstance(d2.get(k), list):
                equal = False
            if len(d1[k]) != len(d2[k]):
                return False
            if len(d1[k]) > 0 and isinstance(d1[k][0], dict):
                if not isinstance(d2[k][0], dict):
                    return False
                d1_sorted = sorted(d1[k], key=lambda item: item.get('created'))
                d2_sorted = sorted(d2[k], key=lambda item: item.get('created'))
                equal = all(equal_dicts(x, y, ignore_keys, equal) for x, y in zip(d1_sorted, d2_sorted)) and equal
            else:
                equal = all(equal_dicts(x, y, ignore_keys, equal) for x, y in zip(d1[k], d2[k])) and equal
        elif isinstance(d1[k], dict):
            if not isinstance(d2.get(k), dict):
                equal = False
            print(k)
            equal = equal_dicts(d1[k], d2[k], ignore_keys, equal) and equal
    return equal
1

very грубо говоря, вы можете просто удалить любые игнорируемые ключи и сравнить эти словари:

def equal_dicts(d1, d2, ignore_keys=()):
    d1_, d2_ = d1.copy(), d2.copy()
    for k in ignore_keys:
        try:
            del d1_[k]
        except KeyError: 
            pass
        try:
            del d2_[k]
        except KeyError: 
            pass

    return d1_ == d2_

(Обратите внимание, что нам здесь не нужна глубокая копия, нам просто нужно избегать измененияd1 а такжеd2.)

действительно сырая)))) georg
12

Использование словосочетаний:

>>> {k: v for k,v in d1.items() if k not in ignore_keys} == \
... {k: v for k,v in d2.items() if k not in ignore_keys}

использование.viewitems() вместо этого на Python 2.

тогда вы можете написать цикл вручную, но вы все равно можете быстрее найти понимание из-за реализации C
Это будет работать в Python 2.7 и 3.
Спасибо, но смотрите мой комментарий к ответу eumiro. Я предпочитаю не строить две дорогие структуры памяти, просто чтобы сравнить их. georg
Сравнение двух словесных представлений - прекрасная однострочность. И я согласен, что этот метод может быть даже быстрее, в зависимости от размера данных.

Похожие вопросы