Python - Diagonal Match in a 2-Dimensional Array - python

I need help with diagonal matching in a 2D Array in Python when 3 or more elements are the same.
In the scenario below, the "2"'s formulate a diagonal match. Thus, I want to make them "-2" since they form a match.
Given
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[3, 1, 2, 0, 0], # 2 at index 2
[1, 2, 1, 0, 0], # 2 at index 1
[2, 3, 3, 0, 0]] # 2 at index 0
Result
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[3, 1,-2, 0, 0], # -2 now at index 2
[1, -2, 1, 0, 0], # -2 now at index 1
[-2, 3, 3, 0, 0]] # -2 now at index 0
I presume they are multiple directions for a diagonal match (from the left/right) so I want to cover those cases. Ideally, I want to do this without importing any module.
Thanks (:

Here is something to start from, assuming length of the rows is the same and you want full length diagonals:
#get diagonals, number of diagonals is 2 * additional rows, where
#multiplication by 2 is for left and right diagonal
diagonals = [[] for asymmetry in range((len(test)%len(test[0])+1)*2)]
for i, diagonal in enumerate(diagonals):
for rowidx in range(len(test[0])):
#if left diagonal
if i % 2 == 0:
diagonal.append(test[i//2+rowidx][rowidx])
#if right diagonal
elif i % 2 == 1:
diagonal.append(test[i//2+rowidx][len(test[0])-rowidx-1])
#Now check for same values in diagonals
for i, diagonal in enumerate(diagonals):
for value in set(diagonal):
#indeces of repeating values:
valueidx = [j for j, val in enumerate(diagonal) if val==value]
#if value repeats and is not 0 and that they're ordered:
if len(valueidx) > 2 and value is not 0 and all(a-1 == b for a, b in zip(valueidx[1:], valueidx[:-1])):
for idx in valueidx:
#case of left diagonal
if i % 2 == 0:
test[i//2 + idx][idx] *=-1
#case of right diagonal
if i % 2 == 1:
test[i//2 + idx][len(test[0])-idx-1] *=-1
It should be possible to simplify this code by a lot, but here is something to start from. Using numpy would simplify the code by a lot, using numpy.diag and simple expressions for replacing the values.
test = [[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[3, 1, 2, 0, 0],
[1, 2, 1, 0, 0],
[2, 3, 3, 0, 0],
[0, 1, 3, 0, 1]]
after running above script:
test
[[ 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0],
[ 3,-1,-2, 0, 0],
[ 1,-2,-1, 0, 0],
[-2, 3, 3, 0, 0],
[ 0, 1, 3, 0,-1]]

Related

How to keep a fixed size of unique values in random positions in an array while replacing others with a mask?

This can be a very simple question as I am still exploring Python. And for this issue I use numpy.
Updated 09/30/21: adopted and modified codes shown below for any potential future reference. I also added an elif in the loop for classes that have fewer counts than the wanted size. Some codes may be unnecessary tho.
new_array = test_array.copy()
uniques, counts = np.unique(new_array, return_counts=True)
print("classes:", uniques, "counts:", counts)
for unique, count in zip(uniques, counts):
#print (unique, count)
if unique != 0 and count > 3:
ids = np.random.choice(count, count-3, replace=False)
new_array[tuple(i[ids] for i in np.where(new_array == unique))] = 0
elif unique != 0 and count <= 3:
ids = np.random.choice(count, count, replace=False)
new_array[tuple(i[ids] for i in np.where(new_array == unique))] = unique
Below is original question.
Let's say I have a 2D array like this:
test_array = np.array([[0,0,0,0,0],
[1,1,1,1,1],
[0,0,0,0,0],
[2,2,2,4,4],
[4,4,4,2,2],
[0,0,0,0,0]])
print("existing classes:", np.unique(test_array))
# "existing classes: [0 1 2 4]"
Now I want to keep a fixed size (e.g. 2 values) in each class that != 0 (in this case two 1s, two 2s, and two 4s) and replace the rest with 0. Where the value being replaced is random with each run (or from a seed).
For example, with run 1 I will have
([[0,0,0,0,0],
[1,0,0,1,0],
[0,0,0,0,0],
[2,0,0,0,4],
[4,0,0,2,0],
[0,0,0,0,0]])
with another run it might be
([[0,0,0,0,0],
[1,1,0,0,0],
[0,0,0,0,0],
[2,0,2,0,4],
[4,0,0,0,0],
[0,0,0,0,0]])
etc. Could anyone help me with this?
My strategy is
Create a new array initialized to all zeros
Find the elements in each class
For each class
Randomly sample two of elements to keep
Set those elements of the new array to the class value
The trick is keeping the shape of the indexes appropriate so you retain the shape of the original array.
import numpy as np
test_array = np.array([[0,0,0,0,0],
[1,1,1,1,1],
[0,0,0,0,0],
[2,2,2,4,4],
[4,4,4,2,2],
[0,0,0,0,0]])
def sample_classes(arr, n_keep=2, random_state=42):
classes, counts = np.unique(test_array, return_counts=True)
rng = np.random.default_rng(random_state)
out = np.zeros_like(arr)
for klass, count in zip(classes, counts):
# Find locations of the class elements
indexes = np.nonzero(arr == klass)
# Sample up to n_keep elements of the class
keep_idx = rng.choice(count, n_keep, replace=False)
# Select the kept elements and reformat for indexing the output array and retaining its shape
keep_idx_reshape = tuple(ind[keep_idx] for ind in indexes)
out[keep_idx_reshape] = klass
return out
You can use it like
In [3]: sample_classes(test_array) [3/1174]
Out[3]:
array([[0, 0, 0, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 0, 0, 0],
[2, 0, 0, 4, 0],
[4, 0, 0, 2, 0],
[0, 0, 0, 0, 0]])
In [4]: sample_classes(test_array, n_keep=3)
Out[4]:
array([[0, 0, 0, 0, 0],
[1, 0, 1, 1, 0],
[0, 0, 0, 0, 0],
[0, 2, 0, 4, 0],
[4, 4, 0, 2, 2],
[0, 0, 0, 0, 0]])
In [5]: sample_classes(test_array, random_state=88)
Out[5]:
array([[0, 0, 0, 0, 0],
[0, 0, 1, 1, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[4, 0, 4, 2, 2],
[0, 0, 0, 0, 0]])
In [6]: sample_classes(test_array, random_state=88, n_keep=4)
Out[6]:
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[2, 2, 0, 4, 4],
[4, 4, 0, 2, 2],
[0, 0, 0, 0, 0]])
Here is my not-so-elegant solution:
def unique(arr, num=2, seed=None):
np.random.seed(seed)
vals = {}
for i, row in enumerate(arr):
for j, val in enumerate(row):
if val in vals and val != 0:
vals[val].append((i, j))
elif val != 0:
vals[val] = [(i, j)]
new = np.zeros_like(arr)
for val in vals:
np.random.shuffle(vals[val])
while len(vals[val]) > num:
vals[val].pop()
for row, col in vals[val]:
new[row,col] = val
return new
The following should be O(n log n) in array size
def keep_k_per_class(data,k,rng):
out = np.zeros_like(data)
unq,cnts = np.unique(data,return_counts=True)
assert (cnts >= k).all()
# calculate class boundaries from class sizes
CNTS = cnts.cumsum()
# indirectly group classes together by partial sorting
idx = data.ravel().argpartition(CNTS[:-1])
# the following lines implement simultaneous drawing without replacement
# from all classes
# lower boundaries of intervals to draw random numbers from
# for each class they start with the lower class boundary
# and from there grow one by one - together with the
# swapping out below this implements "without replacement"
lb = np.add.outer(np.arange(k),CNTS-cnts)
pick = rng.integers(lb,CNTS,lb.shape)
for l,p in zip(lb,pick):
# populate output array
out.ravel()[idx[p]] = unq
# swap out used indices so still available ones occupy a linear
# range (per class)
idx[p] = idx[l]
return out
Examples:
rng = np.random.default_rng()
>>>
>>> keep_k_per_class(test_array,2,rng)
array([[0, 0, 0, 0, 0],
[1, 1, 0, 0, 0],
[0, 0, 0, 0, 0],
[2, 0, 2, 0, 4],
[0, 4, 0, 0, 0],
[0, 0, 0, 0, 0]])
>>> keep_k_per_class(test_array,2,rng)
array([[0, 0, 0, 0, 0],
[1, 1, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 2, 0, 0, 0],
[4, 0, 4, 0, 2],
[0, 0, 0, 0, 0]])
and a large one
>>> BIG = np.add.outer(np.tile(test_array,(100,100)),np.arange(0,500,5))
>>> BIG.size
30000000
>>> res = keep_k_per_class(BIG,30,rng)
### takes ~4 sec
### check
>>> np.unique(np.bincount(res.ravel()),return_counts=True)
(array([ 0, 30, 29988030]), array([100, 399, 1]))

Check for next available row in a column

So say I've got a 2D array like:
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 2],
[0, 2, 2, 0, 2],
[1, 1, 2, 1, 1],
]
And I want the two players (player 1 is represented by 1 and player 2 is represented by 2) to be able to place counters down the columns (like connect 4). How can I find the next available row (if there are any)?
I can parse the column number
Thanks!
table = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 2],
[0, 2, 2, 0, 2],
[1, 1, 2, 1, 1],
]
def move(col, player, table):
available_rows = [i for i in range(len(table)) if table[i][col]==0]
if available_rows == []:
return False # i.e. you can not place on column col. invalid move
target_row = available_rows[-1] # target_row is the next available move on column col
table[target_row][col] = player
return True # i.e. valid move
print(table)
move(0, 2, table) # player 2 has placed its move on column 0 (zero based index)
print(table)
Output
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 2],
[0, 2, 2, 0, 2],
[1, 1, 2, 1, 1]
]
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 2],
[1, 2, 2, 0, 2],
[1, 1, 2, 1, 1]]
You have to check whether the output of move is true or not, to make sure the move is valid.
You can use a for loop to loop through the array, and check if the current row has any zero in it.
for row in array:
if row.count(0) > 0:
# this row has available slots
# Do something here
break
else:
# No free slot at all for all rows
# Do something here
I think I've found a solution:
def findNextRow(self, rowNumber):
for rowIndex in range(len(self._grid), 0, -1):
if self._grid[rowNumber][rowIndex] == 0:
return rowIndex-1
return False
You loop backwards through the 2d array. If the column number you parsed through and the current row is 0, then return the index-1 (since python array indexing starts at 0). If no row can be found, the loop will end without returning anything so you can return False.

