简体   繁体   中英

Self-referential dictionary comprehension

Recently, I was answering a question. The code worked as intended. But I wanted to see if I could implement dict comprehension which I rarely use. First of all, let me explain the problem .

The OP had an example list like M1 = [['a', 14], ['a',7], ['a',16],['b',3],['b',15],['c',22],['c',1],['c',5]] . They wanted an output similar to this. [['a',14,7,16],['b',3,15],['c',22,1,5]] . Makes sense, so I created an answer.


original code

x = [['a', 14,15], ['a',7], ['a',16],['b',3],['b',15],['c',22],['c',1],['c',5]]
dictX = {}
for lsts in x:
    if lsts[0] in dictX.keys():dictX[lsts[0]].extend(lsts[1:])
    else:dictX[lsts[0]] = lsts[1:]  

output

{'a': [14, 15, 7, 16], 'b': [3, 15], 'c': [22, 1, 5]}

my go at this

x = [['a', 14,15], ['a',7], ['a',16],['b',3],['b',15],['c',22],['c',1],['c',5]]
dictX = {}
dictX ={(dictX[lsts[0]].extend(lsts[1:]) if lsts[0] in dictX.keys() else dictX[lsts[0]]): lsts[1:]  for lsts in x}

Error

Traceback (most recent call last): File "/Users/aspera/Documents/Python/Py_Programs/data/timeComplexity/test.py", line 3, in dictX ={(dictX[lsts[0]].extend(lsts[1:]) if lsts[0] in dictX.keys() else dictX[lsts[0]]): lsts[1:] for lsts in x} File "/Users/aspera/Documents/Python/Py_Programs/data/timeComplexity/test.py", line 3, in dictX ={(dictX[lsts[0]].extend(lsts[1:]) if lsts[0] in dictX.keys() else dictX[lsts[0]]): lsts[1:] for lsts in x} KeyError: 'a'

My shot at this seems wrong in so many ways. I used this as a reference (top comment of accepted answer)

{(a if condition else b): value for key, value in dict.items()}

Is there any way I could turn this to dict comprehension. I'd like an example which goes along the lines of the reference I provided and the logic I have used in my original code

Not at all faster due to nested loops, but it is doable. Using list comprehension inside dict comprehension.

seq = [['a', 14,15], ['a',7], ['a',16],['b',3],['b',15],['c',22],['c',1],['c',5]]
res = {i[0]:[k for j in seq if j[0] == i[0] for k in j[1:]] for i in seq}
print(res)

Output

{'a': [14, 15, 7, 16], 'b': [3, 15], 'c': [22, 1, 5]}

PS: I realize this question is more related to Is it possible to access current object while doing list/dict comprehension in Python? , if anyone feels that is a dupe do mark it as such, I am not sure so I am leaving it be.

In general you won't be able to reference the dictionary itself in the comprehension, because the name won't get assigned to the resulting dictionary until the comprehension is completed, so you'll have to settle for predefining the dictionary* and utilize mutating methods of the existing dictionary.

Since you're iterating over the input list, you'll need to update the existing dictionary with the new values whenever you come across the key. And as you can't use an assignment in the dictionary comprehension, you'll want to use the dict.update() method (or __setitem__ or setdefault ). That method always returns None , so you can utilize that to achieve the desired side effect in a number of different places inside the dictionary comprehension.

In particular, any filtering condition clause will be executed, so you can use that. Alternatively, expr or value will evaluate the expression, which will always return None , and since that's falsey the whole expression evaluates to value , so you can place that expression in the key or value. That gives us the following possibilities:

With the side effect in the filter clause:

d = {}
d = {k: d[k] for k, *vals in x if d.update({k: d.get(k, []) + vals}) is None}

With the side effect in a expr or key expression:

d = {}
d = {d.update({k: d.get(k, []) + vals}) or k: d[k] for k, *vals in x}

With the side effect in a expr or value expression:

d = {}
d = {k: d.update({k: d.get(k, []) + vals}) or d[k] for k, *vals in x}

* Using assignment expressions (Python 3.8+), you can predefine the dictionary inside the comprehension itself with this abomination:

d = {k: d.update({k: d.get(k, []) + vals}) or d[k] for i, (k, *vals) in enumerate(x) if i or not (d := {})}

This uses enumerate() to detect when you're on the first iteration, in which case an assignment expression can construct the dictionary that is used in the rest of the comprehension. After the first iteration, the assignment expression is not evaluated again so d doesn't get reassigned in the course of the evaluation.


Note: Obviously, all of the methods shown in this answer are terrible. Side-effects inside comprehensions are unnecessary, unexpected, confusing, and in one word, silly. Do not use this code. But it is fun to see what's possible!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM