Referencing a conditional random element of an array and replacing it - python

This is my second question post on StackOverflow relating to coding in Python/Numpy.
I feel like there is definitely some sort of function which does the pseudocode:
np.random.choice([a[i-1,j],a[i+1,j],a[i,j-1],a[i,j+1]])==0 = 9
Essentially, I would like the random function to select a cell adjacent to mine (up, down, left, right) with the value 0, and replace said cell with a 9
Unforunately, I know why the code I typed is illegal. The first half of the statement returns a True/False boolean as I have used a comparison/checking operator. I can't set this into a value 9.
If I split the code-load into two codes and used an if statement with the random.choice (looking at an adjacent element that equalled zero), then following this, I would need some sort of function or definition to recall which cell (up down left or right) did the random generator originally select, to which I can then set it to 9.
Kind Regards,
EDIT: I may as well attach a sample code, so you can simply just run this (I am including my error)
a = np.empty((6,6,))
a[:] = 0
a[2,3]=a[3,3]=a[2,4] = 1
for (i,j), value in np.ndenumerate(a):
if a[i,j]==1:
np.random.choice([a[i-1,j],a[i+1,j],a[i,j-1],a[i,j+1]])==0 = 9

You could select from a range of directions (up, down, left, right) that map to specific coordinate movements in the 2D array, like this:
# generate a dataset
a = np.zeros((6,6))
a[2,3]=a[3,3]=a[2,4] = 1
# map directions to coordinate movements
nesw_map = {'left': [-1, 0], 'top': [0, 1], 'right': [1,0], 'bottom': [0,-1]}
directions = nesw_map.keys()
# select only those places where a == 1
for col_ind, row_ind in zip(*np.where(a == 1)): # more efficient than iterating over the entire array
x = np.random.choice(directions)
elm_coords = col_ind + nesw_map[x][0], row_ind + nesw_map[x][1]
if a[elm_coords] == 0:
a[elm_coords] = 9
Note that this does not do any type of bounds checking (so if a 1 appears at the edge, you might select an item "off the grid" which will result in an error).

This is the most "basic" way of getting what you need (Adding a try/except statement provides error checking, so you can prevent any unwanted errors):
import random,numpy
a = numpy.empty((6,6,))
a[:] = 0
a[2,3]=a[3,3]=a[5,5] = 1
for (i,j), value in numpy.ndenumerate(a):
var = 0
if a[i,j]==1:
while var==0:
x=random.randrange(0,4) #Generate a random number
try:
if x==0 and a[i-1,j]==0:
a[i-1,j] =9 #Do this if x = 0
elif x==1 and a[i+1,j]==0:
a[i+1,j] =9 #Do this if x = 1
elif x==2 and a[i,j-1]==0:
a[i,j-1] =9 #Do this if x = 2
elif x==3 and a[i,j+1]==0:
a[i,j+1] =9 #Do this if x = 3
var=1
except:
var=0
print a

Related

Remove list in lists that satisfied the condition