Fast randomly sample a varied number of ones by row from one hot encoded matrix

I have a one hot encoded M x N matrix, A, with the following properties:
1 or more columns in each row can eq 1
Every column in the matrix will have exactly one cell with a value of one (all other cells will be zero)
M << N
I also an M x 1 array, B, that contains integers (i.e. number of random samples I want to select). Each cell of B has the following property:
B[i] <= np.sum(M[i])
I’m looking for the most efficient way to randomly sample a subset of the ones in each row of A. The number of samples returned for each row is given by the the integer values in the corresponding cells of B. The output will be an M x N matrix, let's call it C, where B == np.sum(C, axis=1)
Example
A = np.array([[0, 0, 1, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 1],
[1, 0, 0, 1, 1, 0, 0, 0]])
B = np.array([1, 3, 2])
A valid output of running this algorithm would be
array([[0, 0, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 1, 0, 0, 0]])
Another possible output would be
array([[0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 1],
[0, 0, 0, 1, 1, 0, 0, 0]])
Looking for the ability to generate X random samples as fast as possible
What about this?
import numpy as np
np.random.seed(42)
A = np.array([[0, 0, 1, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 1],
[1, 0, 0, 1, 1, 0, 0, 0]])
B = np.array([1, 3, 2])
C = np.c_[B, A]
def sample_ones(x):
indices = np.where(x[1:] == 1)[0] # indices of 1s
subset_indices = np.random.choice(a=indices, size=indices.size - x[0], replace=False) # indices of NON-sampled 1s
x[subset_indices + 1] = 0 # replace non-sampled 1s with 0s
return x[1:]
print(np.apply_along_axis(sample_ones, 1, C))
Output:
[[0 0 1 0 0 0 0 0]
[0 1 0 0 0 0 1 1]
[0 0 0 1 1 0 0 0]]

