python map over list of tuples

Viewed 405

I have list of tuples like this:

rounds = [('R', 'S'), ('S', 'R'), ('S', 'R'), ('R', 'P'), ('S', 'S'),
          ('P', 'S'), ('P', 'S'), ('S', 'P'), ('R', 'R'), ('R', 'S')]

that are simulating RPS game also i have a function like this:

def RPS_winner(weapon1, weapon2):
  if weapon1 == weapon2:
    return 0
  elif (weapon1, weapon2) in [('R', 'S'), ('P', 'S'), ('P', 'S')]:
    return 1
  else:
    return 2

how do i use map() to derive a list of the winners in those 10 rounds? i know is starting like this: list(map(RPS_winner, ....)

4 Answers

You don't need map for something like that, and in fact would gain readability by using a list comprehension:

>>> [RPS_winner(a, b) for a, b in rounds]
[1, 2, 2, 2, 0, 1, 1, 2, 0, 1]

Another possibility is to use itertools.starmap, which is designed precisely for that:

from itertools import starmap

list(starmap(RPS_winner, rounds))

And, of course, you can do the same by hand:

list(map(lambda ab: RPS_winner(*ab), rounds)

which, if you intend to use on very long list of rounds, would benefit being rewritten as:

def RPS_winner_star(ab):
    return RPS_winner(*ab)

list(map(RPS_winner_star, rounds))

Note

A justifiable reason to request the use of map or the like is that rounds is in fact not a list but another iterator. In that case, it's nice to obtain a new iterator that spits out winners as rounds go, without making lists. For example, you could then "pipe" that resulting iterator into a Counter:

irounds = generate_rounds(n=1_000_000)  # a generator
iwinner = map(RPS_winner_star, irounds)
score_board = Counter(iwinner)

Here is a full example and timings:

(Note: the generate_rounds generator is now predictably repetitive in an effort to reduce its own time in the overall measurements; we also now subtract the time spent in the generator in the time comparisons at the end).

import random
from collections import Counter
from itertools import starmap


def generate_rounds(n):
    choices = list('RPS')
    m = len(choices)
    for k in range(n):
        i = k % m
        j = (k // m) % m
        yield choices[i], choices[j]

def f_onlygen(n):
    for _ in generate_rounds(n):
        pass

def f_map(n):
    irounds = generate_rounds(n)  # a generator
    iwinner = map(RPS_winner_star, irounds)
    return Counter(iwinner)

def f_starmap(n):
    irounds = generate_rounds(n)  # a generator
    iwinner = starmap(RPS_winner, irounds)
    return Counter(iwinner)

def f_listmap(n):
    rounds = list(generate_rounds(n))
    winner = list(map(RPS_winner_star, rounds))
    return Counter(winner)

def f_listcomprehension(n):
    rounds = list(generate_rounds(n))
    winner = [RPS_winner(a, b) for a, b in rounds]
    return Counter(winner)

def f_comprehension(n):
    irounds = generate_rounds(n)
    winner = [RPS_winner(a, b) for a, b in rounds]
    return Counter(winner)

Measurements:

n = 1_000_000

t = {}
t['onlygen'] = %timeit -o f_onlygen(n)
t['map'] = %timeit -o f_map(n)
t['starmap'] = %timeit -o f_starmap(n)
t['listmap'] = %timeit -o f_listmap(n)
t['listcomprehension'] = %timeit -o f_listcomprehension(n)
t['comprehension'] = %timeit -o f_comprehension(n)

Results:

res = sorted([
    (k, v.average, v.average - t['onlygen'].average)
    for k, v in t.items()
], key=lambda tup: tup[2])
print(f'{"name":<17} {"total":<6}  above onlygen')
for name, tot, rel in res:
    print(f'{name:<17} {tot*1000:3.0f} ms, {rel*1000:3.0f} ms')
name              total   above onlygen
onlygen           172 ms,   0 ms
comprehension     235 ms,  62 ms
starmap           376 ms, 204 ms
map               432 ms, 260 ms
listcomprehension 470 ms, 298 ms
listmap           482 ms, 310 ms

Itertools provides starmap for this.

from itertools import starmap

rounds = [('R', 'S'), ('S', 'R'), ('S', 'R'), ('R', 'P'), ('S', 'S'),
          ('P', 'S'), ('P', 'S'), ('S', 'P'), ('R', 'R'), ('R', 'S')]

def RPS_winner(weapon1, weapon2):
    if weapon1 == weapon2:
        return 0
    elif (weapon1, weapon2) in [('R', 'S'), ('P', 'S'), ('P', 'S')]:
        return 1
    else:
        return 2

    
list(starmap(RPS_winner, rounds))
# [1, 2, 2, 2, 0, 1, 1, 2, 0, 1]

You can do this:

winners = list(map(lambda x: RPS_winner(*x), rounds))

starting like this: list(map(RPS_winner, ....)

If it shall start like that, it should probably end like this:

list(map(RPS_winner, *zip(*rounds)))

That gives map three arguments: The function, player 1 choices, player 2 choices.

Related