Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python map over list of tuples

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

like image 312
yoj2011 Avatar asked Dec 30 '21 19:12

yoj2011


People also ask

How do I map a list to another list in Python?

The map() function iterates over all elements in a list (or a tuple), applies a function to each and returns a new iterator of the new elements. In this syntax, fn is the name of the function that will call on each element of the list. In fact, you can pass any iterable to the map() function, not just a list or tuple.

How do you apply a map to a list?

Convert to a list. If you want to convert the result of map() to a list, use list() . For example, apply len() to a list of strings to convert it to a list of the number of characters. In the second argument of map() , not only a list but also an iterable such as a tuple or range can be specified.

How do you apply a function to a list of tuples?

The best way to apply a function to each element of a tuple is the Python built-in map(function, iterable) function that takes a function and an iterable as arguments and applies the function to each iterable element. An alternate way is to use list comprehension.


2 Answers

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]
like image 109
Mark Avatar answered Oct 10 '22 21:10

Mark


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
like image 40
Pierre D Avatar answered Oct 10 '22 22:10

Pierre D