5

I feel like Python ought to have a built-in to do this. Take a list of items and turn them into a dictionary mapping keys to a list of items with that key in common.

It's easy enough to do:

# using defaultdict
lookup = collections.defaultdict(list)
for item in items:
    lookup[key(item)].append(item)

# or, using plain dict
lookup = {}
for item in items:
    lookup.setdefault(key(item), []).append(item)

But this is frequent enough of a use case that a built-in function would be nice. I could implement it myself, as such:

def grouped(iterable, key):
    result = {}
    for item in iterable:
        result.setdefault(key(item), []).append(item)
    return result

lookup = grouped(items, key)

This is different than itertools.groupby in a few important ways. To get the same result from groupby, you'd have to do this, which is a little ugly:

lookup = dict((k, list(v)) for k, v in groupby(sorted(items, key=key), key))

Some examples:

>>> items = range(10)
>>> grouped(items, lambda x: x % 2)
{0: [0, 2, 4, 6, 8], 1: [1, 3, 5, 7, 9]}

>>> items = 'hello stack overflow how are you'.split()
>>> grouped(items, len)
{8: ['overflow'], 3: ['how', 'are', 'you'], 5: ['hello', 'stack']}

Is there a better way?

FogleBird
  • 74,300
  • 25
  • 125
  • 131
  • 1
    I don't see how this is a "frequent enough of a use case". I use it seldom and, when I have to, using a `defaultdict` is simply perfect. AFAIK there isn't any built-in that does what you want by itself. – Bakuriu Mar 22 '13 at 21:39
  • You're probably right, but part of me thinks this is just as valid of a builtin as groupby. – FogleBird Mar 23 '13 at 00:16

2 Answers2

3

I also posted this question to comp.lang.python, and the consensus seems to be that this isn't actually common enough to warrant a built-in function. So, using the obvious approaches are best. They work and they're readable.

# using defaultdict
lookup = collections.defaultdict(list)
for item in items:
    lookup[key(item)].append(item)

# or, using plain dict
lookup = {}
for item in items:
    lookup.setdefault(key(item), []).append(item)

I was going to delete my question, but I might as well leave this here in case anyone stumbles across it looking for information.

FogleBird
  • 74,300
  • 25
  • 125
  • 131
  • 1
    See my answer below for how you might extract a function to do the same as above, but using roughly the same API as `groupby`. – tobych Apr 30 '13 at 21:37
1

If you wanted something with roughly the same API as groupby, you could use:

def groupby2(iterable, keyfunc):
    lookup = collections.defaultdict(list)
    for item in iterable:
        lookup[keyfunc(item)].append(item)
    return lookup.iteritems()

So that's the same as your example above, but made into a function returning the iteritems of the lookup table you've built.

tobych
  • 2,941
  • 29
  • 18