invert dictionary with list &c

D

Des Small

Lately I have found myself using a pattern to make new dictionaries
quite often, by which I mean twice:

def invert(d):
nd = {}
[nd.setdefault(val, []).append(key) for k, v in d]
return nd

def count(l):
d = {}
[d.setdefault(w, 0) += 1 for w in l]
return d

Is this the pythonic way to do such things? Ideally I'd like to write
them as one liners, but I can't see how.

Des
 
A

anton muhin

Des said:
Lately I have found myself using a pattern to make new dictionaries
quite often, by which I mean twice:

def invert(d):
nd = {}
[nd.setdefault(val, []).append(key) for k, v in d]
return nd

def count(l):
d = {}
[d.setdefault(w, 0) += 1 for w in l]
return d

Is this the pythonic way to do such things? Ideally I'd like to write
them as one liners, but I can't see how.

Des

Most pythonic way IMHO would be:

def invert(d):
nd = {}
for k, v in d.iteritems():
nd[v] = nd.get(k, []) + [k]
return nd

def count(l):
d = {}
for e in l:
d[e] = d.get(e, 0) + 1
return d

Or to define dict with default values:

import copy

class defdict(dict):
def __init__(self, default = None):
self._default = default
super(dict, self).__init__(self)

def __getitem__(self, k):
return self.get(k, copy.deepcopy(self._default)) # or setdefault

when

def invert(d):
nd = defdict([])
for k, v in d.iteritems():
nd[v] += [k]
return nd

def count(l):
d = defdict(0)
for e in l:
d[e] += 1
return d

However, if you insist on one-liners, I can suggest some ;):

def count(l):
return reduce(
lambda d, e: (d.update(dict([(e, d.get(e, 0) + 1)])), d)[1],
l, {}
)

def invert(d):
return reduce(
lambda d, (k, v): (d.update(dict([(v, d.get(v, []) + [k])])), d)[1],
d.iteritems(), {}
)

(given in several lines, but can be written in one)

or even

count = lambda l: reduce(
lambda d, e: (d.update(dict([(e, d.get(e, 0) + 1)])), d)[1],
l, {}
)

:)

regards,
anton.
 
M

Mel Wilson

Lately I have found myself using a pattern to make new dictionaries
quite often, by which I mean twice:

def invert(d):
nd = {}
[nd.setdefault(val, []).append(key) for k, v in d]
return nd

def count(l):
d = {}
[d.setdefault(w, 0) += 1 for w in l]
return d

Is this the pythonic way to do such things? Ideally I'd like to write
them as one liners, but I can't see how.

Once you've written the above, they are one-liners:

inverted_dict = invert (some_dict)

Why would you need less? There's little doubt in any
reader's mind that inverting a dictionary is really what
you're doing; there's little chance of a typo sneaking in
and messing up the process without anybody noticing, etc.

Regards. Mel.
 
J

John Hunter

def count(l):
d = {}
[d.setdefault(w, 0) += 1 for w in l]
return d

This code raises a SyntaxError.

The standard idiom for counting all the elements in a list with a
dictionary is

def count(l):
d = {}
for w in l: d[w] = d.get(w,0) + 1
return d

I think the basic rule of thumb is to use the setdefault approach for
mutable objects (list, dict) and the get approach for immutable
elements (strings, ints, floats, tuples).

Also, many here would find using list comprehensions while ignoring
their return value, as you did in '[d.setdefault(w, 0) += 1 for w in
l]' to be an abuse of list comps. At least I've been admonished for
doing so, and so I must now admonish you to continue the cycle of
violence.

Des> Is this the pythonic way to do such things? Ideally I'd like
Des> to write them as one liners, but I can't see how.

I think that striving for one-liners is not pythonic. Most python
coders value readability over compactness. It's more important to
write an appropriately named function that works and is readable and
efficient than it is to write a one liner.

John Hunter
 
D

Des Small

John Hunter said:
def count(l):
d = {}
[d.setdefault(w, 0) += 1 for w in l]
return d

This code raises a SyntaxError.

So it does. Whatever it was I had working yesterday wasn't that,
then.
The standard idiom for counting all the elements in a list with a
dictionary is

def count(l):
d = {}
for w in l: d[w] = d.get(w,0) + 1
return d

I think the basic rule of thumb is to use the setdefault approach for
mutable objects (list, dict) and the get approach for immutable
elements (strings, ints, floats, tuples).

