1

I have a list built from a string split operation, and I wanted to input it to map() function, then strip these substrings from their trailing parenthesis and spaces. And rebuild a list from them.

teststring=" (A) / (B) "
result = list(map(str.strip, teststring.split("/"), " ()"))

But in the end, I only strip oddly the substrings, while "all combinations are tested", as stated by docs.

I know another method is available with list comprehension:

result = [substr.strip(' ()' for substr in teststring.split("/")]  

which works.. But I wonder why map doesn't work correctly.

I am on python 3.6.4, with Anaconda 4.4 on windows64.

Subsidiary question; the following topic gives some pointers to find source code of functions. But I could not find the code for map (and generally, for builtin functions), so I could not see if there were bugs into it...

Ando Jurai
  • 1,003
  • 2
  • 14
  • 29
  • 1
    What do you expect `map(str.strip, teststring.split("/"), " ()")` to do? When you give `map` multiple iterables it iterates over them in parallel (like `zip` does). – PM 2Ring Jan 16 '18 at 12:21

5 Answers5

4

map with 3 arguments is not behaving the way you think it does. The 3rd argument for map is not the arguments for the function provided as the 1st argument. It is used as a 2nd iterable.

From the docs:

map(function, iterable, ...) Return an iterator that applies function to every item of iterable, yielding the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. With multiple iterables, the iterator stops when the shortest iterable is exhausted. For cases where the function inputs are already arranged into argument tuples, see itertools.starmap()

In other words:

map(str.strip, teststring.split('/'), ' ()') is not equivalent to

[substr.strip(' ()' for substr in teststring.split('/')]. It is remotely equivalent to

[(substr_1.strip(), substr_2.strip()) for (substr_1, substr_2) in zip(teststring.split("/"), ' ()')]

See the difference?

DeepSpace
  • 78,697
  • 11
  • 109
  • 154
  • Thanks. I badly misunderstood the "*iterable" in the docs. I think if I did not want to use the list comprehension, I could achieve what I want with a zip and starmap, but indeed list comprehension is better. – Ando Jurai Jan 16 '18 at 12:33
  • 1
    @AndoJurai no, zip+starmap would do the equivalent of what you are doing here! I.E. `map(f, iterable1, iterable2)` is equivalent to `starmap(f, zip(iterable1, iterable2))` – juanpa.arrivillaga Jan 16 '18 at 12:34
  • I meant using something like starmap(str.strip, zip(teststring.split, repeat(" ()") ) ). starmap works when data is as tuples, but if you input " ()" each time, it is ok. – Ando Jurai Jan 16 '18 at 13:02
3

map works differently from how you are using it. When you do map(f, a, b), it yields f(a[0], b[0]), f(a[1], b[1]), ..., while you are using it like it will yield f(a[0], b), f(a[1], b), ....

To fix this, you can use a list comprehension, like in the question, or a lambda like lambda string: string.strip(" ()").

internet_user
  • 3,149
  • 1
  • 20
  • 29
  • 2
    Of course, using `map` with a lambda like that is going to be slower than the equivalent list comp, since the list comp avoids that extra Python function call. – PM 2Ring Jan 16 '18 at 12:24
3

The problem is that you seem to be under the impression that the last argument to map gets passed as an argument to the function being mapped, that isn't what happens, instead, from the docs:

map(func, *iterables) --> map object

Make an iterator that computes the function using arguments from each of the iterables. Stops when the shortest iterable is exhausted.

In other words, list(map(f, [1,2,3], 'abc')) is the equivalent of:

[f(1,'a'), f(2, 'b'), f(3, 'c')]

Which is not what you want. You want to partially apply string.strip using " ()" as the second argument, but instead, " ()" is being take as another iterable of arguments. So, straightforward solution, use a helper function:

In [9]: def strip_stuff(s):
   ...:     return s.strip(" ()")
   ...:

In [10]: list(map(strip_stuff, teststring.split("/")))
Out[10]: ['A', 'B']

You could make a factor function if you will be needing to be flexible:

In [12]: def make_strip(stuff):
    ...:     def strip(s):
    ...:         return s.strip(stuff)
    ...:     return strip
    ...:

In [13]: list(map(make_strip(" ()"), teststring.split("/")))
Out[13]: ['A', 'B']

In [14]: list(map(make_strip("()"), teststring.split("/")))
Out[14]: [' (A) ', ' (B) ']

In [15]: list(map(make_strip(" )"), teststring.split("/")))
Out[15]: ['(A', '(B']

In [16]: list(map(make_strip(" ("), teststring.split("/")))
Out[16]: ['A)', 'B)']
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
3

You could use operator.methodcaller:

list(map(operator.methodcaller('strip',' ()'),teststring.split("/")))
O.Suleiman
  • 898
  • 1
  • 6
  • 11
1

Using lambda function it can be achieved as follows:

map(lambda s: s.strip(" ()"), teststring.split("/"))
Ilayaraja
  • 2,388
  • 1
  • 9
  • 9