I'm trying to make a quick OCR for specific use, I know should've just write a preprocessor for normal OCR and that would been faster but this idea came up to me first and I figure I should just try it anyway haha. This program would take a picture on a region of screen and identify the number within it, as of right now, it's only 0 and 1 but I've been working on it and stuck with some problems. Here is my code
while True:
if keyboard.is_pressed('`'):
Firstlist = list(pyautogui.locateAllOnScreen(image[0], confidence = 0.95,region=( 1570 , 990 , 230 , 70 )))
print(len(Firstlist))
Firstlist1 = list(pyautogui.locateAllOnScreen(image1, confidence = 0.95,region=( 1570 , 990 , 230 , 70 ))) + Firstlist
print(len(Firstlist1))
print(Firstlist)
if len(Firstlist) > 0:
print(Firstlist[0][0])
#compare all first instance of that number and eliminate all duplicated with in a different of 5 x pixel
break
Which would identify some predetermined set of number like this on screen and right now, it would give me a set of coordinate for number zero on screen, here is the result, please ignore other parts, it's just me playing around. Problem with this is pyautogui.locateAllOnScreen would sometimes generate duplicate value of the same picture within the coordinate ranging from approx 0-5 pixels if not set the confidence level right.
Example:
Value supposed to be [ (1655,1024,20,26),(1675,1024,20,26) ] but will yield a third value like [ (1655,1024,20,26), (1658,1024,20,26), (1675,1024,20,26) ].
And that's why I'm trying to make a correction for this. Is there anyway to identified if that x value of second duplicate coordinate is within a range of 0-5 pixels to the first coordinate and just delete it, moving the rest up the ladder so that the number would come up right and in order? Thank you!
Note: I'm still working on learning the list removal process by myself, and read the removing list with lambda to me is like gibberish. Please forgive me if something is wrong. Have a good day y'all!
You can try this.
if len(Firstlist) > 2:
elems = [f[0] for f in Firstlist] # create a list of just first index
i = 0
while i < len(elems) - 1: # iterate through the list with i
j = i + 1
while j < len(elems): # iterate through the rest of the list with j
if abs(elems[i] - elems[j]) <= 5: # if item at index i is within 5 pixels of item at index j
del elems[j] # delete item j and continue
else:
j += 1 # otherwise move to next item
i += 1 # Move to next i item
list1 = [ (1655,1024,20,26), (1658,1024,20,26), (1675,1024,20,26) ]
x = [list1[0]] + [x for x in list1 if abs(list1[0][0] - x[0]) > 5]
print(x)
Output:
[(1655, 1024, 20, 26), (1675, 1024, 20, 26)]

Intersection of lists having comparing 3 elements at a time

I have tried creating two separate lists by the name of 'sample' and 'game'. These contain outcomes of 3 games, eg, (1,1,0) (0,1,0) shown as [1,1,0,0,1,0] in both of the lists. I am trying to find intersection between both the lists through my last loop which should compare 3 elements of one list with 3 elements of another list and then return the match by appending it to list 'intersection'.
Eg, sample has [1,1,0,0,1,0] and game has [1,0,1,1,1,0,1,1,0]. The intersection of both should give me [1,1,0] that is the first 3 elements of 'sample' and 3 elements from index 3 of 'game'.
However, I am facing an error of index out of range.
Also, (1,1,0) in one list might get compared with the same (1,1,0) in other list twice, if that other list has (1,1,0) 2 times, which should not happen in intersection.
import random
P1 = 1/2 # win 1st game
P2 = 2/3 # win game immediately after a win
P3 = 1/3 # win game immediately after a loss
A = [0,1] # 0 for losing a game and 1 for winning a game
N = 100
sample_points = []; G1=[]; G2=[]; G3=[]
for i in range(N):
Game1 = random.choice([0,1])
Game2 = random.choice([0,1])
Game3 = random.choice([0,1])
G1.append(Game1)
G2.append(Game2)
G3.append(Game3)
sample_points.extend([Game1, Game2, Game3])
sample = []; game=[];intersection=[]
i = 0
# creating two separate lists
while i < len(sample_points):
if sample_points[i] + sample_points[i+1] + sample_points[i+2] == 2:
n1 = sample_points[i] ; n2 = sample_points[i+1] ; n3 = sample_points[i+2]
sample.append(n1);sample.append(n2);sample.append(n3)
if sample_points[i] == 1:
q1 = sample_points[i] ; q2 = sample_points[i+1] ; q3 = sample_points[i+2]
game.append(q1);game.append(q2);game.append(q3)
i = i+3
i=0
j=0
while j < len(sample):
for i in range(len(game)):
for j in range(len(sample)):
if game[i] == sample[j] and game[i+1] == sample[j+1] and game[i+2] == sample[j+2]:
intersection.append(sample[j]);intersection.append(sample[j+1]);intersection.append(sample[j+2])
j = j+3
i=i+3
Let's look at this block of code
while j < len(sample):
for i in range(len(game)):
for j in range(len(sample)):
if game[i] == sample[j] and game[i+1] == sample[j+1] and game[i+2] == sample[j+2]:
intersection.append(sample[j]);intersection.append(sample[j+1]);intersection.append(sample[j+2])
j = j+3
i=i+3
Notice the you let i and j to run until the very end of the vector and yet you consider indices like i+1 and i+2.
I would use range to indicate the increment by 3 and also we can compare two lists rather than using multiple and statement. I have also tried to use extend. You might like to replace it with something like
for i in range(0, len(game)-3, 3):
for j in range(0, len(sample)-3, 3):
if game[i:i+3] == sample[j:j+3]:
intersection.extend(sample[j:j+3])
print(intersection)
Also, you mentioned that you want to avoid duplicate, you might want to use set to check for duplicate for the two separate lists and then convert them back to a list.

