Defaultdict одноэтапная инициализация
Было бы удобно, если бы defaultdict
можно было инициализировать по следующим строкам
d = defaultdict(list, (('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2),
('b', 3)))
для создания
defaultdict(<type 'list'>, {'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]})
Вместо этого я получаю
defaultdict(<type 'list'>, {'a': 2, 'c': 3, 'b': 3, 'd': 4})
Чтобы получить то, что мне нужно, мне приходится делать это:
d = defaultdict(list)
for x, y in (('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2), ('b', 3)):
d[x].append(y)
Это ИМО на один шаг больше, чем нужно, я что-то упустил?
Ответы
Ответ 1
описанное вами поведение не согласуется с другими типами поведения defaultdict
. Похоже, что вы хотите FooDict
, чтобы
>>> f = FooDict()
>>> f['a'] = 1
>>> f['a'] = 2
>>> f['a']
[1, 2]
Мы можем это сделать, но не с defaultdict; позволяет называть его AppendDict
import collections
class AppendDict(collections.MutableMapping):
def __init__(self, container=list, append=None, pairs=()):
self.container = collections.defaultdict(container)
self.append = append or list.append
for key, value in pairs:
self[key] = value
def __setitem__(self, key, value):
self.append(self.container[key], value)
def __getitem__(self, key): return self.container[key]
def __delitem__(self, key): del self.container[key]
def __iter__(self): return iter(self.container)
def __len__(self): return len(self.container)
Ответ 2
То, что вам явно не хватает, состоит в том, что defaultdict
- это простой (не особо "магический" ) подкласс dict
. Все первые аргументы - это функция factory для отсутствующих ключей. Когда вы инициализируете defaultdict
, вы инициализируете dict
.
Если вы хотите создать
defaultdict(<type 'list'>, {'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]})
вы должны инициализировать его так, как вы бы инициализировали любой другой dict
, значения которого являются списками:
d = defaultdict(list, (('a', [1, 2]), ('b', [2, 3]), ('c', [3]), ('d', [4])))
Если ваши исходные данные должны быть в виде кортежей, второй элемент которых всегда является целым числом, то просто перейдите в цикл for
. Вы называете это еще одним шагом; Я называю это ясным и очевидным способом сделать это.
Ответ 3
Сортировка и itertools.groupby
пройдут длинный путь:
>>> L = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2), ('b', 3)]
>>> L.sort(key=lambda t:t[0])
>>> d = defaultdict(list, [(tup[0], [t[1] for t in tup[1]]) for tup in itertools.groupby(L, key=lambda t: t[0])])
>>> d
defaultdict(<type 'list'>, {'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]})
Чтобы сделать это более однострочным:
L = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2), ('b', 3)]
d = defaultdict(list, [(tup[0], [t[1] for t in tup[1]]) for tup in itertools.groupby(sorted(L, key=operator.itemgetter(0)), key=lambda t: t[0])])
Надеюсь, что это поможет
Ответ 4
Я думаю, что в большинстве случаев это много дыма и зеркал, чтобы избежать простого цикла for:
di={}
for k,v in [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2),('b', 3)]:
di.setdefault(k,[]).append(v)
# di={'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]}
Если ваша цель - одна строка, и вы хотите оскорбительный синтаксис, который я вообще не могу одобрить или поддерживать, вы можете использовать понимание побочного эффекта:
>>> li=[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2),('b', 3)]
>>> di={};{di.setdefault(k[0],[]).append(k[1]) for k in li}
set([None])
>>> di
{'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]}
Если вы действительно хотите зайти за борт в нечитаемое:
>>> {k1:[e for _,e in v1] for k1,v1 in {k:filter(lambda x: x[0]==k,li) for k,v in li}.items()}
{'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]}
Вы не хотите этого делать. Используйте цикл for Luke!
Ответ 5
>>> kvs = [(1,2), (2,3), (1,3)]
>>> reduce(
... lambda d,(k,v): d[k].append(v) or d,
... kvs,
... defaultdict(list))
defaultdict(<type 'list'>, {1: [2, 3], 2: [3]})