Numpy minimum for a given column - python

I have a two-dimensional numpy array of data, holding symmetric information about pairs of elements I'm tracking, very much like a round-robin bracket in a tournament. Keeping with that analogy, each player occupies one row with the column data being their score against a given opponent. For "legacy" reasons, all scores will be positive numbers except for the score against oneself, which will be 0.
Let's say I want to find the worst score of a given player, returning both the score and the opponent they played for that score. How might I do this?
The mediocre version might look like:
minimum = float('inf')
min_opp = None
for opponent_id in player_ids:
if opponent_id != player_id:
score = match_matrix[player_id, opponent_id]
if score < minimum:
minimum = score
min_opp = opponent_id
return minimum, min_opp
But that's not using the power of numpy at all. I feel like there should be an easy solution, but I cannot seem to find it.
score = np.min(match_matrix[player, :])
gives the self-score, and I can't quite make the code from this answer work correctly.
Thanks!
EDIT: This answer provides good ideas, but only gets the minimum of the entire array, not a single row.

You can select the given row, mask out the self-score and return the minimum of the remainder. Basically what you did but with an extra masking step. I would also recommend using np.argmin instead of np.min because it returns the index of the minimum, which is much more informative in this case:
mask = np.ones(match_matrix.shape(1), dtype=np.bool)
mask[player] = False
opponent = np.argmin(match_matrix[player, mask])
if opponent >= player:
opponent += 1
score = match_matrix[player, opponent]

Here's one way to do it. np.min() will give you the minimum value in a row, and np.where can tell you where that value is.
grid = np.random.randint(0,10,[5,5]) #Random matchups of 5x5 players
player = 1
low_score = np.min(grid[player][grid[player]>0]) #Only look at positive scores
opponent = np.where(grid[player]==low_score)[0]
Here, opponents will be an array of opponents, in case player got the same low score against multiple opponents.

Related

Using variable names for 2d matrix elements for readability

While solving Leetcode problems I've been trying to make my answers as easily intelligible as possible, so I can quickly glance at them later and make sense of them. Toward that end I assigned variable names to indices of interest in a 2D list. When I see "matrix[i][j+1]" and variations thereof repeatedly, I sometimes lose track of what I'm dealing with.
So, for this problem: https://leetcode.com/problems/maximal-square/
I wrote this code:
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
maximum = 0
for y in range(len(matrix)):
for x in range(len(matrix[0])):
#convert to integer from string
matrix[y][x] = int(matrix[y][x])
#use variable for readability
current = matrix[y][x]
#build largest square counts by checking neighbors above and to left
#so, skip anything in first row or first column
if y!=0 and x!=0 and current==1:
#assign variables for readability. We're checking adjacent squares
left = matrix[y][x-1]
up = matrix[y-1][x]
upleft = matrix[y-1][x-1]
#have to use matrix directly to set new value
matrix[y][x] = current = 1 + min(left, up, upleft)
#reevaluate maximum
if current > maximum:
maximum = current
#return maximum squared, since we're looking for largest area of square, not largest side
return maximum**2
I don't think I've seen people do this before and I'm wondering if it's a bad idea, since I'm sort of maintaining two versions of a value.
Apologies if this is a "coding style" question and therefore just a matter of opinion, but I thought there might be a clear answer that I just haven't found yet.
It is very hard to give a straightforward answer, because it might vary from person to person. Let me start from your queries:
When I see "matrix[i][j+1]" and variations thereof repeatedly, I sometimes lose track of what I'm dealing with.
It depends. People who have moderate programming knowledge should not be confused by seeing a 2-D matrix in matrix[x-pos][y-pos] shape. Again, if you don't feel comfortable, you can use the way you have shared here. But, you should try to adopt and be familiar with this type of common concepts parallelly.
I don't think I've seen people do this before and I'm wondering if it's a bad idea, since I'm sort of maintaining two versions of a value.
It is not a bad idea at all. It is "Okay" as long as you are considering to do this for your comfort. But, if you like to share your code with others, then it might not be a very good idea to use something that is too obvious. It might reduce the understandability of your code to others. But, you should not worry with the maintaining two versions of a value, as long as the extra memory is constant.
Apologies if this is a "coding style" question and therefore just a matter of opinion, but I thought there might be a clear answer that I just haven't found yet.
You are absolutely fine by asking this question. As you mentioned, it is really just a matter of opinion. You can follow some standard language guideline like Google Python Style Guide. It is always recommended to follow some standards for this type of coding style things. Always keep in mind, a piece of good code is always self-documented and putting unnecessary comments sometimes make it boring. Also,
Here I have shared my version of your code. Feel free to comment if you have any question.
# Time: O(m*n)
# Space: O(1)
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
"""Given an m x n binary matrix filled with 0's and 1's,
find the largest square containing only 1's and return its area.
Args:
matrix: An (m x n) string matrix.
Returns:
Area of the largest square containing only 1's.
"""
maximum = 0
for x in range(len(matrix)):
for y in range(len(matrix[0])):
# convert current matrix cell value from string to integer
matrix[x][y] = int(matrix[x][y])
# build largest square side by checking neighbors from up-row and left-column
# so, skip the cells from the first-row and first-column
if x != 0 and y != 0 and matrix[x][y] != 0:
# update current matrix cell w.r.t. the left, up and up-left cell values respectively
matrix[x][y] = 1 + min(matrix[x][y-1], matrix[x-1][y], matrix[x-1][y-1])
# re-evaluate maximum square side
if matrix[x][y] > maximum:
maximum = matrix[x][y]
# returning the area of the largest square
return maximum**2