Create a NxN array for all diagonals

Given an integer n, create nxn nummy array such that all of the elements present in both its diagonals are 1 and all others are 0
Input: 4
Output
*[[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 0],
[1, 0, 0, 1]]*
how do i achieve this array?
You can use the fill_diagonal to fill the elements in the principal diagonal and use it with np.fliplr to fill elements across the other diagonal. Refer link
import numpy as np
a = np.zeros((4, 4), int)
np.fill_diagonal(a, 1)
np.fill_diagonal(np.fliplr(a), 1)
Output :
array([[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 0],
[1, 0, 0, 1]])
Create an identity matrix and its flipped view, then take the maximum of the two:
np.maximum(np.eye(5, dtype=int), np.fliplr(np.eye(5, dtype=int)))
#array([[1, 0, 0, 0, 1],
# [0, 1, 0, 1, 0],
# [0, 0, 1, 0, 0],
# [0, 1, 0, 1, 0],
# [1, 0, 0, 0, 1]])
Edited: changed [::-1] to np.fliplr (for better performance).
I would do (assuming n=5):
import numpy as np
d = np.diagflat(np.ones(5,int))
a = d | np.rot90(d)
print(a)
Output:
[[1 0 0 0 1]
[0 1 0 1 0]
[0 0 1 0 0]
[0 1 0 1 0]
[1 0 0 0 1]]
I harness fact that we could use | (binary OR) here for getting same effect as max, because arrays holds solely 0s and 1s.

In a matrix having a row of zeros, how do I replace the corresponding diagonal entry of the matrix with a one?

I have a square matrix, A, whose values are zero or one and that contains one or more rows of
zeros. For each row of zeros, I wish to replace the corresponding diagonal entry of A with a one.
For example, suppose
A=np.array([[0,1,1,0,1],[0,0,1,1,1],[0,0,0,0,0],[0,1,0,0,0],[0,0,0,0,0]])
for which rows 3 and 5 are all zeros. I wish to set A[3,3] and A[5,5] equal to one.
The matrix is:
>>> A
array([[0, 1, 1, 0, 1],
[0, 0, 1, 1, 1],
[0, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 0]])
We can find out the sum of all the rows:
>>> A.sum(axis=1)
array([3, 3, 0, 1, 0])
We want all the diagonals corresponding to 0-sum rows to be set to 1.
Thus, the following works:
>>> row_sums = A.sum(axis=1)
>>> A[row_sums == 0, row_sums == 0] = 1
>>> A
array([[0, 1, 1, 0, 1],
[0, 0, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1]])
Note that this works because row_sums == 0 is True for the desired rows:
>>> row_sums == 0
array([False, False, True, False, True])
and thus A[row_sums == 0, row_sums == 0] selects the required elements.

Categories

Resources