Python对元组列表进行映射

3
我有一个类似这样的元组列表:
rounds = [('R', 'S'), ('S', 'R'), ('S', 'R'), ('R', 'P'), ('S', 'S'),
          ('P', 'S'), ('P', 'S'), ('S', 'P'), ('R', 'R'), ('R', 'S')]

这些内容涉及模拟 RPS 游戏的 IT 技术。同时,我有一个类似于下面这样的函数:

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, ....)

4个回答

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

2

像这样的操作并不需要使用 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)

如果您打算使用非常长的轮次列表,则最好将其重写为:

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

我知道我可以使用你所写的内容,但问题要求使用 map。 - yoj2011
你大部分时间是在测量generate_rounds的工作时间吗?这似乎不太好。 - no comment
好的,我记下了这个要点。 - Pierre D

0
你可以这样做:
winners = list(map(lambda x: RPS_winner(*x), rounds))

没有起作用。它显示:“map”对象不可调用。 - yoj2011
另外,可以使用 itertools.starmap - Paul M.
@yoj2011 这是有效的。也许你以某种方式重新定义了映射对象…… - Riccardo Bucco

0
如果要以这种方式开始,它可能应该以这种方式结束:list(map(RPS_winner, ....)
list(map(RPS_winner, *zip(*rounds)))

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接