Demetri Pananos
Demetri Pananos

Reputation: 7404

How can I find where a two dimensional array satisfies some conditional?

Background

If you've ever played risk, you're familiar with rolling dice to determine the outcome of a battle. If not, here is a brief synopsis:

The rules for determining how many dice a player may shake are as follows:

  1. The attacker may shake one less die than the number of armies on his country, to a maximum of three.
  2. The defender may shake as many dice as the number of armies on his country, to a maximum of two.

The rules for deciding the outcome of a particular throw of the dice are as follows:

  1. The highest attacker die is compared against the highest defender die. Whoever has the lower number loses one army. Ties go to the defender.
  2. The procedure is repeated for the second-highest dice.

In cases where either the attacker or the defender only rolls a single die, a total of only one army will be lost; in all other cases a total of two armies will be lost.

I'd like to analyze the frequency of wins/losses for each pair of die rolls. I could loop through all the possibilities, but I'm interested in using ndarrays and slicing to do the calculations.

For instance, consider the scenario where attacker rolls one die and defender rolls one die. We can arrange all possible outcomes in an ndarray.

In [1]: import numpy as np

In [2]: x = np.tile(np.arange(1,7),(6,1))

In [3]: x
Out[3]: 
array([[1, 2, 3, 4, 5, 6],
       [1, 2, 3, 4, 5, 6],
       [1, 2, 3, 4, 5, 6],
       [1, 2, 3, 4, 5, 6],
       [1, 2, 3, 4, 5, 6],
       [1, 2, 3, 4, 5, 6]])

If defender rolls are the columns and attacker rolls are the rows, then the the area where the defender wins is the upper triangular portion of this array

defence_win_region= array([[1, 1, 1, 1, 1, 1],
                           [0, 1, 1, 1, 1, 1],
                           [0, 0, 1, 1, 1, 1],
                           [0, 0, 0, 1, 1, 1],
                           [0, 0, 0, 0, 1, 1],
                           [0, 0, 0, 0, 0, 1]])

Question

How can I obtain an array like defence_win_region from an array like x? How can I extend that method to arrays of higher dimensions in order to analyze 2-1,3-1,3-2,1-2 rolls?

Upvotes: 2

Views: 196

Answers (2)

Daniel F
Daniel F

Reputation: 14399

import numpy as np
import scipy
import itertools

def riskRoll(ad,dd):  #Never gonna give you up . . . 
    minD=min(ad,dd)
    a=np.array(list(itertools.combinations_with_replacement(
        np.arange(6,0,-1),ad)))
    d=np.array(list(itertools.combinations_with_replacement(
        np.arange(6,0,-1),dd)))
    na=np.array([scipy.misc.factorial(ad)/np.prod(
        scipy.misc.factorial(np.unique(roll,return_counts=True)[1])) for roll in a])
    nd=np.array([scipy.misc.factorial(dd)/np.prod(
        scipy.misc.factorial(np.unique(roll,return_counts=True)[1])) for roll in d])
    a_wins= np.sum(p.where(a[None,:,0:minD]>d[:,None,0:minD],1,-1), axis=-1)+ad-dd
    nd_count=na[:,None]*nd[None,:]
    return a_wins*nd_count

How it works:

  1. Output is a matrix of size C((6,ad)) x C((6,dd)) with all combinations in descending order
  2. Values in the matrix a_wins are the number of attacker wins, negative numbers are defensive wins. This includes non-contested dice.
  3. Values in nd_count are weighting factors, equal to the number of times that combination will exist in a 6**ad x 6**dd combination matrix
  4. Final output is the product of the two, showing wins weighted by occurance. sum and divide by 6**(ad+dd) to get expected wins/losses

Upvotes: 1

user6655984
user6655984

Reputation:

A short answer to your specific question about 2D array is:

np.where(x >= np.transpose(x), 1, 0)

But to push this further, you need tools other than tile. The natural way of preparing the sample space is meshgrid:

die = np.arange(1, 7)
a, d = np.meshgrid(die, die)

Now a and d are 2d arrays holding the scores of the attacker and defender. As before, np.where(a <= d, 1, 0) yields a 1-0 table (transpose of the previous one, but that's the matter of choice).

Let's see what happens when each rolls two dice:

a1, a2, d1, d2 = np.meshgrid(die, die, die, die)

Here is where the defender wins the first round (comparing highest scores):

np.where(np.maximum(a1, a2) <= np.maximum(d1, d2), 1, 0)

And here is the comparison of minima, which are 2nd highest scores:

np.where(np.minimum(a1, a2) <= np.minimum(d1, d2), 1, 0)

Both are 4D arrays, since the sample space is 4-dimensional.

Things get more complicated when someone throws 3 or more dice, because the selection of "2nd highest" isn't a straightforward operation like maximum. It can be done by stacking the dice of a player and sorting along the new axis, then slicing:

a1, a2, a3, d1, d2 = np.meshgrid(die, die, die, die, die)  # 3-2 roll 
attack = np.stack([a1,a2,a3])       # stack attacker into a 6D array
attack.sort(axis=0)                 # sort their scores
attack_max = attack[-1,:,:,:,:,:]   # maximum score
attack_2nd = attack[-2,:,:,:,:,:]   # 2nd highest attack score

Now we compare as before:

defender_wins_1 = np.where(attack_max <= np.maximum(d1, d2), 1, 0)
defender_wins_2 = np.where(attack_2nd <= np.minimum(d1, d2), 1, 0)

Upvotes: 0

Related Questions