Scoring system of a pool of dice I want to do some Monte Carlo simulations on a system based on a pool of dice. For each trial, a number of dies are rolled, and the winner is the highest value, getting as many points as dice are over the second, including ties.

Examples:


[1, 2], [5, 1]  One point for the second (5 is the only value over 2).
[20, 12, 20, 14], [17, 15, 19, 18] Two points for the first one (two 20 over 19).
[20, 12, 20, 14], [17, 15, 20, 18] Two points for the first one (one tie at 20, one die over 18).


Here is my algorithm. Can you think of a better way?

from collections import Counter
import numpy as np


def check_rolls(one, other):
    one_c = Counter(one)
    other_c = Counter(other)
    ties = Counter()

    # Remove ties:
    for tie_val in set(one_c.keys()).intersection(other_c.keys()):
        toremove = min(one_c[tie_val], other_c[tie_val])
        one_c[tie_val] -= toremove
        other_c[tie_val] -= toremove
        ties[tie_val] = toremove

        # Clear if 0
        if one_c[tie_val] == 0:
            del one_c[tie_val]
        if other_c[tie_val] == 0:
            del other_c[tie_val]

    max_one = max(one_c.keys()) if one_c else 0
    max_other = max(other_c.keys()) if other_c else 0

    winner = max_one &gt; max_other
    threshold = min(max_one, max_other)

    if winner:
        # One wins
        result = sum(val for key, val in one_c.iteritems()
                   if key &gt; threshold) + \
                   sum(val for key, val in ties.iteritems() 
                       if key &gt;= threshold)
    else:
        result = -(sum(val for key, val in other_c.iteritems()
                    if key &gt; threshold) +
                    sum(val for key, val in ties.iteritems()
                        if key &gt;= threshold))
    return result

def roll(d_player, d_gm):
    return check_rolls(np.random.randint(1, 21, size=d_player),
                       np.random.randint(1, 21, size=d_gm))

def test():
    assert check_rolls([1, 1, 2], [1, 1]) == 3
    assert check_rolls([1, 2, 4, 5], [3, 4]) == 2
    assert check_rolls([1, 2, 4, 5], [3, 5]) == 2
    assert check_rolls([1, 2, 4, 5], [3, 6]) == -1
    assert check_rolls([20, 12, 20, 14], [17, 15, 20, 5, 18, 2]) == 2
    assert check_rolls([20, 10, 2, 4], [ 5, 20, 6, 4, 19, 3]) == -2


I am just using it for a quick and dirty script plotting histograms for different configurations. Performance improvements are welcomed, but they are not critical. Style and integration are of low priority.
Disclaimer : this is not quite an answer but more than a comment.

You've written tests and I'd like to start by thanking your for this. This definitly makes reviewing easier.

Good think to know if that you can make easily double the amount of test you have without thinking too much.

Indeed, it seems like by inversing the parameters of your function, the return value should be the opposite value.

You can write this :

for d1, d2, res in [
        ([1, 1, 2], [1, 1],  3),
        ([1, 2, 4, 5], [3, 4],  2),
        ([1, 2, 4, 5], [3, 5],  2),
        ([1, 2, 4, 5], [3, 6],  -1),
        ([20, 12, 20, 14], [17, 15, 20, 5, 18, 2],  2),
        ([20, 10, 2, 4], [ 5, 20, 6, 4, 19, 3],  -2)
    ]:
    assert check_rolls(d1, d2) == res
    assert check_rolls(d2, d1) == - res


Also, I thought I'd take this chance to test check_rolls(d1, d1) and it seems like it always returns - len(d1). Shouldn't you handle perfect tie situation in a different way ?



Edit : still not quite a review but you got me interested and I wondered if this could be written in a more simple way. Here's what I got translating my understanding of the problem into Python code :

def check_rolls(d1, d2):
    c1, c2 = Counter(d1), Counter(d2)
    res = 0 # nb points
    winner = 0 # player winning so far : 1 if 1, 2 if 2, 0 otherwise
    for key in reversed(sorted(set(c1) | set(c2))):
        v1, v2 = c1[key], c2[key]
        if v1 == v2:
            res+= v1 # ties count for whoever is winning
        else:
            local_winner = 1 if v1 &gt; v2 else 2
            if winner and local_winner != winner:
                break
            else:
                winner = local_winner
                res+= max(v1, v2)
    return res if winner == 1 else -res