Адаптировать словарь в зависимости от значений - PullRequest
0 голосов
/ 19 февраля 2019

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

D = dict()
D[0] = [1]
D[1] = [2]
D[2] = [0]

ИЛИ:

D = dict()
D[0] = [1, 2]
D[1] = [1, 2]
D[2] = [0]

Если список, хранящийся в словаре, имеет более одного значения, это всегда означает, что этот список присутствует под двумя разными ключами.Теперь я хочу преобразовать оба dict в следующее:

D = dict()
D[0] = 1
D[1] = 2
D[2] = 0

Для первого, это просто, функция просто заменит значения dict на первое значение в списке:

def transform_dict(D):
    for key, value in D.items():
        D[key] = value[0]
    return D

Однако во втором случае функция должна назначить одну клавишу с одним значением, а другую - другим.Например, клавише «0» может быть присвоено значение «1» или «2»;и клавиша «1» будет назначена другой.

Я борюсь с этой простой проблемой, и я не вижу способа сделать это эффективно.Есть ли у вас какие-либо идеи?

РЕДАКТИРОВАТЬ: Объяснение n ° 2

Исходный dict может иметь следующий формат:

D[key1] = [val1]
D[key2] = [val2]
D[key3] = [val3, val4]
D[key4] = [val3, val4]

Если aсписок значений состоит из более чем одного элемента, это означает, что в словаре существует второй ключ с таким же списком значений (key3 и key4).

Цель состоит в том, чтобы преобразовать этот dict в:

D[key1] = val1
D[key2] = val2
D[key3] = val3
D[key4] = val4

Где val3 и val4 каким-либо образом связаны с key3 и key4 (мне все равно, какой из них подходит к какому ключу).

EDIT2: Примеры:

# Input dict
D[0] = [7]
D[1] = [5]
D[2] = [4]
D[3] = [1, 2, 3]
D[4] = [6, 8]
D[5] = [1, 2, 3]
D[6] = [1, 2, 3]
D[7] = [6, 8]

#Output
D[0] = 7
D[1] = 5
D[2] = 4
D[3] = 1
D[4] = 6
D[5] = 2
D[6] = 3
D[7] = 8

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

Вы также можете создать класс, который ведет себя как словарь.Таким образом, вам не нужны никакие дополнительные функции для «очистки» словаря, а скорее решать его на лету :)

Как это работает:

  • Мы расширяемcollections.abc.Mapping и перезаписать стандартные словарные функции __getitem__, __setitem__ и __iter__.Мы используем self._storage для сохранения фактического словаря.

  • Мы используем второй словарь _unresolved для отслеживания ключей, которые еще не были разрешены.Например, в приведенном выше примере он имеет запись (1, 2, 3): [4, 5].

  • Мы используем вспомогательную функцию _resolve(), которая проверяет, является ли len((1,2,3)) == len([4,5]).В тот момент, когда вы назначаете D[6], эти длины равны, а элементы присваиваются self._storage.

Пытались добавить комментарии в код.

from collections.abc import Mapping
from collections import defaultdict 

class WeirdDict(Mapping):
    def __init__(self, *args, **kw):
        self._storage = dict()  # the actual dictionary returned

        self._unresolved = defaultdict(list)  # a reversed mapping of the unresolved items
        for key, value in dict(*args, **kw).items():
            self._unresolved_vals[value].append(key)

        self._resolve()

    def __getitem__(self, key):
        return self._storage[key]

    def __setitem__(self, key, val):
        """ Setter. """
        if type(val) == int:
            self._storage[key] = val 
        elif len(val) == 1:
            self._storage[key] = val[0]
        elif key not in self._storage:
            self._unresolved[tuple(val)].append(key)
            self._resolve()

    def _resolve(self):
        """ Helper function - checks if any keys can be resolved """
        resolved = set()
        for val, keys in self._unresolved.items():  # left to resolve
            if len(val) == len(keys):  # if we can resolve (count exhausted)
                for i, k in enumerate(keys):
                    self._storage[k] = val[i]
                    resolved.add(val)
        # Remove from todo list              
        for val in resolved:
            del self._unresolved[val]

    def __iter__(self):
        return iter(self._storage)

    def __len__(self):
        return len(self._storage)

А затем начните с:

D = WeirdDict()

D[0] = [7]
D[1] = 5
D[2] = (4)
D[3] = (1, 2, 3)
D[4] = (6, 8)
D[5] = (1, 2, 3)
D[6] = (1, 2, 3)
D[7] = [6, 8]

# Try this for different output 
D[7]  # gives 8 
0 голосов
/ 19 февраля 2019

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

in_dict = dict()
in_dict[0] = [7]
in_dict[1] = [5]
in_dict[2] = [4]
in_dict[3] = [1, 2, 3]
in_dict[4] = [6, 8]
in_dict[5] = [1, 2, 3]
in_dict[6] = [1, 2, 3]
in_dict[7] = [6, 8]

out_dict = dict()
out_dict[0] = 7
out_dict[1] = 5
out_dict[2] = 4
out_dict[3] = 1
out_dict[4] = 6
out_dict[5] = 2
out_dict[6] = 3
out_dict[7] = 8

def weird_process(mapping):
    result = dict()
    for key, val in mapping.items():
        if len(val) == 1:
            result[key] = val[0]
        elif key not in result:  # was: `else:`
            # find other keys having the same value
            matching_keys = [k for k, v in mapping.items() if v == val]
            for i, k in enumerate(matching_keys):
                result[k] = val[i]
    return result


weird_process(in_dict) == out_dict
# True

РЕДАКТИРОВАТЬ: я немного упростил код.

EDIT2: я улучшил эффективность, пропустив уже обработанные элементы


EDIT3

Еще быстрееподход будет использовать временную копию клавиш ввода, чтобы уменьшить внутреннюю петлю, потребляя ввод, как только он будет использован:

def weird_process(mapping):
    unseen = set(mapping.keys())
    result = dict()
    for key, val in mapping.items():
        if len(val) == 1:
            result[key] = val[0]
        elif key not in result:
            # find other keys having the same value
            matching_keys = [k for k in unseen if mapping[k] == val]
            for i, k in enumerate(matching_keys):
                result[k] = val[i]
                unseen.remove(k)
    return result
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...