Spawning objects in groups when the first object of the group was spawned randomly Python

I'm currently doing a project, and in the code I have, I'm trying to get trees .*. and mountains .^. to spawn in groups around the first tree or mountain which is spawned randomly, however, I can't figure out how to get the trees and mountains to spawn in groups around a single randomly generated point. Any help?
grid = []
def draw_board():
row = 0
for i in range(0,625):
if grid[i] == 1:
print("..."),
elif grid[i] == 2:
print("..."),
elif grid[i] == 3:
print(".*."),
elif grid[i] == 4:
print(".^."),
elif grid[i] == 5:
print("[T]"),
else:
print("ERR"),
row = row + 1
if row == 25:
print ("\n")
row = 0
return
There's a number of ways you can do it.
Firstly, you can just simulate the groups directly, i.e. pick a range on the grid and fill it with a specific figure.
def generate_grid(size):
grid = [0] * size
right = 0
while right < size:
left = right
repeat = min(random.randint(1, 5), size - right) # *
right = left + repeat
grid[left:right] = [random.choice(figures)] * repeat
return grid
Note that the group size need not to be uniformly distributed, you can use any convenient distribution, e.g. Poisson.
Secondly, you can use a Markov Chain. In this case group lengths will implicitly follow a Geometric distribution. Here's the code:
def transition_matrix(A):
"""Ensures that each row of transition matrix sums to 1."""
copy = []
for i, row in enumerate(A):
total = sum(row)
copy.append([item / total for item in row])
return copy
def generate_grid(size):
# Transition matrix ``A`` defines the probability of
# changing from figure i to figure j for each pair
# of figures i and j. The grouping effect can be
# obtained by setting diagonal entries A[i][i] to
# larger values.
#
# You need to specify this manually.
A = transition_matrix([[5, 1],
[1, 5]]) # Assuming 2 figures.
grid = [random.choice(figures)]
for i in range(1, size):
current = grid[-1]
next = choice(figures, A[current])
grid.append(next)
return grid
Where the choice function is explained in this StackOverflow answer.

How to structure a program to work with minesweeper configurations

