Weird map behavior when using str.replace

Question:

I tried to use map with str.replace and I couldn’t get it to work properly.

Can someone explain why?

Here’s my code with a few examples:

1.

code:

print(list(map(str.replace, "abc", "ab", "b")))

output:

['b']

2.

code:

print(list(map(str.replace, "abc", "b", "c")))

output:

['a']

3.

code:

print(list(map(str.replace, "abc", "abc", "abc")))

output:

['a', 'b', 'c']

As you can see the output logic isn’t very consistent and I can’t understand why.

Asked By: Eilonlif

||

Answers:

You gave map() 3 iterables in each example. map() takes arguments for the callable, the first argument, from each of those iterables in turn, as if you used the zip() function on them. It stops when the shortest of the arguments is exhausted.

From the documentation for map():

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

The important thing to realise here is that strings are iterables of separate characters; they are not treated as single values.

The first example has one argument that’s only a single character, 'b', so there is just one call to str.replace(); this is called with each of the first characters of the inputs; for "abc", "ab", "b" that’s, the first characters are "a", "a" and "b", respectively, and so map() executes:

str.replace("a", "a", "b")

str.replace() is the unbound method, and by passing in "a" as the first argument, it’s applied as if you used:

"a".replace("a", "b")

Replacing "a" with "b" gives "b". This is then the one and only result produced.

The same patterns apply to the other examples; pair up the first, then second, etc. letters of the inputs, and apply those three single character strings as the three arguments to str.replace(). Stop when one of the inputs has no more characters left. So, for the two remaining examples, we get:

  1. map(str.replace, "abc", "b", "c") has 2 inputs that are just 1 character long, so there is just a single str.replace("a", "b", "c") call, and so returns "a" (no "b" characters can be replaced).

  2. With map(str.replace, "abc", "abc", "abc") you gave 3 inputs of each 3 characters, so there are 3 str.replace() calls, each with single characters as the inputs:

    1. str.replace("a", "a", "a"), producing "a"
    2. str.replace("b", "b", "b"), producing "b"
    3. str.replace("c", "c", "c"), producing "c"

If you wanted the whole strings to be used as the inputs, wrap each of them in another iterable, such as a list or a tuple:

>>> print(list(map(str.replace, ["abc"], ["ab"], ["b"])))
['bc']
>>> print(list(map(str.replace, ("abc",), ("ab",), ("b",))))
['bc']
Answered By: Martijn Pieters

This is a simplified version of the map function:

def my_map(func, *iterables):
    for items in zip(*iterables):
        yield func(*items)

Now, we can add a debug print to figure out what’s going on:

def my_map(func, *iterables):
    for items in zip(*iterables):
        print(items)
        yield func(*items)

Now, call the functions:

list(my_map(str.replace, "abc", "ab", "b"))

There is only one line output: ('a', 'a', 'b'). The for-loop only goes through once before finishing.

list(my_map(str.replace, "abc", "b", "c"))

Again, only one line, ('a', 'b', 'c'). The for-loop has gone through once again.

list(my_map(str.replace, "abc", "abc", "abc"))

Three lines output. This time the for-loop has gone round three times.

This is because the zip function stops yielding once the shortest iterable has been exhausted. In the first two cases, that is after one iteration. In the third one, it is after three.

You might want to check out the documentation of map as well.


Note: what you want is want is itertools.starmap:

import itertools

print(list(itertools.starmap(str.replace, ("abc", "ab", "b"))))
Answered By: The Thonnu
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.