Find diagonals in a Python matrix using lists - python

I'm trying to code the game connect-4, and there is a part of my code in which i try to find in a 7x6 matrix, diagonals of 4 ones in a row or 4 2s in a row.
This part of my code does not work i tried everything I was able to. And sometimes it detects that there is a 4 1s or 4 2s diagonal where is not. I created the matrix puting a 7 zero list in each position of a 6 zeros lists. I'm trying to do it using only lists functions, i cant use the numpy library or similar.
Okay, in this part of the code I'm trying to find if in each possible diagonal of the matrice there are 4 zeros in a row. PD: I'm only trying to find the diagonals that go from left to right ATM. Thanks for your help, I tried to explain my problem as good as I can because english is not my main tongue.
This is my code:
import random
llista = [0]*6 #when i say llista y mean matrix
for i in range(6):
llista[i]=[0]*7
#Here i fill the 7*6 matrix of 0s 1s and 2s randomly so i can see if it works.
for i in range(30):
x=random.randrange(-1,-7,-1)
y=random.randrange(0,7,1)
llista[x][y]=1
for i in range(30):
x=random.randrange(-1,-7,-1)
y=random.randrange(0,7,1)
llista[x][y]=2
#This 2 loops here are too see if it is possible to have a diagonal in the matrece because if you want a diagonal u need at least a one or 2 in the center, the problem is not here.
for i in range(-1,-7,-1):
possible = False
if llista[i][3]==1:
possible = True
break
for i in range(7):
possible2 = False
if llista[-4][i]==1 or llista[-4][i]==1:
possible2=True
break
if possible==True and possible2==True:
#The problem starts here. This first loop i use it too find the diagonals that go from left to right. I want to find diagonals of 4 1s or 4 2s.
for i in range(len(llista)-3):
for j in range(len(llista[i])-3):
#This if is too see if we have four 1 or 2 togheter in the list, if we have them it prints the sentence below.
if (llista[i][j]==1 and llista[i+1][j+1]==1 and llista[i+2][j+2]==1 and llista[i+3][j+3]==1) or (llista[i][j]==2 and llista[i+1][j+1]==2 and llista[i+2][j+2]==2 and llista[i+3][j+3]==2 ):
print("There is at least one left to right diagonal")
#This loop is the same than the last one but to find diagonals from right to left (a 4 diagonal made of 1s or 2s)
for i in range(len(llista)):
for j in range(len(llista[i])):
if i-3<0 and j-3<0:
if (llista[i][j]==1 and llista[i-1][j-1]==1 and llista[i-2][j-2]==1 and llista[i-3][j-3]==1) or (llista[i][j]==2 and llista[i-1][j-1]==2 and llista[i-2][j-2]==2 and llista[i-3][j-3]==2 ):
print("There is at least one right to left diagonal")
#Here i print the matrix
for i in range(6):
print(llista[i])
#So this program should say if there is at least one left to right diagonal
or right to left diagonal.
#I dont want to use functions that are not being used already, and i dont wanna do it another way because i must understand this way. thanks