What I really want is a generic pattern that constructs dictionary
from iterators in a clean way, while allowing mutation of the
dictionary entry as necessary.
Also, many here would find using list comprehensions while ignoring
their return value, as you did in '[d.setdefault(w, 0) += 1 for w in
l]' to be an abuse of list comps. At least I've been admonished for
doing so, and so I must now admonish you to continue the cycle of
violence.

I shall be breaking the cycle by not caring, although if and when
generator comprehensions allow me to comply with your preferences I'll
probably do so.

[...]

Des
 
D

Des Small

anton muhin said:
Des said:
Lately I have found myself using a pattern to make new dictionaries
quite often, by which I mean twice:
def invert(d):
nd = {}
[nd.setdefault(val, []).append(key) for k, v in d]
return nd
def count(l):
d = {}
[d.setdefault(w, 0) += 1 for w in l]
return d

As another poster noted, this latter is broken.
Is this the pythonic way to do such things? Ideally I'd like to
write
them as one liners, but I can't see how.
Des

Most pythonic way IMHO would be:

def invert(d):
nd = {}
for k, v in d.iteritems():
nd[v] = nd.get(k, []) + [k]
return nd

def count(l):
d = {}
for e in l:
d[e] = d.get(e, 0) + 1
return d

I was anxious to extract the common pattern, if possible, but these
are clean enough that it seems slightly perverse.

[...]
However, if you insist on one-liners, I can suggest some ;):

def count(l):
return reduce(
lambda d, e: (d.update(dict([(e, d.get(e, 0) + 1)])), d)[1],
l, {}
)

def invert(d):
return reduce(
lambda d, (k, v): (d.update(dict([(v, d.get(v, []) + [k])])), d)[1],
d.iteritems(), {}
)

(given in several lines, but can be written in one)

This, however, has given me ideas. It was never concision I wanted
but rather redundancy elimination, and the pattern I wanted _can_ be
written:

def dict_cons(iter, func, default):
def process_element(d, (k, v)):
val = d.get(k, default)
d.update(dict([[k, func(v, val)]]))
return d
return reduce(process_element, iter, {})

Which is not to say that it should be, of course.
Whereupon, we can say:

def count(l):
def pair_pad(l): return [(e, ()) for e in l]
return dict_cons(pair_pad(l), lambda k,d: d+1, 0)

def invert(d):
def invertitems(l): for k,v in l: yield v,k
def addtolist(k, l): return l+[k]
return dict_cons(invertitems(d.iteritems()),
addtolist, [])

Perhaps I'm terminally unpythonic, but I quite like these.
count = lambda l: reduce(
lambda d, e: (d.update(dict([(e, d.get(e, 0) + 1)])), d)[1],
l, {}
)

:)

If Python's lambda wasn't so broken I'd use it all the time, for sure.

Des
 
D

Des Small

Des Small said:
This, however, has given me ideas. It was never concision I wanted
but rather redundancy elimination, and the pattern I wanted _can_ be
written:

def dict_cons(iter, func, default):
def process_element(d, (k, v)):
val = d.get(k, default)
d.update(dict([[k, func(v, val)]]))
return d
return reduce(process_element, iter, {})

Or rather:

def dict_cons(iter, func, default):
def process_element(d, (k, v)):
d[k] = func(v, d.get(k, default))
return d
return reduce(process_element, iter, {})

def count(l):
def pair_pad(l): return [(e, ()) for e in l]
return dict_cons(pair_pad(l), lambda k,d: d+1, 0)

def invert(d):
def invertitems(l):
for k,v in l: yield v,k
def addtolist(k, l): return l+[k]
return dict_cons(invertitems(d.iteritems()),
addtolist, [])
Which is not to say that it should be, of course.
Whereupon, we can say:

def count(l):
def pair_pad(l): return [(e, ()) for e in l]
return dict_cons(pair_pad(l), lambda k,d: d+1, 0)

def invert(d):
def invertitems(l): for k,v in l: yield v,k
def addtolist(k, l): return l+[k]
return dict_cons(invertitems(d.iteritems()),
addtolist, [])

Perhaps I'm terminally unpythonic, but I quite like these.

[...]
 
A

anton muhin

Des said:
[...]


This, however, has given me ideas. It was never concision I wanted
but rather redundancy elimination, and the pattern I wanted _can_ be
written:

def dict_cons(iter, func, default):
def process_element(d, (k, v)):
val = d.get(k, default)
d.update(dict([[k, func(v, val)]]))
return d
return reduce(process_element, iter, {})


Or rather:

def dict_cons(iter, func, default):
def process_element(d, (k, v)):
d[k] = func(v, d.get(k, default))
return d
return reduce(process_element, iter, {})

