0

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.

  • Well, there is no function I could think of that does exactly what you need. If you write such a function, perhaps composed of other functions, then you can `map` it over the list, sure… – deceze Apr 25 '22 at 08:02
  • 1
    Note that [comprehensions are perfectly fine for functional programming](https://wiki.haskell.org/List_comprehension). – MisterMiyagi Apr 25 '22 at 08:38
  • 1
    A list comprehension **is a functional programming construct**, stolen directly from the rather hardcore, purely functional programming language Haskell. – juanpa.arrivillaga Apr 26 '22 at 02:12

1 Answers1

2

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}]
Mechanic Pig
  • 6,756
  • 3
  • 10
  • 31
  • I think you could reduce the complexity a bit: `list(map(lambda d: dict(zip(d, map(itemgetter(0), methodcaller("values")(d)))), lst))`? – Timus Apr 25 '22 at 09:00
  • 2
    @Timus In that case, we might as well change `methodcaller("values")(d)` directly to `d.values()` – Mechanic Pig Apr 25 '22 at 09:07
  • Indeed, but I wanted to keep it as _functional_ as possible (not saying this is good practice) :) – Timus Apr 25 '22 at 09:09
  • If I understood your solution correctly, you're getting the first element of each dictionary and constructing a new dictionary for each dictionary, correct? Do you think there is a way to do the same without lambda, like is there a function in some module that facilitates such operation? –  Apr 25 '22 at 16:40
  • @DarthSkywalker You seem to have misunderstood. I took out all the items of each dictionary, with the key of each dictionary as the new key and the first element of the value as the new value. – Mechanic Pig Apr 26 '22 at 00:34
  • @DarthSkywalker There seems to be no way to convert all the items of each dictionary into a series of new dictionaries without the help of lambda expression. – Mechanic Pig Apr 26 '22 at 00:37
  • I think using `zip` here is just making it harder to get at what you want – juanpa.arrivillaga Apr 26 '22 at 02:25
  • 1
    @DarthSkywalker why? The whole *point* of functional programming is to write and use functions. There is no built-in function to do this in Python. Note, Python is very much *not* a functional programming language, so even if that were a reasonable built-in convenience function for a functional programming language to have, it certainly wouldn't mean that you'd expect to find it in Python. – juanpa.arrivillaga Apr 26 '22 at 02:28
  • @juanpa.arrivillaga There are a lot of answers on here that suggest that mapping a builtin function (without lambda) is faster than list comprehension and I was wondering if it was possible to do it in this case. I'm just learning this stuff and I don't even know if I used the correct terminology (re: functional programming) but I found your comments very informative. Thank you. –  Apr 26 '22 at 05:09
  • 1
    @DarthSkywalker that isn't really true. Sometimes it is, but the difference in *either case* is usually not a relevant factor, especially as the work done in the mapping operation increases. `map` vs list-comprehension is a matter of style. Same in relation to just a regular for-loop. Write code to be clear, maintainable, modular, and not obviously inefficient (e.g. using a quadratic time algorithm when a linear time one is trivial) – juanpa.arrivillaga Apr 26 '22 at 05:36