Search code examples
pythondictionaryfunctional-programmingpython-itertools

Convert a list comprehension to functional programming


I have a list of dictionaries

lst = [{'a': (1, 2, 3), 'b': (2, 3)},
       {'c': (3, 6), 'd': (4, 8), 'e': (5, 10)},
       {'d': (6, 12), 'e': (7, 14)}]

For each key in each dictionary, I want to keep only the first element of the values. So the desired output is

[{'a': 1, 'b': 2}, {'c': 3, 'd': 4, 'e': 5}, {'d': 6, 'e': 7}]

I can get it using a list comprehension like

[{key: val[0] for key, val in dct.items()} for dct in lst]

However, I want to know if it's possible to get the same output using map, itemgetter, itertools, functools etc. What I have so far:

map(dict.values, lst)

But I don't know how to go from here.


Solution

  • For nested iterations, I don't think we can do it without the help of lambda expressions:

    from operator import itemgetter, methodcaller
    
    list(map(
         lambda items: dict(zip(
                 map(itemgetter(0), items),
                 map(itemgetter(0), map(itemgetter(1), items))
             )), map(methodcaller('items'), lst)))
    # [{'a': 1, 'b': 2}, {'c': 3, 'd': 4, 'e': 5}, {'d': 6, 'e': 7}]
    

    I have to say it's very ugly.

    Update: I found a way to avoid lambda:

    1. First, according to the comment area, we simplify the above expression (the outermost list is omitted here to reduce the difficulty of understanding):
    func = lambda d: dict(zip(d, map(itemgetter(0), d.values())))
    map(func, lst)
    
    1. It is easy to observe that dict can be moved outside lambda. We just need to add another map:
    func = lambda d: zip(d, map(itemgetter(0), d.values()))
    map(dict, map(func, lst))
    
    1. Similarly, we can move the zip outside lambda:
    func = lambda d: map(itemgetter(0), d.values())
    map(dict, map(zip, lst, map(func, lst)))
    
    1. This seems to be the end, and there seems to be no way to convert lambda into a combination of multiple built-in functions, but there are still ways, let's first try to move d.values outside lambda. Here, since the element type of the list is determined, we directly use dict.values instead of operator.methodcaller:
    func = lambda values: map(itemgetter(0), values)
    map(dict, map(zip, lst, map(func, map(dict.values, lst))))
    
    1. The answer is ready to come out. We can eliminate lambda by using functools.partial:
    map(dict, map(zip, lst, map(partial(map, itemgetter(0)), map(dict.values, lst))))
    

    Test:

    >>> from operator import itemgetter
    >>> from functools import partial
    >>> lst = [{'a': (1, 2, 3), 'b': (2, 3)},
    ...        {'c': (3, 6), 'd': (4, 8), 'e': (5, 10)},
    ...        {'d': (6, 12), 'e': (7, 14)}]
    >>> map(dict, map(zip, lst, map(partial(map, itemgetter(0)), map(dict.values, lst))))
    <map object at 0x000002A0542CBB20>
    >>> list(_)
    [{'a': 1, 'b': 2}, {'c': 3, 'd': 4, 'e': 5}, {'d': 6, 'e': 7}]