Random Integer inside range with probability (Python) [duplicate]

This question already has answers here:
Random weighted choice
(7 answers)
Closed 8 years ago.
I am making a text-based RPG. I have an algorithm that determines the damage dealt by the player to the enemy which is based off the values of two variables. I am not sure how the first part of the algorithm will work quite yet, but that isn't important.
(AttackStrength is an attribute of the player that represents generally how strong his attacks are. WeaponStrength is an attribute of swords the player wields and represents generally how strong attacks are with the weapon.)
Here is how the algorithm will go:
import random
Damage = AttackStrength (Do some math operation to WeaponStrength) WeaponStrength
DamageDealt = randrange(DamageDealt - 4, DamageDealt + 1) #Bad pseudocode, sorry
What I am trying to do with the last line is get a random integer inside a range of integers with the minimum bound as 4 less than Damage, and the maximum bound as 1 more than Damage. But, that's not all. I want to assign probabilities that:
X% of the time DamageDealt will equal Damage
Y% of the time DamageDealt will equal one less than Damage
Z% of the time DamageDealt will equal two less than Damage
A% of the time DamageDealt will equal three less than Damage
B% of the time DamageDealt will equal three less than Damage
C% of the time DamageDealt will equal one more than Damage
I hope I haven't over-complicated all of this thank you!
I think the easiest way to do random weighted probability when you have nice integer probabilities like that is to simply populate a list with multiple copies of your choices - in the right ratios - then choose one element from it, randomly.
Let's do it from -3 to 1 with your (original) weights of 10,10,25,25,30 percent. These share a gcd of 5, so you only need a list of length 20 to hold your choices:
choices = [-3]*2 + [-2]*2 + [-1]*5 + [0]*5 + [1]*6
And implementation done, just choose randomly from that. Demo showing 100 trials:
trials = [random.choice(choices) for _ in range(100)]
[trials.count(i) for i in range(-3,2)]
Out[18]: [11, 7, 27, 22, 33]
Essentially, what you're trying to accomplish is simulation of a loaded die: you have six possibilities and want to assign different probabilities to each one. This is a fairly interesting problem, mathematically speaking, and here is a wonderful piece on the subject.
Still, you're probably looking for something a little less verbose, and the easiest pattern to implement here would be via roulette wheel selection. Given a dictionary where keys are the various 'sides' (in this case, your possible damage formulae) and the values are the probabilities that each side can occur (.3, .25, etc.), the method looks like this:
def weighted_random_choice(choices):
max = sum(choices.values())
pick = random.uniform(0, max)
current = 0
for key, value in choices.items():
current += value
if current > pick:
return key
Suppose that we wanted to have these relative weights for the outcomes:
a = (10, 15, 15, 25, 25, 30)
Then we create a list of partial sums b and a function c:
import random
b = [sum(a[:i+1]) for i,x in enumerate(a)]
def c():
n = random.randrange(sum(a))
for i, v in enumerate(b):_
if n < v: return i
The function c will return an integer from 0 to len(a)-1 with probability proportional to the weights specified in a.
This can be a tricky problem with a lot of different probabilities. Since you want to impose probabilities on the outcomes it's not really fair to call them "random". It always helps to imagine how you might represent your data. One way would be to keep a tuple of tuples like
probs = ((10, +1), (30, 0), (25, -1), (25, -2), (15, -3))
You will notice I have adjusted the series to put the highest adjustment first and so on. I have also removed the duplicate "15, -3) that your question implies because (I imagine) of a line duplicated by accident. One very useful test is to ensure that your probabilities add up to 100 (since I've represented them as integer percentages). This reveals a data fault:
>>> sum(prob[0] for prob in probs)
105
This needn't be an issue unless you really want your probabilities to sum to a sensible value. If this isn't necessary you can just treat them as weightings and select random numbers from (0, 104) instead of (0, 99). This is the course I will follow, but the adjustment should be relatively simple.
Given probs and a random number between 0 and (in your case) 104, you can iterate over the probs structure, accumulating probabilities until you find the bin this particular random number belongs to. This would look (something) like:
def damage_offset(N):
prob = random.randint(0, N-1)
cum_prob = 0
for prob, offset in probs:
cum_prob += prob
if cum_prob >= prob:
return offset
This should always terminate if you get your data right (hence my paranoid check on your weightings - I've been doing this quite a while).
Of course it's often possible to trade memory for speed. If the above needs to work faster then it's relatively easy to create a structure that maps random integer choices direct to their results. One way to construct such a mapping would be
damage_offsets = []
for i in range(N):
damage_offsets.append(damage_offset(i))
Then all you have to do after you've picked your random number r between 1 and N is to look up damage_offsets[r-1] for the particular value of r1 and you have created an O(1) operation. As I mentioned at the start, this isn't likely going to be terribly useful unless your probability list becomes huge (but if it does then you really will need to to avoid O(N) operations when you have large N for the number of probability buckets).
Apologies for untested code.

connect 4 minimax algorithm: one for loop

I'm trying to write the minimax algorithm in python with one for loop (yes I know wikipedia says the min and max players are often treated separately), and I'm using the variable turn to keep track of whether the min or max player is currently exploring options. I think, however, that at present the code wrongly evaluates for X when it is the O player's turn and O when it is the X player's turn.
Here's the source (p12) : http://web.cs.wpi.edu/~rich/courses/imgd4000-d10/lectures/E-MiniMax.pdf
Things you might be wondering about:
b is a list of lists; 0 denotes an available space
evaluate is used both for checking for a victory (by default) as well as for scoring the board for a particular player (we look for places where the value of a cell on the board ).
makeMove returns the row of the column the piece is placed in (used for subsequent removal)
Any help would be very much appreciated. Please let me know if anything is unclear.
def minMax(b, turn, depth=0):
player, piece = None, None
best, move = None, -1
if turn % 2 == 0 : # even player is max player
player, piece = 'max', 'X'
best, move = -1000, -1
else :
player, piece = 'min', 'O'
best, move = 1000, -1
if boardFull(b) or depth == MAX_DEPTH:
return evaluate(b, False, piece)
for col in range(N_COLS):
if possibleMove(b, col) :
row = makeMove(b, col, piece)
turn += 1 # now the other player's turn
score = minMax(b, turn, depth+1)
if player == 'max':
if score > best:
best, move = score, col
else:
if score < best:
best, move = score, col
reset(b, row, col)
return move
#seaotternerd. Yes I was wondering about that. But I'm not sure that is the problem. Here is one printout. As you can see, X has been dropped in the fourth column by AI but is evaluating from the min player's perspective (it counts 2 O units in the far right column).
Here's what the evaluate function determines, depending on piece:
if piece == 'O':
return best * -25
return best * 25
You are incrementing turn every time that you find a possible move and not undoing it. As a result, when control returns to a given minMax call, turn is 1 greater than it was before. Then, the next time your program finds a possible move, it increments turn again. This will cause the next call to minMax to select the wrong player as the current one. Overall, I believe this will result in the board getting evaluated for the wrong player approximately half the time. You can fix this by adding 1 to turn in the recursive call to minMax(), rather than by changing the value stored in the variables:
row = makeMove(b, col, piece)
score = minMax(b, turn+1, depth+1)
EDIT: Digging deeper into your code, I'm finding a number of additional problems:
MAX_DEPTH is set to 1. This will not allow the ai to see its own next move, instead forcing it to make decisions solely based on getting in the way of the other player.
minMax() returns the score if it has reached MAX_DEPTH or a win condition, but otherwise it returns a move. This breaks propagation of the score back up the recursion tree.
This is not critical, but it's something to keep in mind: your board evaluation function only takes into account how long a given player's longest string is, ignoring how the other player is doing and any other factors that may make one placement better than another. This mostly just means that your AI won't be very "smart."
EDIT 2: A big part of the problem with the way that you're keeping track of min and max is in your evaluation function. You check to see if each piece has won. You are then basing the score of that board off of who the current player is, but the point of having a min player and a max player is that you don't need to know who the current player is to evaluate the board. If max has won, the score is infinity. If min has won, the score is -infinity.
def evaluate(b, piece):
if evaluate_aux(b, True, 'X'):
return 100000
if evaluate_aux(b, True, 'O'):
return -100000
return evaluate_aux(b, False, piece)
In general, I think there is a lot that you could do to make your code cleaner and easier to read, which would make it a lot easier to detect errors. For instance, if you are saying that "X" is always max and "Y" is always min, then you don't need to bother keeping track of both player and piece. Additionally, having evaluate_aux sometimes return a boolean and sometimes return an int is confusing. You could just have it count the number of each piece in a row, for instance, with contiguous "X"s counting positive and contiguous "O"s counting negative and sum the scores; an evaluation function isn't supposed to be from one player's perspective or the other. Obviously you would still need to have a check for win conditions in there. This would also address point 3.
It's possible that there are more problems, but like I said, this code is not particularly easy to wade through. If you fix the things that I've already found and clean it up, I can take another look.

count number of basketball plays when given the final score

Given the final score of a basketball game, how i can count the number of possible scoring sequences that lead to the final score.
Each score can be one of: 3 point, 2 point, 1 point score by either the visiting or home team. For example:
basketball(3,0)=4
Because these are the 4 possible scoring sequences:
V3
V2, V1
V1, V2
V1, V1, V1
And:
basketball(88,90)=2207953060635688897371828702457752716253346841271355073695508308144982465636968075
Also I need to do it in a recursive way and without any global variables(dictionary is allowed and probably is the way to solve this)
Also, the function can get only the result as an argument (basketball(m,n)).
for those who asked for the solution:
basketballDic={}
def basketball(n,m):
count=0;
if n==0 and m==0:
return 1;
if (n,m) in basketballDic:
return basketballDic[(n,m)]
if n>=3:
count+= basketball(n-3,m)
if n>=2:
count+= basketball(n-2,m)
if n>=1:
count+= basketball(n-1,m)
if m>=3:
count+= basketball(n,m-3)
if m>=2:
count+= basketball(n,m-2)
if m>=1:
count+= basketball(n,m-1)
basketballDic[(n,m)]=count;
return count;
When you're considering a recursive algorithm, there are two things you need to figure out.
What is the base case, where the recursion ends.
What is the recursive case. That is, how can you calculate one value from one or more previous values?
For your basketball problem, the base case is pretty simple. When there's no score, there's exactly one possible set of baskets that has happened to get there (it's the empty set). So basketball(0,0) needs to return 1.
The recursive case is a little more tricky to think about. You need to reduce a given score, say (M,N), step by step until you get to (0,0), counting up the different ways to get each score on the way. There are six possible ways for the score to have changed to get to (M,N) from whatever it was previously (1, 2 and 3-point baskets for each team) so the number of ways to get to (M,N) is the sum of the ways to get to (M-1,N), (M-2,N), (M-3,N), (M,N-1), (M,N-2) and (M,N-3). So those are the recursive calls you'll want to make (after perhaps some bounds checking).
You'll find that a naive recursive implementation takes a very long time to solve for high scores. This is because it calculates the same values over and over (for instance, it may calculate that there's only one way to get to a score of (1,0) hundreds of separate times). Memoization can help prevent the duplicate work by remembering previously calculated results. It's also worth noting that the problem is symmetric (there are the same number of ways of getting a score of (M,N) as there are of getting (N,M)) so you can save a little work by remembering not only the current result, but also its reverse.
There are two ways this can be done, and neither come close to matching your specified outputs. The less relevant way would be to count the maximum possible number of scoring plays. Since basketball has 1 point scores, this will always be equal to the sum of both inputs to our basketball() function. The second technique is counting the minimum number of scoring plays. This can be done trivially with recursion, like so:
def team(x):
if x:
score = 3
if x < 3:
score = 2
if x < 2:
score = 1
return 1 + team(x - score)
else:
return 0
def basketball(x, y):
return team(x) + team(y)
Can this be done more tersely and even more elegantly? Certainly, but this should give you a decent starting point for the kind of stateless, recursive solution you are working on.
tried to reduce from the given result (every possible play- 1,2,3 points) using recursion until i get to 0 but for that i need global variable and i cant use one.
Maybe this is where you reveal what you need. You can avoid a global by passing the current count and/or returning the used count (or remaining count) as needed.
In your case I think you would just pass the points to the recursive function and have it return the counts. The return values would be added so the final total would roll-up as the recursion unwinds.
Edit
I wrote a function that was able to generate correct results. This question is tagged "memoization", using it gives a huge performance boost. Without it, the same sub-sequences are processed again and again. I used a decorator to implement memoization.
I liked #Maxwell's separate handling of teams, but that approach will not generate the numbers you are looking for. (Probably because your original wording was not at all clear, I've since rewritten your problem description). I wound up processing the 6 home and visitor scoring possibilities in a single function.
My counts were wrong until I realized what I needed to count was the number of times I hit the terminal condition.
Solution
Other solutions have been posted. Here's my (not very readable) one-liner:
def bball(vscore, hscore):
return 1 if vscore == 0 and hscore == 0 else \
sum([bball(vscore-s, hscore) for s in range(1,4) if s <= vscore]) + \
sum([bball(vscore, hscore-s) for s in range(1,4) if s <= hscore])
Actually I also have this line just before the function definition:
#memoize
I used Michele Simionato's decorator module and memoize sample. Though as #Blckknght mentioned the function is commutative so you could customize memoize to take advantage of this.
While I like the separation of concerns provided by generic memoization, I'm also tempted to initialize the cache with (something like):
cache = {(0, 0): 1}
and remove the special case check for 0,0 args in the function.

How to make a selective RNG for a game in Python?

This is almost certainly a very novice question, but being as I am a complete novice, I'm fine with that.
To put it simply, I'd like to know how to make a loot drop system in a simple game, where when you achieve a certain objective, you have a chance of getting certain objects more than others. If there are any open-source python games that have this, please refer me to them.
Here is what I know how to do: given a sample [A,B,C,D,E,F], select 3 items.
This is really simple and easy, however, what do I do when I would like to have somethings from the sample be selected more often than others, ie: given sample [A,B,C,D,E,F] have 3 be selected, without repeats, but have A be selected 30% of the time, B 25%, C 20%, D 15%, E 5%, F 5%.
Or, perhaps even better, have no limit (or a ranged limit, eg. 3-5 items) on the amount selected, but have each item in the sample be selected at a different rate and without repeats, so that I could do A 20%, B 20%, C 15%, D 10%, E 2%, F 1%.
Hope this makes sense.
Here's an easy, lazy way to do it.
Given a list of (item,weight) pairs.
loot = [ (A,20), (B,20), (C,15), (D,10), (E,2), (F,1) ]
Note, the weights don't have to add to anything in particular, they just have to be integers.
One-time preparation step.
choices = []
for item, weight in loot:
choices.extend( [item]*weight )
Now it's just random.choice( choices ).
You threw me off a little when you characterized this question as a "very novice" one. It's not as simple as it looks, depending on what kind of behavior you want. BarsMonster's answer is a good one if you don't mind that a lucky player can win all the items and an unlucky player can come away with nothing.
If you want to always select a certain number of items, then I would go with S.Lott's method of picking one item, but use it repeatedly. If you don't want to allow the same item to be selected more than once, you have to remove the chosen item from loot and then rebuild choices between selections. For example (very rough pseudocode):
items_won = random.randint(3, 5)
for i in range(items_won):
item_won = s_lott_weighted_selection()
inventory.add(item_won)
loot.remove(item_won)
An alternative to S.Lott's weighted selection.
Warning - untested code.
import random
def weighted_selection(weights):
"""returns an index corresponding to the weight of the item chosen"""
total_sum = sum(weights)
rnd = random.uniform(0, total_sum)
cumulative_sum = 0
for (idx, weight) in enumerate(weights):
if rnd <= cumulative_sum + weight:
return idx
cumulative_sum += weight
assert(0) # should never get here
weights = [30, 25, 20, 15, 5, 5]
# example of choosing 1 - will return value from 0 to 5
choice = weighted_selection(weights)
# example of choosing 3 such values without repeats
choices = []
for n in range(3):
new_choice = weighted_selection(weights)
del weights[new_choice]
choices.append(new_choice)
You may want to wrap the selection-without-replacement code at the end in some sort of wrapper that ensures the number of unique choices you make never exceeds the number of options available.
pseudocode:
if(random()<0.2)addloot(a);
if(random()<0.15)addloot(b);
if(random()<0.1)addloot(c);
if(random()<0.02)addloot(d);
if(random()<0.01)addloot(e);
where random is a random number from 0 to 1. This is how it works in all MMORPG.
Here's a nice recipe if you want a smooth gradation of likelihoods without making an enormous list to sample from:
class WeightedRandom(random.Random):
"""All numbers are random, but some are more random than others.
Initialise with a weighting curve gamma. gamma=1 is unweighted, >1 returns
the lower numbers more often, <1 prefers the higher numbers.
"""
def __init__(self, gamma):
self.gamma= gamma # 1 is unweighted, >1 pushes values downwards
random.Random.__init__(self)
def random(self):
return random.Random.random(self)**self.gamma
# Override the standard sample method, whose pool-based 'optimisation' cocks
# up weighted sampling. We know result set is small, so no need for dict
# lookup either.
#
def sample(self, population, k):
if k>=len(population):
return population
indexes= []
for _ in range(k):
while True:
index= int(self.random()*len(population))
if index not in indexes:
break
indexes.append(index)
return [population[index] for index in indexes]
>>> r= WeightedRandom(0.5)
>>> r.sample(range(100), 3)
[86, 98, 81]

Categories