Home > Software design >  Weird map behavior when using str.replace
Weird map behavior when using str.replace

Time:08-28

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.

CodePudding user response:

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']

CodePudding user response:

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"))))
  • Related