6

I'm trying to invert a dictionary. In the case of many keys having the same value, the new key (old value) should associate with a set of the new values (old keys). I solved the problem, but I'm trying to refactor using dictionary comprehension, and some helper methods.

def has_unique_value(k):
    return d.values().count(d[k]) == 1

def keys_with_same_value_as_key(k):
    return set([key for key in d.keys() if d[key] == d[k]])

print( {d[k]:k if has_unique_value(k) else d[k]:keys_with_same_value_as_key(k) for k in d.keys()} )

However, this raises a syntax error

print( {d[k]:k if has_unique_value(k) else d[k]:keys_with_same_value_as_key(k) for k in d} )
                                               ^
SyntaxError: invalid syntax

Hopefully the alignment in that code works right. It should point to the second :, in the else clause. Any idea what's up here? I've tried as many forms of parenthesizing as I can conceive.

Brad Rice
  • 1,334
  • 2
  • 17
  • 36
  • Note that it's slightly awkward to have some values be (effectively) scalars and others be sets of values. It'd be more natural to have sets of one value for unique cases. – DSM Jan 18 '14 at 20:28
  • @DSM yeah, it's an exercise, not really being used for production code of any kind. Just an interesting brain teaser (for me) – Brad Rice Jan 18 '14 at 20:30

2 Answers2

7

The ternary expression can only be applied to one value and not to the equivalent of a dictionary assignment. Try this instead:

{d[k]: k if has_unique_value(k) else keys_with_same_value_as_key(k) for k in d.keys()}

Your first approach would be similar to the following (which does not work):

d[k] = k if k else d[k] = None  # doesn't work
Justin O Barber
  • 11,291
  • 2
  • 40
  • 45
  • That did the trick. So the whole ternary expression is actually evaluating the `k` part of the initial `d[k]:k` then. Cool! Thanks – Brad Rice Jan 18 '14 at 20:27
  • @Brad Rice That's right. The ternary expression is evaluating what value to assign to the place immediately after the initial d[k]. Glad it helped. – Justin O Barber Jan 18 '14 at 20:30
7

Close!

The following code

d = {'a': 'x', 'b': 'y', 'c': 'x'}

def has_unique_value(k):
    return d.values().count(d[k]) == 1

def keys_with_same_value_as_key(k):
    return set([key for key in d.keys() if d[key] == d[k]])

print( {d[k]:k if has_unique_value(k) else keys_with_same_value_as_key(k) for k in d.keys()} )

Produces

{'y': 'b', 'x': set(['a', 'c'])}

The only difference is the second d[k]: is removed.


In general, the ternary expression looks like

a = val_if_true if test else val_if_false

not something closer to what you had:

a = val_if_true if test else a = val_if_false

You specify where the value from the ternary expression should go once, at the beginning.


RE: Question in comments

This is still definitely a dictionary comprehension.

It's basically doing the following:

m = {}
for k in d.keys():
    orig_key = k
    orig_val = d[k]

    if has_unique_value(k):
        m[orig_val] = orig_key
    else:
        m[orig_val] = keys_with_same_value_as_key(orig_key)

print m

The only difference is that, in the dictionary comprehension, the m dictionary is not kept in the namespace (and the variables orig_key and orig_val I used to clarify the code never exist).

jedwards
  • 29,432
  • 3
  • 65
  • 92
  • So it's technically __still__ a "list" comprehension, then. Not really dealing with a new dictionary. The ternary expression is really just assigning the new `.values()` list being returned? – Brad Rice Jan 18 '14 at 20:29
  • It's certainly still a dictionary comprehension. One second, I'll edit my answer to try to elaborate on that. – jedwards Jan 18 '14 at 20:32
  • Ok yeah, that's essentially what my old code looked like. Way too verbose for such a simple task. So it's called a dictionary comprehension because it's building a dictionary, rather than a list? And as you said before, you don't have to specify the `d[k]:` again, because you do that before the initial `if` clause? – Brad Rice Jan 18 '14 at 20:45
  • Yep, [dictionary comprehensions were added in 2.7](http://www.python.org/dev/peps/pep-0274/). And also yes, the `d[k]:` part "receives" the output of the entire ternary expression, which is `k if else `. – jedwards Jan 18 '14 at 21:01