EDIT: This was a while ago and I've since got it working, if you'd like to see the code it's included at github.com/LewisGaul/minegaulerQt.
I'm trying to write a program to calculate probabilities for the game minesweeper, and have had some difficulty working out how best to structure it. While it may seem quite simple at first with the example below, I would like to know the best way to allow for more complex configurations. Note I am not looking for help with how to calculate probabilities - I know the method, I just need to implement it!
To make it clear what I'm trying to calculate, I will work through a simple example which can be done by hand. Consider a minesweeper configuration
# # # #
# 1 2 #
# # # #
where # represents an unclicked cell. The 1 tells us there is exactly 1 mine in the leftmost 7 unclicked cells, the 2 tells us there are exactly 2 in the rightmost 7. To calculate the probability of each individual cell containing a mine, we need to determine all the different cases (only 2 in this simple case):
1 mine in leftmost 3 cells, 2 mines in rightmost 3 cells (total of 3 mines, 3x3=9 combinations).
1 mine in center 4 cells, 1 mine in rightmost 3 cells (total of 2 mines, 4x3=12 combinations).
Given the probability of a mine being in a random cell is about 0.2, it is (in a random selection of cells) about 4 times more likely there is a total of 2 mines rather than a total of 3, so the total number of mines in a configuration matters, as well as the number of combinations of each configuration. So in this case the probability of case 1 is 9/(9+4x12)=0.158, and the probability of there being a mine in a given leftmost cell is therefore about 0.158/3=0.05, as those cells are effectively equivalent (they share exactly the same revealed neighbours).
I have created a GUI with Tkinter which allows me to easily enter configurations such as the one in the example, which stores the grid as a numpy array. I then made a NumberGroup class which isolates each of the clicked/numbered cells, storing the number and a set of the coordinates of its unclicked neighbours. These can be subtracted to get equivalence groups... Although this would not be as straightforward if there were three or more numbers instead of just two. But I am unsure how to go from here to getting the different configurations. I toyed with making a Configuration class, but am not hugely familiar with how different classes should work together. See working code below (numpy required).
Note: I am aware I could have attempted to use a brute force approach, but if possible I would like to avoid that, keeping the equivalent groups separate (in the above example there are 3 equivalence groups, the leftmost 3, the middle 4, the rightmost 3). I would like to hear your thoughts on this.
import numpy as np
grid = np.array(
[[0, 0, 0, 0],
[0, 2, 1, 0],
[0, 0, 0, 0]]
)
dims = (3, 4) #Dimensions of the grid
class NumberGroup(object):
def __init__(self, mines, coords, dims=None):
"""Takes a number of mines, and a set of coordinates."""
if dims:
self.dims = dims
self.mines = mines
self.coords = coords
def __repr__(self):
return "<Group of {} cells with {} mines>".format(
len(self.coords), self.mines)
def __str__(self):
if hasattr(self, 'dims'):
dims = self.dims
else:
dims = (max([c[0] for c in self.coords]) + 1,
max([c[1] for c in self.coords]) + 1)
grid = np.zeros(dims, int)
for coord in self.coords:
grid[coord] = 1
return str(grid).replace('0', '.').replace('1', '#')
def __sub__(self, other):
if type(other) is NumberGroup:
return self.coords - other.coords
elif type(other) is set:
return self.coords - other.coords
else:
raise TypeError("Can only subtract a group or a set from another.")
def get_neighbours(coord, dims):
x, y = coord
row = [u for u in range(x-1, x+2) if u in range(dims[0])]
col = [v for v in range(y-1, y+2) if v in range(dims[1])]
return {(u, v) for u in row for v in col}
groups = []
all_coords = [(i, j) for i in range(dims[0])
for j in range(dims[1])]
for coord, nr in [(c, grid[c]) for c in all_coords if grid[c] > 0]:
empty_neighbours = {c for c in get_neighbours(coord, dims)
if grid[c] == 0}
if nr > len(empty_neighbours):
print "Error: number {} in cell {} is too high.".format(nr, coord)
break
groups.append(NumberGroup(nr, empty_neighbours, dims))
print groups
for g in groups:
print g
print groups[0] - groups[1]
UPDATE:
I have added a couple of other classes and restructured a bit (see below for working code), and it is now capable of creating and displaying the equivalence groups, which is a step in the right direction. However I still need to work out how to iterate through all the possible mine-configurations, by assigning a number of mines to each group in a way that creates a valid configuration. Any help is appreciated.
For example,
# # # #
# 2 1 #
# # # #
There are three equivalence groups G1: the left 3, G2: the middle 4, G3: the right 3. I want the code to loop through, assigning groups with mines in the following way:
G1=2 (max the first group) => G2=0 => G3=1 (this is all configs with G1=2)
G1=1 (decrease by one) => G2=1 => G3=0 (this is all with G1=1)
G1=0 => G2=2 INVALID
So we arrive at both configurations. This needs to work for more complicated setups!
import numpy as np
def get_neighbours(coord, dims):
x, y = coord
row = [u for u in range(x-1, x+2) if u in range(dims[0])]
col = [v for v in range(y-1, y+2) if v in range(dims[1])]
return {(u, v) for u in row for v in col}
class NrConfig(object):
def __init__(self, grid):
self.grid = grid
self.dims = grid.shape # Dimensions of grid
self.all_coords = [(i, j) for i in range(self.dims[0])
for j in range(self.dims[1])]
self.numbers = dict()
self.groups = []
self.configs = []
self.get_numbers()
self.get_groups()
self.get_configs()
def __str__(self):
return str(self.grid).replace('0', '.')
def get_numbers(self):
for coord, nr in [(c, self.grid[c]) for c in self.all_coords
if self.grid[c] > 0]:
empty_neighbours = {c for c in get_neighbours(
coord, self.dims) if self.grid[c] == 0}
if nr > len(empty_neighbours):
print "Error: number {} in cell {} is too high.".format(
nr, coord)
return
self.numbers[coord] = Number(nr, coord, empty_neighbours,
self.dims)
def get_groups(self):
coord_neighbours = dict()
for coord in [c for c in self.all_coords if self.grid[c] == 0]:
# Must be a set so that order doesn't matter!
coord_neighbours[coord] = {self.numbers[c] for c in
get_neighbours(coord, self.dims) if c in self.numbers}
while coord_neighbours:
coord, neighbours = coord_neighbours.popitem()
equiv_coords = [coord] + [c for c, ns in coord_neighbours.items()
if ns == neighbours]
for c in equiv_coords:
if c in coord_neighbours:
del(coord_neighbours[c])
self.groups.append(EquivGroup(equiv_coords, neighbours, self.dims))
def get_configs(self):
pass # WHAT GOES HERE?!
class Number(object):
"""Contains information about the group of cells around a number."""
def __init__(self, nr, coord, neighbours, dims):
"""Takes a number of mines, and a set of coordinates."""
self.nr = nr
self.coord = coord
# A list of the available neighbouring cells' coords.
self.neighbours = neighbours
self.dims = dims
def __repr__(self):
return "<Number {} with {} empty neighbours>".format(
int(self), len(self.neighbours))
def __str__(self):
grid = np.zeros(self.dims, int)
grid[self.coord] = int(self)
for coord in self.neighbours:
grid[coord] = 9
return str(grid).replace('0', '.').replace('9', '#')
def __int__(self):
return self.nr
class EquivGroup(object):
"""A group of cells which are effectively equivalent."""
def __init__(self, coords, nrs, dims):
self.coords = coords
# A list of the neighbouring Number objects.
self.nr_neighbours = nrs
self.dims = dims
if self.nr_neighbours:
self.max_mines = min(len(self.coords),
max(map(int, self.nr_neighbours)))
else:
self.max_mines = len(coords)
def __repr__(self):
return "<Equivalence group containing {} cells>".format(
len(self.coords))
def __str__(self):
grid = np.zeros(self.dims, int)
for coord in self.coords:
grid[coord] = 9
for number in self.nr_neighbours:
grid[number.coord] = int(number)
return str(grid).replace('0', '.').replace('9', '#')
grid = np.array(
[[0, 0, 0, 0],
[0, 2, 1, 0],
[0, 0, 0, 0]]
)
config = NrConfig(grid)
print config
print "Number groups:"
for n in config.numbers.values():
print n
print "Equivalence groups:"
for g in config.groups:
print g
If you don't want to brute-force it, you could model the process as a decision tree. Suppose we start with your example:
####
#21#
####
If we want to start placing mines in a valid configuration, we at this point essentially have eight choices. Since it doesn't really matter which square we pick within an equivalence group, we can narrow that down to three choices. The tree branches. Let's go down one branch:
*###
#11#
####
I placed a mine in G1, indicated by the asterisk. Also, I've updated the numbers (just one number in this case) associated with this equivalence group, to indicate that these numbered squares can now border one fewer mines.
This hasn't reduced our freedom of choice for the following step, we can still place a mine in any of the equivalence groups. Let's place another one in G1:
*XX#
*01#
XXX#
Another asterisk marks the new mine, and the numbered square has again been lowered by one. It has now reached zero, meaning it cannot border any more mines. That means that for our next choice of mine placement, all the equivalence groups dependent upon this numbered square are ruled out. Xs mark squares where we can now not place any mine. We can only make one choice now:
*XX*
*00X
XXXX
Here the branch ends and you've found a valid configuration. By running along all the branches in this tree in this manner, you should find all of them. Here we found your first configuration. Of course, there's more than one way to get there. If we had started by placing a mine in G3, we would have been forced to place the other two in G1. That branch leads to the same configuration, so you should check for duplicates. I don't see a way to avoid this redundancy right now.
The second configuration is found by either starting with G2, or placing one mine in G1 and then the second in G2. In either case you again end up at a branch end:
**XX
X00X
XXXX
Invalid configurations like your example with zero mines in G1 do not pop up. There are no valid choices along the tree that lead you there. Here is the whole tree of valid choices.
Choice 1: 1 | 2 | 3
Choice 2: 1 2 3 | 1 | 1
Choice 3: 3 1 | |1
Valid configurations are the branch ends at which no further choice is possible, i.e.
113
12
131
21
311
which obviously fall into two equivalent classes if we disregard the order of the numbers.

