Best way to sort a list into a dict based off of an attr on list elements

Is there a better way to sort a list into a dict based off of an attr on list elements?

def getProjectTypesToListOfProjects(projects):
    result = {}
    for project in projects:
        try:
            result[str(project.type)].append(project)
        except KeyError:
            result[str(project.type)] = [project]
    return result

Perfect job for collections.defaultdict:

result = defaultdict(list)
for project in projects: result[str(project.type)].append(project)

(If you don’t need to stringify the types for other reasons, don’t do it just for the dictionary. They can handle any hashable type.)

1 Like

Hi Ryan,

Your code seems quite practical to me. But if you want it shorter (though less readable), for example:

y = [0, 1, 2, 3.0, 4.0, None, True, 'foo', (), [], type, lambda:y]
pred = type

result = {}
for t in set(map(pred, y)):
    result[t] = [x for x in y if pred(x)==t]

which gives

>>> pprint(result)
{<class 'bool'>: [True],
 <class 'float'>: [3.0, 4.0],
 <class 'function'>: [<function <lambda> at 0x00000287490F5090>],
 <class 'list'>: [[]],
 <class 'int'>: [0, 1, 2],
 <class 'NoneType'>: [None],
 <class 'tuple'>: [()],
 <class 'type'>: [<class 'type'>],
 <class 'str'>: ['foo']}

I think Chris’s answer looks better:

from collections import defaultdict

result = defaultdict(list)
for x in y: 
    result[pred(x)].append(x)

especially if the behavior against nonexistent keys is desirable.

I like dict.setdefault() for this sort of thing:

create a dataset to try (NOTE: much better to post a small dataset yourself :slight_smile: ) borrowing from @komoto48g

In [38]: @dataclass
    ...: class Project():
    ...:     type: str
    ...:     value: None
    ...: 

In [39]: y = [0, 1, 2, 3.0, 4.0, None, True, 'foo', (), [], type, lambda:y]

In [40]: projects = [Project(str(type(i)), i) for i in y]

Now your function:

In [41]: def getProjectTypesToListOfProjects(projects):
    ...:     result = {}
    ...:     for project in projects:
    ...:         result.setdefault(project.type, []).append(project)
    ...:     return result
    ...: 
    ...: 

And try it out

In [42]: getProjectTypesToListOfProjects(projects)
Out[42]: 
{"<class 'int'>": [Project(type="<class 'int'>", value=0),
  Project(type="<class 'int'>", value=1),
  Project(type="<class 'int'>", value=2)],
 "<class 'float'>": [Project(type="<class 'float'>", value=3.0),
  Project(type="<class 'float'>", value=4.0)],
 "<class 'NoneType'>": [Project(type="<class 'NoneType'>", value=None)],
 "<class 'bool'>": [Project(type="<class 'bool'>", value=True)],
 "<class 'str'>": [Project(type="<class 'str'>", value='foo')],
 "<class 'tuple'>": [Project(type="<class 'tuple'>", value=())],
 "<class 'list'>": [Project(type="<class 'list'>", value=[])],
 "<class 'type'>": [Project(type="<class 'type'>", value=<class 'type'>)],
 "<class 'function'>": [Project(type="<class 'function'>", value=<function <lambda> at 0x10543de10>)]}
1 Like