If you consider the logic of your 'right-to-left' loop, you are actually just doing the same as your 'left-to-right' loop in reverse order. To really get the 'right-to-left' pass right you have to have your i and j indices moving in different directions.
So your conditional statement in this section should look like:
if i-3>=0 and j+3<7:
if (llista[i][j]==1 and llista[i-1][j+1]==1 and llista[i-2][j+2]==1 and llista[i-3][j+3]==1) or (llista[i][j]==2 and llista[i-1][j+1]==2 and llista[i-2][j+2]==2 and llista[i-3][j+3]==2 ):
print("There is at least one right to left diagonal")
There are a ton of optimizations you could use by importing libraries like numpy or itertools as AResem showed. That answer is not entirely correct though for the following reason.
When you say return True, k you are not exercising any control over the value of k because it was used in the list comprehension just above and will just have the value of the last item it iterated over. So when your function finds a diagonal it reports the wrong number about two thirds of the time.
Here is an edited function that gives the correct results:
def check_diagonals(matrix):
for offset in range(-2, 4):
diag = matrix.diagonal(offset=offset)
# Here you can create a tuple of numbers with the number of times they are repeated.
# This allows you to keep your k and g values associated.
repeat_groups = [(k, sum(1 for _ in g)) for k, g in groupby(diag)]
# By using the built-in max function with the 'key' keyword, you can find
# the maximum number of repeats and return the number associated with that.
num, max_repeats = max(repeat_groups, key=lambda item: item[1])
if max_repeats >= 4:
return True, num
return False, None
If you run this function with print statements added you can get output like the following:
Matrix:
[[1 0 2 2 1 0 1]
[0 2 0 2 1 1 1]
[2 2 0 0 0 0 1]
[0 0 2 2 0 2 2]
[2 1 1 1 1 1 0]
[2 2 0 2 1 0 2]]
offset -2
diag [2 0 1 2]
repeat_groups [(2, 1), (0, 1), (1, 1), (2, 1)]
num, max_repeats 2 1
offset -1
diag [0 2 2 1 1]
repeat_groups [(0, 1), (2, 2), (1, 2)]
num, max_repeats 2 2
offset 0
diag [1 2 0 2 1 0]
repeat_groups [(1, 1), (2, 1), (0, 1), (2, 1), (1, 1), (0, 1)]
num, max_repeats 1 1
offset 1
diag [0 0 0 0 1 2]
repeat_groups [(0, 4), (1, 1), (2, 1)]
num, max_repeats 0 4
(True, 0)
There is at least one left to right diagonal: 0's' # Correct!
If you want to ignore diagonals of zeros, you can easily add an extra condition, e.g.
if max_repeats >= 4 and num != 0:
return True, num
You could try and recreate this without using numpy if you wanted.

Perhaps you are not getting the right answer because of the way you parsed your conditional statements.
You should have
if cond1 or cond2:
# do something
with the conditions contained in parentheses (). At the moment only your second condition is contained in parentheses.
Try the following:
if (matrix[i][j]==1 and matrix[i+1][j+1]==1 and matrix[i+2][j+2]==1 and matrix[i+3][j+3]==1) or (matrix[i][j]==2 and matrix[i+1][j+1]==2 and matrix[i+2][j+2]==2 and matrix[i+3][j+3]==2 ):
print("There is at least one left to right diagonal")

You may consider using numpy for this problem. Here goes some code to get you started.
import numpy as np
from itertools import groupby
def check_diagonals(matrix):
for offset in range(-2, 4):
diag = matrix.diagonal(offset=offset)
if max([sum(1 for _ in g) for k, g in groupby(diag)]) >= 4:
return True, k
return False, None
# random test matrix
matrix = np.random.randint(0, 3, 6 * 7)
matrix = matrix.reshape(6,7)
# left to right check
resp_tuple = check_diagonals(matrix)
if resp_tuple[0]:
print("There is at least one left to right diagonal: {}'s'".format(resp_tuple[1]))
else:
# right to left
resp_tuple = check_diagonals(np.fliplr(matrix))
if resp_tuple[0]:
print("There is at least one right to left diagonal: {}'s'".format(resp_tuple[1]))
else:
# No diagonals
print('No diagonals')

Related

How can I fix my code to solve for this puzzle? (Python)