For cycle gets stuck in Python

My code below is getting stuck on a random point:
import functions
from itertools import product
from random import randrange
values = {}
tables = {}
letters = "abcdefghi"
nums = "123456789"
for x in product(letters, nums): #unnecessary
values[x[0] + x[1]] = 0
for x in product(nums, letters): #unnecessary
tables[x[0] + x[1]] = 0
for line_cnt in range(1,10):
for column_cnt in range(1,10):
num = randrange(1,10)
table_cnt = functions.which_table(line_cnt, column_cnt) #Returns a number identifying the table considered
#gets the values already in the line and column and table considered
line = [y for x,y in values.items() if x.startswith(letters[line_cnt-1])]
column = [y for x,y in values.items() if x.endswith(nums[column_cnt-1])]
table = [x for x,y in tables.items() if x.startswith(str(table_cnt))]
#if num is not contained in any of these then it's acceptable, otherwise find another number
while num in line or num in column or num in table:
num = randrange(1,10)
values[letters[line_cnt-1] + nums[column_cnt-1]] = num #Assign the number to the values dictionary
print(line_cnt) #debug
print(sorted(values)) #debug
As you can see it's a program that generates random sudoku schemes using 2 dictionaries : values that contains the complete scheme and tables that contains the values for each table.
Example :
5th square on the first line = 3
|
v
values["a5"] = 3
tables["2b"] = 3
So what is the problem? Am I missing something?
import functions
...
table_cnt = functions.which_table(line_cnt, column_cnt) #Returns a number identifying the table considered
It's nice when we can execute the code right ahead on our own computer to test it. In other words, it would have been nice to replace "table_cnt" with a fixed value for the example (here, a simple string would have sufficed).
for x in product(letters, nums):
values[x[0] + x[1]] = 0
Not that important, but this is more elegant:
values = {x+y: 0 for x, y in product(letters, nums)}
And now, the core of the problem:
while num in line or num in column or num in table:
num = randrange(1,10)
This is where you loop forever. So, you are trying to generate a random sudoku. From your code, this is how you would generate a random list:
nums = []
for _ in range(9):
num = randrange(1, 10)
while num in nums:
num = randrange(1, 10)
nums.append(num)
The problem with this approach is that you have no idea how long the program will take to finish. It could take one second, or one year (although, that is unlikely). This is because there is no guarantee the program will not keep picking a number already taken, over and over.
Still, in practice it should still take a relatively short time to finish (this approach is not efficient but the list is very short). However, in the case of the sudoku, you can end up in an impossible setting. For example:
line = [6, 9, 1, 2, 3, 4, 5, 8, 0]
column = [0, 0, 0, 0, 7, 0, 0, 0, 0]
Where those are the first line (or any line actually) and the last column. When the algorithm will try to find a value for line[8], it will always fail since 7 is blocked by column.
If you want to keep it this way (aka brute force), you should detect such a situation and start over. Again, this is very unefficient and you should look at how to generate sudokus properly (my naive approach would be to start with a solved one and swap lines and columns randomly but I know this is not a good way).

Categories