def count(l):
def pair_pad(l): return [(e, ()) for e in l]
return dict_cons(pair_pad(l), lambda k,d: d+1, 0)

def invert(d):
def invertitems(l):
for k,v in l: yield v,k
def addtolist(k, l): return l+[k]
return dict_cons(invertitems(d.iteritems()),
addtolist, [])

Which is not to say that it should be, of course.
Whereupon, we can say:

def count(l):
def pair_pad(l): return [(e, ()) for e in l]
return dict_cons(pair_pad(l), lambda k,d: d+1, 0)

def invert(d):
def invertitems(l): for k,v in l: yield v,k
def addtolist(k, l): return l+[k]
return dict_cons(invertitems(d.iteritems()),
addtolist, [])

Perhaps I'm terminally unpythonic, but I quite like these.


[...]

Or like this:

def dict_update(iter, func, default, d):
def process_element(d, e):
d[e[0]] = func(d.get(e[0], default), *e[1:])
return d

return reduce(process_element, iter, d)

def count(l):
return dict_update(l, lambda x: x + 1, 0, {})

def invert(d):
return dict_update(
[(v, k) for k, v in d.iteritems()],
# In the future (I hope): ((v, k) for k, v in d.iteritems()),
lambda l, e: l + [e], [], {}
)

print count(list('aabbbbcc'))

print invert({'A': 'a', 'B': 'b', 'C': 'a'})

regards,
anton.
 
D

Des Small

anton muhin said:
Des said:
Des Small said:
anton muhin <[email protected]> writes:
[...]

Or like this:

def dict_update(iter, func, default, d):
def process_element(d, e):
d[e[0]] = func(d.get(e[0], default), *e[1:])
return d

return reduce(process_element, iter, d)

def count(l):
return dict_update(l, lambda x: x + 1, 0, {})

With these I get:

Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in count
File "<stdin>", line 5, in dict_update
File "<stdin>", line 3, in process_element
TypeError: <lambda>() takes exactly 1 argument (3 given)

I tried to write something that would work on more generic arguments,
but I couldn't do it without explicit type checking, so I gave up.

[...]

Des
has now embalmed versions in his utils collection.
 
A

anton muhin

Des said:
Des said:

Or like this:

def dict_update(iter, func, default, d):
def process_element(d, e):
d[e[0]] = func(d.get(e[0], default), *e[1:])
return d

return reduce(process_element, iter, d)

def count(l):
return dict_update(l, lambda x: x + 1, 0, {})


With these I get:

count(["yes", "yes", "no"])


Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in count
File "<stdin>", line 5, in dict_update
File "<stdin>", line 3, in process_element
TypeError: <lambda>() takes exactly 1 argument (3 given)

I tried to write something that would work on more generic arguments,
but I couldn't do it without explicit type checking, so I gave up.

[...]

Des
has now embalmed versions in his utils collection.

My fault :( A small patch:

def count(l):
return dict_update([(e,) for e in l], lambda x: x + 1, 0, {})

now

print count(['yes', 'yes', 'no'])

print count('aabbbc')

prints

{'yes': 2, 'no': 1}
{'a': 2, 'c': 1, 'b': 3}

regards,
anton.
 
A

Anton Vredegoor

anton muhin said:
However, if you insist on one-liners, I can suggest some ;):

def count(l):
return reduce(
lambda d, e: (d.update(dict([(e, d.get(e, 0) + 1)])), d)[1],
l, {}
)

def invert(d):
return reduce(
lambda d, (k, v): (d.update(dict([(v, d.get(v, []) + [k])])), d)[1],
d.iteritems(), {}
)

(given in several lines, but can be written in one)

or even

count = lambda l: reduce(
lambda d, e: (d.update(dict([(e, d.get(e, 0) + 1)])), d)[1],
l, {}
)

:)

Is big bot not watching? Then:

def invert(D):
return reduce(lambda d,(k,v):
d.setdefault(v,[]).append(k) or d,D.iteritems(),{})

def count(L):
return reduce(lambda d,v:
d.__setitem__(v,d.get(v,0)+1) or d,L,{})

def test():
print count('aabbbbcc')
print invert({'A': 'a', 'B': 'b', 'C': 'a'})
print count("yes yes no".split())

if __name__=='__main__':
test()

"""
{'a': 2, 'c': 2, 'b': 4}
{'a': ['A', 'C'], 'b': ['B']}
{'yes': 2, 'no': 1} """

Anton
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,173
Messages
2,570,938
Members
47,475
Latest member
NovellaSce

Latest Threads

Top