python 映射元组列表

python map over list of tuples

我有这样的元组列表:

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

正在模拟RPS游戏 我也有这样的功能:

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

我如何使用 map() 得出这 10 轮的获胜者名单? 我知道是这样开始的:list(map(RPS_winner, ....)

你可以这样做:

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

你不需要 map 这样的东西,实际上可以通过使用列表理解来提高可读性:

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

另一种可能性是使用 itertools.starmap,它正是为此而设计的:

from itertools import starmap

list(starmap(RPS_winner, rounds))

当然,您也可以手动完成同样的操作:

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

如果您打算在非常长的 rounds 列表中使用,将其重写为:

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

list(map(RPS_winner_star, rounds))

备注

请求使用 map 等的正当理由是 rounds 实际上不是列表而是另一个迭代器。在那种情况下,获得一个新的迭代器是很好的,该迭代器可以随着 rounds 的进行而吐出获胜者,而无需列出列表。例如,您可以将生成的迭代器“管道化”为 Counter:

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

这是一个完整的例子和时间安排:

(注意generate_rounds 生成器现在可预测地重复,以努力减少其自身在整体测量中的时间;我们现在还减去花费在生成器在最后的时间比较中)。

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)

测量值:

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)

结果:

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 为此提供 starmap

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]

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

如果这样开始,大概应该这样结束:

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

这给出了 map 三个参数:函数、玩家 1 的选择、玩家 2 的选择。