Dict понимание для группировки слов по первой букве

Кто-нибудь знает, как избежать ошибки <generator object dictionary.<locals>.<genexpr> at 0x000001D295344580>, которую я получаю при попытке создать понимание dict, которое генерирует определенные ключи: значения?

Например, если у нас есть список:

words = ["hallo" , "hell", "hype", "empty", "full", "charge", "hey"]

Я хочу создать словарь

{starting character of the item in list : list of items in words that start with the specific character}

поэтому для моего примера ожидаемый результат будет таким:

{"h": ["hallo", "hell" , "hype", "hey"], "e" : ["empty"], "f": ["full"], "c": ["charge] }

Мой код:

{(chr(c) for c in range(ord("a"), ord("z")+1)):
            [word for word in words if word.startswith("a")]}

То же самое произойдет, если я попытаюсь обобщить оператор word.startswith().


person Aru    schedule 10.12.2020    source источник
comment
Не могли бы вы уточнить вашу проблему? Ваш код не является минимально воспроизводимым примером, поэтому мы должны полагаться на вашу информацию. ‹Словарь объекта генератора.. по адресу 0x000001D295344580› не является ошибкой, это объект генератора. Вы намеревались (chr(c) for c in range(ord("a"), ord("z")+1)) создать кортеж?   -  person MisterMiyagi    schedule 10.12.2020
comment
Ключами словаря должны быть буквы az, а значениями должен быть подсписок слов, начинающихся с буквы   -  person Aru    schedule 10.12.2020
comment
вы, вероятно, ищете что-то вроде {chr(c):[word for word in words if word.startswith(chr(c))] for c in range(ord("a"),ord("z"))}?   -  person Onyambu    schedule 10.12.2020
comment
Это самое далекое, что вы можете получить в понимании списка. Тогда у вас может быть еще один цикл for для удаления пустых значений. Но это дорого. Используйте обычный цикл for только один раз, и вы получите ожидаемые результаты.   -  person Onyambu    schedule 10.12.2020
comment
да, это то, что я искал, большое спасибо. Я просто не могу понять, как ты туда попал   -  person Aru    schedule 10.12.2020
comment
последняя часть может быть изменена на c в диапазоне (97 123)   -  person Aru    schedule 10.12.2020
comment
Это похоже на еще одну фантастическую победу (не)пониманий: просто используйте цикл for, потому что в (я думаю) 99% нетривиальных случаев его намного проще написать, проще отладить, проще понять и изменить.   -  person barny    schedule 11.12.2020


Ответы (3)


Ваше текущее решение - и исправленная версия - довольно неэффективны, поскольку они повторяются для всех букв и для каждой буквы для всех слов, поэтому 26 * (количество слов) циклов.

Вы можете сделать это, выполнив итерацию только один раз в списке слов, создав ключ словаря и список, который будет содержать слова на лету. defaultdict упрощает эту задачу:

from collections import defaultdict

words = ["hallo" , "hell", "hype", "empty", "full", "charge", "hey"]

out = defaultdict(list)
for word in words:
    out[word[0]].append(word)
    
print(out)
# defaultdict(<class 'list'>, {'h': ['hallo', 'hell', 'hype', 'hey'], 'e': ['empty'], 'f': ['full'], 'c': ['charge']})

всего 7 циклов вместо 26*7 и столько же тестов, и более простой код...

person Thierry Lathuille    schedule 10.12.2020

Это легко сделать с помощью itertools.groupby:

>>> from itertools import groupby
>>> {k: list(v) for k, v in groupby(sorted(words), lambda s: s[0])}
{'c': ['charge'], 'e': ['empty'], 'f': ['full'], 'h': ['hallo', 'hell', 'hey', 'hype']}

Как только слова отсортированы в обычном лексикографическом порядке, их можно сгруппировать по первым буквам. (Также будет достаточно сортировки только по первой букве.)

person chepner    schedule 10.12.2020

Это не ошибка, это объект, который вы вставили как ключ. Похоже, вы запутались в синтаксисе понимания dict. выражение генератора, которое вы написали ((chr(c) for c in ...)), не расширяется , вместо этого он используется как ключ. На самом деле, то, что вы написали, даже не является пониманием словаря.

Чтобы сделать то, что вы хотите, цикл должен быть после пары ключ-значение.

{chr(c): [word for word in words if word.startswith(chr(c))]
 for c in range(ord("a"), ord("z")+1)}

Для сравнения, вот свободная версия синтаксиса:

{key: value for x in iterable}

Это наивное решение. См. Тьерри и чепнера ответы на лучшие решения. С наивным вам также нужно удалить пустые списки:

>>> d = {chr(c): [word for word in words if word.startswith(chr(c))]
...      for c in range(ord("a"), ord("z")+1)}
>>> d
{'a': [], 'b': [], 'c': ['charge'], 'd': [], 'e': ['empty'], 'f': ['full'], 'g': [], 'h': ['hallo', 'hell', 'hype', 'hey'], 'i': [], 'j': [], 'k': [], 'l': [], 'm': [], 'n': [], 'o': [], 'p': [], 'q': [], 'r': [], 's': [], 't': [], 'u': [], 'v': [], 'w': [], 'x': [], 'y': [], 'z': []}
>>> {k: v for k, v in d.items() if v}
{'c': ['charge'], 'e': ['empty'], 'f': ['full'], 'h': ['hallo', 'hell', 'hype', 'hey']}
person wjandrea    schedule 10.12.2020