This is a diagram of the puzzle I'm solving for:
Where A,B,C,D are four numbers that are either 0 or 1, and alphas are some random constants (could be positive or negative, but ignore the case when they're 0). The goal is to determine the list [A,B,C,D] given a random array of alpha. Here're some rules:
If alpha >0, then the neighboring points have different values. (Ex: if alpha_A = 4.5, then A,B = 01 or 10.
If alpha <0, then the neighboring points have the same value. (Ex: if alpha_B = -4.5, then B,C = 00 or 11.
Making the two adjacent points constant for the highest alpha_i, then start from the 2nd highest absolute value, iterate to the 2nd lowest one. Rule 1 and 2 could be violated only if alpha_i has the minimum absolute value.
Here's the current code I have:
alpha = np.random.normal(0, 10, N_object4)
pa = np.abs(alpha)
N_object4 = 4
num = pa.argsort()[-3:][::-1] # Index of descending order for abs
print(alpha)
print(num)
ABCD = np.zeros(N_object4).tolist()
if alpha[num[0]] > 0:
ABCD[num[0]+1] = 1 # The largest assignment stays constant.
for i in range (1,len(num)): # Iterating from the 2nd largest abs(alpha) to the 2nd smallest.
if i == 3:
num[i+1] = num[0]
elif alpha[num[i]] > 0:
if np.abs(num[i]-num[0]) == 1 or 2:
ABCD[num[i+1]] = 1-ABCD[num[i]]
elif np.abs(num[i]-num[0]) == 3:
ABCD[num[i]] = 1-ABCD[num[0]]
elif alpha[num[i]] < 0:
if np.abs(num[i]-num[0]) == 1 or 2:
**ABCD[num[i+1]] = ABCD[num[i]]**
elif np.abs(num[i]-num[0]) == 3:
ABCD[num[i]] = ABCD[num[0]]
I think I'm still missing something in my code. There's an error in my current version (marked in the line with **):
IndexError: index 3 is out of bounds for axis 0 with size 3
I'm wondering where's my mistake?
Also, an example of the desired output: if
alpha = [12.74921599, -8.01870123, 11.07638142, -3.51723019]
then
num = [0, 2, 1]
The correct list of ABCD should be:
ABCD = [0, 1, 1, 0] (or [1, 0, 0, 1])
My previous version could give me an output, but the list ABCD doesn't look right.
Thanks a lot for reading my question, and I really appreciate your help:)
Looking at your code if found a few smells:
nums is 3 elements long, seems that you want num = pa.argsort()[-4:][::-1]
You also have indexing issues on other places:
if i == 3:
num[i+1] = num[0]
element 4 is not on the array
My personal advice: given the small size of the problem, get rid of numpy and just use python lists.
The other observation (that took me years to learn) is that for small problems, bruteforce is ok.
This is my bruteforce solution, with more time, this can be improved to do backtracking or dynamic programming:
import random
# To make things simpler, I will consider A = 0, and so on
pairs = [
(0, 1),
(1, 2),
(2, 3),
(3, 0),
]
# Ensure no zeros on our list
alphas = [(random.choice([1, -1]) * random.uniform(1, 10), pairs[n]) for n in range(4)]
print(alphas)
# Sort descending in
alphas.sort(reverse=True, key=lambda n: abs(n[0]))
print(alphas[:-1])
def potential_solutions():
"""Generator of potential solutions"""
for n in range(16):
# This just abuses that in binary, numbers from 0 to 15 cover all posible configurations
# I use shift and masking to get each bit and return a list
yield [n >> 3 & 1 , n >> 2 & 1, n >> 1 & 1, n & 1]
def check(solution):
"""Takes a candidate solution and check if is ok"""
for alpha, (right, left) in alphas[:-1]:
if alpha < 0 and solution[right] != solution[left]:
return False
elif alpha > 0 and solution[right] == solution[left]:
return False
return True
# Look for correct solutions
for solution in potential_solutions():
if check(solution):
print(solution)

need with identifying the meaning of a nested for loops in a function that i found

I happen to come upon a function that takes a nxn matrix of ones and converting it to ones and zeros one after the other.
I wanted to know how those nested loops work, why we use nested for loops, why we type
(0, mat.shape[0], 2), why we have '1' in the start of the second nested for loop and what is that '2' in the end of every for loop.
the function is:
import numpy as np
def chess_board(n):
mat = np.ones((n, n))
for i in range(0, mat.shape[0], 2):
for j in range(0, mat.shape[1], 2):
mat[i, j] = 0
for i in range(1, mat.shape[0], 2):
for j in range(1, mat.shape[0], 2):
mat[i, j] = 0
print(mat)
chess_board(n=5)
So mat.shape is going to provide the dimensions of your matrix. Since the matrix is 5x5, there's actually no point in doing that since both of those values will be 5.
The 1 is because we need to offset each row and column by 1. So imagine that the topmost point (0,0) is a 0 on the chess board. If we offset both row and column by 1, we get (1,1), which is along the same diagonal. And diagonals along a chess board have the same color (which equals the same value - 0 or 1).
The 2 is because we want every other square to be the same color/value. So if (0,0) is white, (0,1) is black and then (0,2) is white again. The 2 is the "step" argument in the range function. So range(0, 11, 2) will product 0, 2, 4, 6, 8, 10
Just to make a quick and brief note :
=> shape[0] : no of rows
=> shape[1] : no of columns
=> That 2 just makes i and j jump 2 steps in every iteration. Like i += 2 / j += 2

What's wrong in my N-Queens solution?

class Solution:
def solveNQueens(self, n):
def n_queens_solver(row, visited_cols, unique_y_intercepts, res):
if row == n:
results.append(res)
return
for col in range(n):
if col not in visited_cols and (row + col) not in unique_y_intercepts and (col - row) not in unique_y_intercepts:
visited_cols_dup = visited_cols.copy()
unique_y_intercepts_dup = unique_y_intercepts.copy()
res_dup = res.copy()
visited_cols_dup.add(col)
unique_y_intercepts_dup.add(row + col)
unique_y_intercepts_dup.add(col - row)
this_row = '.' * col + 'Q' + '.' * (n - col - 1)
res_dup.append(this_row)
n_queens_solver(row + 1, visited_cols_dup, unique_y_intercepts_dup, res_dup)
results = []
n_queens_solver(0, set(), set(), [])
return results
I haven't yet looked at an actual solution for this problem, and I imagine mine is pretty damn inefficient, but I don't know what I'm doing wrong.
Sample input:
n = 4
Correct output:
[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
My output:
[]
To account for unique diagonals, I figured for every valid place to insert a queen, we need to account for all lines with slope +1 and slope -1. Each of these lines has a unique y-intercept. Using y=x+b and y=-x+b, every potential point to insert a queen has to not have (y-x == b) or (y+x == b).
Anything glaringly incorrect with my rationale here?
Note - I'm not asking for a different or more optimal solution. Am only trying to figure out where my thought process is incorrect.
The mistake in the algorithm is as follows. Look at what happens when you try to mark diagonals as visited.
Let's draw the 4x4 board and assign each cell the number of positive-sloped diagonal it is located on (put i + j on the square located in the row i and col j):
0 1 2 3
1 2 3 4
2 3 4 5
3 4 5 6
Suppose that you put the first queen on the position (1,3). I know that your algorithm starts with row 0, but it doesn't matter in this case. Now the board looks as follows:
0 1 2 3
1 2 3 Q
2 3 4 5
3 4 5 6
Then you add 3 to visited_cols, add 3 + 1 = 4 to y_intercepts and add 3 - 1 = 2 to y_intercepts. We have cols = {3} and y_intercepts = {2,4}. Let's mark with X on the board the diagonal corresponding to the value 2 of y_intercept:
0 1 X 3
1 X 3 Q
X 3 4 5
3 4 5 6
Now suppose we proceed with algorithm to row 2 and we try to put the second queen on square (2,0). This is a valid position because the first queen doesn't attack this square. But your algorithm will reject this position, because row + col = 2 + 0 = 2 is in the y_intercepts set. What happened? We did a positive-slope test for the square (2, 0) and it failed because there was 2 in the y_intercepts set. But note that this 2 was added there after checking the negative-sloped diagonal (remember 3 - 1 = 2 ?). So the tests for positive-sloped and negative-sloped diagonals got mixed which resulted in the incorrect rejection of square (2, 0). This is the reason why your program outputs the empty results vector - at some point of the algorithm there is a row such that all its squares are rejected (some are rejected correctly and others are rejected incorrectly similarly to what we just observed).
One way to fix it is to create two separate sets for positive-sloped diagonals and negative-sloped diagonals and check row + col for positive-sloped ones and col - row for negative ones.

Why do i keep getting index out of range

I'm working through the checkio site and have encountered a problem that I don't understand. The input is an array of ones and zero's plus 2 co ordinates. I need to write a function to return how many ones are in a circle to my co ordinates, not counting the one I'm on. I keep getting index out of range, which it is but I thought the 'try: except index error: ' bit of my code would ignore it and move onto the next iterable. It works with the other examples the site gives, which do have other places where I'm out of range but the code skips it and moves on. Just the last test that fails and I cant figure it out. I've had a good search and can't see the problem. At first I had the 'try' inside the 'for' loop but it wasn't getting that far. I felt like the except should be inside the for loop but every example i've seen its on the same level as the 'try' If you help me you are mega cool. :) Here's the code with the example i'm getting stuck at.
def count_neighbours(grid, row, col):
count = 0
a = row - 1
b = row + 1
c = col - 1
d = col + 1
order = [grid[a][c], grid[a][col], grid[a][d],
grid[row][c], grid[row][d],
grid[b][c], grid[b][col], grid[b][d]]
try:
for z in order:
count += z
except IndexError:
pass
return count
count_neighbours(((1, 1, 1),(1, 1, 1),(1, 1, 1)), 0, 2)
Because when you are on any edge you are trying to look past the edge at locations which don't exist.
For example, checking the neighbors of cell [0,0] in the array you are going to try look at cell [-1,-1] (using grid[a][c]) which is an index which is "out of range" (not between 0 and the size of the array). This will happen every time you are on the top or bottom row and the left or rightmost column. You need to guard against these special conditions.
i.e.
for z in order:
if z >= 0 and z <= length:
count += z
The IndexError exception is not raised by the for loop but by the initialisation of the order list, you are sometime trying to access the values that are not in the grid (negative indices or > than the size of the grid or > than the size of the tuples stored in the grid).
One solution could be the storage of the indices rather than the values, like this:
def count_neighbours(grid, row, col):
count = 0
a = row - 1
b = row + 1
c = col - 1
d = col + 1
orders = [(a, c), (a, col), (a, d),
(row, c), (row, d),
(b, c), (b, col), (b, d)]
try:
for x, y in orders:
count += grid[x][y]
except IndexError:
pass
return count
count_neighbours(((1, 1, 1),(1, 1, 1),(1, 1, 1)), 0, 2)

create a matrix of binary representation of numbers in python

for programming the Hamming cod in sage ( a compiler based on python) I need to create a matrix in which each column is a binary representation of a number
say Hamming(3) the matrix should look like this
0 0 0 1 1 1 1
0 1 1 0 0 1 1
1 0 1 0 1 0 1
which is the binary represenation of numbers from 1 to 7.
so what i did till now is to convert any given number to it's binary representation:
i made this little function it take two values n and r
and repserent n over r bits
binrep(n,r)
x=n
L=[]
LL=[]
while (n>0):
a=int(float(n%2))
L.append(a)
n=(n-a)/2
while (len(L)<r):
L.append(0)
#print(L)
LL=L[::-1]
return LL
so now i want to collect all the LL i got and make them in one big matrix like the one above
In addition to the other ones, an answer using numpy:
import numpy as np
# we're creating the binary representation for all numbers from 0 to N-1
N = 8
# for that, we need a 1xN matrix of all the numbers
a = np.arange(N, dtype=int)[np.newaxis,:]
# we also need a log2(N)x1 matrix, for the powers of 2 on the numbers.
# floor(log(N)) is the largest component that can make up any number up to N
l = int(np.log2(N))
b = np.arange(l, dtype=int)[::-1,np.newaxis]
# This step is a bit complicated, so I'll explain it below.
print np.array(a & 2**b > 0, dtype=int)
This prints:
[[0 0 0 0 1 1 1 1]
[0 0 1 1 0 0 1 1]
[0 1 0 1 0 1 0 1]]
The line
print np.array(a & 2**b > 0, dtype=int)
does a few things at once. I'll split it up into simpler steps:
# this takes our matrix b and creates a matrix containing the powers of 2
# up to 2^log2(N) == N
# (if N is a power of 2; otherwise, up to the next one below)
powers = 2**b
# now we calculate the bit-wise and (&) for each combination from a and b.
# because a has one row, and b as one column, numpy will automatically
# broadcast all values, so the resulting array has size log2(N)xN.
u = a & powers
# this is almost what we want, but has 4's in the first row,
# 2's in the second row and 1's in the last one.
# one method of getting 1's everywhere is to divide by powers:
print u / powers
# another way is to check where u > 0, which yields an array of bools,
# which we then convert to numbers by turning it into an array of ints.
print np.array(u > 0, dtype=int)
You just have to convert all those binary representations to lists of integers, collect them in a list of lists, and finally transpose that list of lists to get the desired output.
n = 3
binary = []
for i in range(1, 2**n):
s = binrep(i, n)
binary.append(map(int, s))
matrix = zip(*binary)
Or in one line: matrix = zip(*(map(int, binrep(i, n)) for i in range(1, 2**n)))
Result is
[(0, 0, 0, 1, 1, 1, 1),
(0, 1, 1, 0, 0, 1, 1),
(1, 0, 1, 0, 1, 0, 1)]
Also note that there are other ways to convert numbers to binary, e.g. using str.format:
def binrep(n,r):
return "{0:0{1}b}".format(n, r)
Like zoosuck mentioned, I would suggest the bin() function in replacement to your code. To print them in a sequence, assuming that the format is similar:
L = ['10101010', '10101010']
for i in L:
print ' '.join([j for j in i])
In a similar manner, you can append it to a list or a dictionary.

Categories

Resources