4

So i have some code that works, but it is at best hard to read, and I feel inefficient as it uses two list comprehensions where a single one should suffice.

What I need is to create a dictionary of all n combinations of the letters in alpha, with the key to the dictionary for each item being a tuple of the indices in alpha for the elements in the combination. This should work for any n:

n=2

from itertools import combinations 

alpha = "abcde" 
n = 2

D = {tuple([c_i[0] for c_i in comb]): tuple([c_i[1] for c_i in comb]) 
     for comb in combinations(enumerate(alpha), n)}

>>>{(0, 1): ('a', 'b'),
 (0, 2): ('a', 'c'),
 (0, 3): ('a', 'd'),
 (0, 4): ('a', 'e'),
 (1, 2): ('b', 'c'),
 (1, 3): ('b', 'd'),
 (1, 4): ('b', 'e'),
 (2, 3): ('c', 'd'),
 (2, 4): ('c', 'e'),
 (3, 4): ('d', 'e')}

n=3

from itertools import combinations 

alpha = "abcde" 
n = 3

D = {tuple([c_i[0] for c_i in comb]): tuple([c_i[1] for c_i in comb]) 
     for comb in combinations(enumerate(alpha), n)}

>>>{(0, 1, 2): ('a', 'b', 'c'),
 (0, 1, 3): ('a', 'b', 'd'),
 (0, 1, 4): ('a', 'b', 'e'),
 (0, 2, 3): ('a', 'c', 'd'),
 (0, 2, 4): ('a', 'c', 'e'),
 (0, 3, 4): ('a', 'd', 'e'),
 (1, 2, 3): ('b', 'c', 'd'),
 (1, 2, 4): ('b', 'c', 'e'),
 (1, 3, 4): ('b', 'd', 'e'),
 (2, 3, 4): ('c', 'd', 'e')}

This is working as desired, but I want to know if there is a more readable implementation, or one where I don't need a separate comprehension for [c_i[0] for c_i in comb] and [c_i[1] for c_i in comb] as this feels inefficient.

Note: this is a minimal case representation of a more complex problem where the elements of alpha are arguments to an expensive function and I want to store the output of f(alpha[i], alpha[j], alpha[k]) in a dictionary for ease of lookup without recomputation: ans = D[(i, j, k)]

FinleyGibson
  • 911
  • 5
  • 18

2 Answers2

3

One way to avoid the seemingly redundant tuple key-value formation is to use zip with an assignment expression:

from itertools import combinations 
alpha = "abcde" 
n = 2
D = {(k:=list(zip(*comb)))[0]:k[1] for comb in combinations(enumerate(alpha), n)}

Output:

{(0, 1): ('a', 'b'), (0, 2): ('a', 'c'), (0, 3): ('a', 'd'), (0, 4): ('a', 'e'), (1, 2): ('b', 'c'), (1, 3): ('b', 'd'), (1, 4): ('b', 'e'), (2, 3): ('c', 'd'), (2, 4): ('c', 'e'), (3, 4): ('d', 'e')}
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
  • This is great, and knocks ~1/3 off the compute time. Thanks! (if anything exacerbates the readability issue, but you can't have it all) – FinleyGibson Jun 24 '21 at 16:30
  • @FinleyGibson Glad to help. I personally think that in terms of readability, `(k:=list(zip(*comb)))[0]` and `k[1]` are shorter and more concise that the list comprehensions, but I suppose that is somewhat subjective :) – Ajax1234 Jun 24 '21 at 16:37
  • @Ajax1234 is your code not compatible with Python 3.7.6 or does it have errors? I'm getting a syntax error – Shubham Jun 24 '21 at 16:38
  • 2
    @Shubham The assignment expression (`:=`) is valid in Python versions >= 3.8 – Ajax1234 Jun 24 '21 at 16:39
  • 1
    @Ajax1234 you might want to edit the solution to syntax compatible with Python 3.6+, since they are still widely in use (colab still uses 3.7.10), or mention the version dependency in your solution. Just a suggestion, cheers! – Shubham Jun 24 '21 at 16:40
  • 1
    I wish I could green tick both answers, but I think I am going to put it on the other solution, due to the greater compatibility, and (albeit marginal) faster computation of this solution. Thank you both (I am off to read up on walrus operators). – FinleyGibson Jun 24 '21 at 16:43
3

Try this: (I feel it's a lot less complicated than the other answer, but that one works well too)

from itertools import combinations 

alpha = "abcde" 
n = 2

print({key: tuple([alpha[i] for i in key]) for key in combinations(range(len(alpha)), n)})

Output:

{(0, 1): ('a', 'b'), (0, 2): ('a', 'c'), (0, 3): ('a', 'd'), (0, 4): ('a', 'e'), (1, 2): ('b', 'c'), (1, 3): ('b', 'd'), (1, 4): ('b', 'e'), (2, 3): ('c', 'd'), (2, 4): ('c', 'e'), (3, 4): ('d', 'e')}
Shubham
  • 1,310
  • 4
  • 13