Count possibilites for single crossovers - python

I have the following problem. In my code
I have arrays of size L. The entries are either 0 or 1 for all arrays. Lets set L=3 for an example. Three possible arrays are (1,1,1), (1,0,0) and (0,1,1). Now I want to know how many single crossovers are possible with (1,0,0) and (0,1,1) to form (1,1,1). The answer would be one. For (1,0,1) and (0,1,0) to form (1,1,1) the answer would be 0, because I would need two crossovers. I am looking for an algorithm which does this for general L. (L is usually not larger than 9). So far I don't have any idea and that is why I have post this problem her but I will make an edit if I got one. Hope you can help me :)
Edit: The outcome can be of course not only 0 or 1 but also greater than 1.
Example: (1,1,0,0) and (0,0,0,0) to form (0,0,0,0) the outcome would be 2. (I can only take the last entry of the first array or the last 2 entries of the first array)
Edit 2: By single crossover I mean, that I can take the left/right side of the first sequence and the right/left side of the second sequence to form the given third sequence. (1,1,0,0) and (0,0,0,0) to form (0,0,0,0) --> 0,0) + (0,0, or 0) + (0,0,0,

Another way of interpreting the problem is as calculating the Hamming Distance. Here is a snippet to create a dictionary of all pairs of each Hamming Distance/crossovers.
from itertools import combinations
tuples = [(0, 0, 1), (1, 0, 0), (1, 0, 1)]
crossovers = {k: [] for k in range(len(tuples[0]))}
for a, b in combinations(tuples, r=2):
num_crossovers = sum(el1 != el2 for el1, el2 in zip(a, b))
crossovers[num_crossovers].append((a, b))
After executing crossovers will be as follows
{0: [],
1: [((0, 0, 1), (1, 0, 1)), ((1, 0, 0), (1, 0, 1))],
2: [((0, 0, 1), (1, 0, 0))]}
EDIT:
I missed that you were using numpy arrays instead of tuples, you could do
arrays = np.array([[0, 0, 1], [1, 0, 0], [1, 0, 1]])
crossovers = {k: [] for k in range(arrays[0].size)}
for a, b in combinations(arrays, r=2):
num_crossovers = sum(np.abs(a - b))
crossovers[num_crossovers].append((a, b))

Related

Move a shape inside a list of lists

I have the following list of lists representing a matrix:
space = [
[0, 1, 1, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
]
The number 1s represent an upside down L (like a gamma, "Γ"). How can I make this "object" move to the right, left, up and down as if it was a block? I move it with "asdw" keys.
Important: I am not able to use numpy, so thats makes the work way more difficult.
This is my failed method to make a RIGHT direction movement (btw it doesnt make the movement correctly), but i dont think its the best way and dont really think I can escalate it to other movements:
def show_space(space):
for line in space:
print(line)
x = input("Movement: ")
if x == 'd':
for i in range(4):
for j in range(4):
if space[i][j] == 1:
try:
if space[i][j+1] == 0:
space[i][j] = 0
space[i][j+1] = 1
if space[i][j+1] == 1 and space[i][j+2] == 0:
space[i][j] = 0
space[i][j+2] = 1
except IndexError:
pass
show_space(space)
Is there any other method I could try? Or correct the one Im using? Thanks in advance
EDIT:
The gamma not only should be able to move right up down left, it should also be able to move 90 degrees, mirror itself, and all possible shapes that form can take. So if i had to hardcode all possible gamma or L combinations, i would have to hardcode 48 possibilities. I dont know wether hardcoding that is the optimal way to be honest.
Im not saying hardcoding the postions is not acceptable, it could definitely be a solution, but I just dont feel like its the correct way. i may be wrong of course.
What do you think?
Here's how I'd suggest doing something like this:
Calculate all the indices for each gamma shape in the matrix, kept stored in a dictionary with the each corner index tuple as the dictionary keys, then whenever the corner moves, figure out the indices that should be 1s, and assign to a copy of a matrix of zeros.
positions = {(0, 0): ((0, 1), (1, 0), (2, 0)),
(0, 1): ((0, 2), (1, 1), (2, 1)),
(0, 2): ((0, 3), (1, 2), (2, 2)),
(1, 0): ((1, 1), (2, 0), (3, 0)),
(1, 1): ((1, 2), (2, 1), (3, 1)),
(1, 2): ((1, 3), (2, 2), (3, 2))}
def move_gamma(corner):
board = [[0 for _ in range(4)] for _ in range(4)]
try:
for (i, j) in (corner, *positions[corner]):
board[i][j] = 1
return board
except KeyError:
print("You can't move there!")
return board
def flip_h(board):
return [[*reversed(row)] for row in board]
def flip_v(board):
return [*reversed(board)]
def transpose(board):
return [[*t] for t in zip(*board)]
Demo:
In [3]: board = move_gamma((1, 1))
In [4]: print(*board, sep="\n")
[0, 0, 0, 0]
[0, 1, 1, 0]
[0, 1, 0, 0]
[0, 1, 0, 0]
In [5]: board = move_gamma((1, 2))
In [6]: print(*board, sep="\n")
[0, 0, 0, 0]
[0, 0, 1, 1]
[0, 0, 1, 0]
[0, 0, 1, 0]
In [7]: print(*transpose(board), sep="\n")
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 1, 1, 1]
[0, 1, 0, 0]
Just a heads up, you'd still need to implement the logic for mapping WASD to movement relative to the current corner indices.
If you need to stay within the confines of the standards library, you can use collection.deque, which has a rotate method that does exactly what you need and will have to implement in lists if you can't use deque.
This is what I can offer with deque.
NB. this wraps around the edges which might not be intended,
from collections import deque
space = deque([
deque([0, 1, 1, 0]),
deque([0, 0, 1, 0]),
deque([0, 0, 1, 0]),
deque([0, 0, 0, 0]),
])
def move(mvt):
if mvt == "s":
space.rotate(1)
elif mvt == "w":
space.rotate(-1)
elif mvt == "d":
[x.rotate(1) for x in space]
elif mvt == "a":
[x.rotate(-1) for x in space]
else:
raise NotImplementedError
move("d") # etc
Your answer is in your question - instead of keeping track of all space (array with mostly 0s), use an object (python dict) to instead specify the shape, and where to start drawing it e.g.:
{
'abs_pos': (0,1),
'rel_points': [(0,0), (0,1), (1,0), (2,0)]
}
Then moving it only amounts to updating the location of (e.g.) the upper-left corner abs_pos.
When it's time to print your map of all space, start from abs_pos and then add a dot at each point in rel_points.

What does array[...,list([something]) mean?

I am going through the following lines of code but I didn't understand image[...,list()]. What do the three dots mean?
self.probability = 0.5
self.indices = list(permutations(range(3), 3))
if random.random() < self.probability:
image = np.asarray(image)
image = Image.fromarray(image[...,list(self.indices[random.randint(0, len(self.indices) - 1)])])
What exactly is happening in the above lines?
I have understood that the list() part is taking random channels from image? Am I correct?
It is an object in Python called Ellipsis (for example, as a placeholder for something missing).
x = np.random.rand(3,3,3,3,3)
elem = x[:, :, :, :, 0]
elem = x[..., 0] # same as above
This should be helpful if you want to access a specific element in a multi-dimensional array in NumPy.
list(permutations(range(3), 3)) generates all permutations of the intergers 0,1,2.
from itertools import permutations
list(permutations(range(3), 3))
# [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
So the following chooses among these tuples of permutations:
list(self.indices[random.randint(0, len(self.indices) - 1)])]
In any case you'll have a permutation over the last axis of image which is usually the image channels RGB (note that with the ellipsis (...) here image[...,ixs] we are taking full slices over all axes except for the last. So this is performing a shuffling of the image channels.
An example run -
indices = list(permutations(range(3), 3))
indices[np.random.randint(0, len(indices) - 1)]
# (2, 0, 1)
Here's an example, note that this does not change the shape, we are using integer array indexing to index on the last axis only:
a = np.random.randint(0,5,(5,5,3))
a[...,(0,2,1)].shape
# (5, 5, 3)

How to segment a matrix by neighbouring values?

Suppose I have a matrix like this:
m = [0, 1, 1, 0,
1, 1, 0, 0,
0, 0, 0, 1]
And I need to get the coordinates of the same neighbouring values (but not diagonally):
So the result would be a list of lists of coordinates in the "matrix" list, starting with [0,0], like this:
r = [[[0,0]],
[[0,1], [0,2], [1,0], [1,1]],
[[0,3], [1,2], [1,3], [2,0], [2,1], [2,2]]
[[2,3]]]
There must be a way to do that, but I'm really stuck.
tl;dr: We take an array of zeros and ones and use scipy.ndimage.label to convert it to an array of zeros and [1,2,3,...]. We then use np.where to find the coordinates of each element with value > 0. Elements that have the same value end up in the same list.
scipy.ndimage.label interprets non-zero elements of a matrix as features and labels them. Each unique feature in the input gets assigned a unique label. Features are e.g. groups of adjacent elements (or pixels) with the same value.
import numpy as np
from scipy.ndimage import label
# make dummy data
arr = np.array([[0,1,1,0], [1,1,0,0], [0,0,0,1]])
#initialise list of features
r = []
Since OP wanted all features, that is groups of zero and non-zero pixels, we use label twice: First on the original array, and second on 1 - original array. (For an array of zeros and ones, 1 - array just flips the values).
Now, label returns a tuple, containing the labelled array (which we are interested in) and the number of features that it found in that array (which we could use, but when I coded this, I chose to ignore it. So, we are interested in the first element of the tuple returned by label, which we access with [0]:
a = label(arr)[0]
b = label(1-arr)[0]
Now we check which unique pixel values label has assigned. So we want the set of a and b, repectively. In order for set() to work, we need to linearise both arrays, which we do with .ravel(). We have to subtract {0} in both cases, because for both a and b we are interested in only the non-zero values.
So, having found the unique labels, we loop through these values, and use np.where to find where on the array a given value is located. np.where returns a tuple of arrays. The first element of this tuple are all the row-coordinates for which the condition was met, and the second element are the column-coordinates.
So, we can use zip(* to unpack the two containers of length n to n containers of length 2. This means that we go from list of all row-coords + list of all column-coords to list of all row-column-coordinate pairs for which the condition is met. Finally in python 3, zip is a generator, which we can evaluate by calling list() on it. The resulting list is then appended to our list of coordinates, r.
for x in set(a.ravel())-{0}:
r.append(list(zip(*np.where(a==x))))
for x in set(b.ravel())-{0}:
r.append(list(zip(*np.where(b==x))))
print(r)
[[(0, 1), (0, 2), (1, 0), (1, 1)],
[(2, 3)],
[(0, 0)],
[(0, 3), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2)]]
That said, we can speed up this code slightly by making use of the fact that label returns the number of features it assigned. This allows us to avoid the set command, which can take time on large arrays:
a, num_a = label(arr)
for x in range(1, num_a+1): # range from 1 to the highest label
r.append(list(zip(*np.where(a==x))))
A solution with only standard libraries:
from pprint import pprint
m = [0, 1, 1, 0,
1, 1, 0, 0,
0, 0, 0, 1]
def is_neighbour(x1, y1, x2, y2):
return (x1 in (x2-1, x2+1) and y1 == y2) or \
(x1 == x2 and y1 in (y2+1, y2-1))
def is_value_touching_group(val, groups, x, y):
for d in groups:
if d['color'] == val and any(is_neighbour(x, y, *cell) for cell in d['cells']):
return d
def check(m, w, h):
groups = []
for i in range(h):
for j in range(w):
val = m[i*w + j]
touching_group = is_value_touching_group(val, groups, i, j)
if touching_group:
touching_group['cells'].append( (i, j) )
else:
groups.append({'color':val, 'cells':[(i, j)]})
final_groups = []
while groups:
current_group = groups.pop()
for c in current_group['cells']:
touching_group = is_value_touching_group(current_group['color'], groups, *c)
if touching_group:
touching_group['cells'].extend(current_group['cells'])
break
else:
final_groups.append(current_group['cells'])
return final_groups
pprint( check(m, 4, 3) )
Prints:
[[(2, 3)],
[(0, 3), (1, 3), (1, 2), (2, 2), (2, 0), (2, 1)],
[(0, 1), (0, 2), (1, 1), (1, 0)],
[(0, 0)]]
Returns as a list of groups under value key.
import numpy as np
import math
def get_keys(old_dict):
new_dict = {}
for key, value in old_dict.items():
if value not in new_dict.keys():
new_dict[value] = []
new_dict[value].append(key)
else:
new_dict[value].append(key)
return new_dict
def is_neighbor(a,b):
if a==b:
return True
else:
distance = abs(a[0]-b[0]), abs(a[1]-b[1])
return distance == (0,1) or distance == (1,0)
def collate(arr):
arr2 = arr.copy()
ret = []
for a in arr:
for i, b in enumerate(arr2):
if set(a).intersection(set(b)):
a = list(set(a+b))
ret.append(a)
for clist in ret:
clist.sort()
return [list(y) for y in set([tuple(x) for x in ret])]
def get_groups(d):
for k,v in d.items():
ret = []
for point in v:
matches = [a for a in v if is_neighbor(point, a)]
ret.append(matches)
d[k] = collate(ret)
return d
a = np.array([[0,1,1,0],
[1,1,0,0],
[0,0,1,1]])
d = dict(np.ndenumerate(a))
d = get_keys(d)
d = get_groups(d)
print(d)
Result:
{
0: [[(0, 3), (1, 2), (1, 3)], [(0, 0)], [(2, 0), (2, 1)]],
1: [[(2, 2), (2, 3)], [(0, 1), (0, 2), (1, 0), (1, 1)]]
}

How to make generated matrix non-repeating in Python?

We have created a matrix that is a random size, and has random digits, but we aren't sure how to make sure that none of the generated strings are the same. For reference, we need the matrix to be non-repeating because we're trying to calculate the recursive teaching dimension -- basically a complexity measurement of data sets -- of the set of all the strings and it can't be computed if any of the strings are the same.
An example of a matrix generated is:
[[0 0 0]
[0 1 0]
[0 1 0]
[0 0 1]
[1 0 0]
[1 0 1]
[0 0 0]]
As you can see, the second and third strings and the first and last strings are identical.
This is our current code. How should we go about ensuring that nothing repeats?
def matrix():
import numpy as np
import random
a = random.randrange (2, 10)
b = random.randrange (2, a)
A = np.random.randint(2, size=(a,b))
print (A)
matrix()
If you are not fixated on numpy, here is an option using itertools' product and random's sample:
import itertools
import random
b = random.randrange (2, 10)
a = random.randrange (2, 2**b)
words = list(itertools.product([0, 1], repeat=b))
matrix = random.sample(words, a)
print(matrix)
Running with fixed values of b=3 and a=7 gives:
[(1, 0, 1),
(0, 1, 0),
(0, 1, 1),
(1, 0, 0),
(1, 1, 1),
(1, 1, 0),
(0, 0, 1)]
Note that product returns tuples, so if that matters, a simple conversion is needed:
words = [list(tup) for tup in itertools.product([0, 1], repeat=b)]
One possibility would be to choose a different numbers between zero and 2^b-1
and use their binary representations as the different strings in A.
import numpy as np
a = random.randrange (2, 10)
b = random.randrange (2, a)
# Choos numbers between 0 and the largest representable number with b digits
nums_base_ten = np.random.choice(np.arange(2**b-1), a, replace=False)
print(nums)
A = np.zeros((a, b))
# Loop over digits and generate the binary representation for the chosen numbers
# Fill in ones if necessary to represent the numbers, reduce, repeat
for i in range(b):
temp_value_base2 = 2**(b-1-i)
bool_digit = nums >= temp_value_base2
A[:, i] = bool_digit
nums[bool_digit] -= temp_value_base2
print(A)

Finding longest path using recursion with a set of rules

For a bit of background, I'm not a computer scientist or programmer at all (studied physics in college where I picked up some python). My problem is to find the longest path through a matrix with a given set of rules. An example matrix would look something like this:
[0, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[1, 1, 1, 0, 0],
[0, 0, 0, 0, 0],
[1, 0, 0, 1, 0],
The rules are as follows:
Start on a given "1" position, these are the valid positions.
Each jump must be to another valid position on the same row or column (it's possible to jump over "0"s).
Consecutive jumps can not be in the same direction (horizontal/vertical) unless jumping to and from a position on-diagonal.
No position can be used twice.
A valid path on the example matrix would look like:
(5,4),(5,1),(3,1),(3,3),(3,2),(2,2)
An invalid path because of rule #3 would look like:
(3,1),(3,2),(3,3)
whereas the following is possible:
(3,1),(3,3),(3,2)
Though I have a bit of experience with python, I've never tried recursion (I'm pretty sure that's how to tackle this), and I can't seem to find any help online that's at my level.
There are several solutions for this. I would suggest first converting the grid to a more object oriented structure, i.e. an undirected graph with nodes for where there are 1s in the input.
Then I would distinguish three kinds of edges in that graph:
Those where one of the end points is on a diagonal ("special" edges)
Those where the above is not true, but nodes are in the same row
Those where the above is not true, but nodes are in the same column
While recurring, you would always consider the special edges, and in addition to that, the edges that are in one of the other two sets of edges (based on the previous direction taken).
Here is an implementation:
class Node:
def __init__(self, y, x, size):
self.x = x
self.y = y
self.coord = (y, x)
self.diagonal = x == y or size - 1 - y
# Separate lists of neighbors: vertical, horizontal.
# Third list is for when this node or neighbor is on diagonal
self.neighbors = [[], [], []]
def addNeighbor(self, node, direction):
self.neighbors[direction].append(node)
class Maze:
def __init__(self, grid):
def addedge(a, b):
direction = 2 if a.diagonal or b.diagonal else int(a.x == b.x)
a.addNeighbor(b, direction)
b.addNeighbor(a, direction)
# alternative grid having Node references:
self.nodes = [[None] * len(grid) for _ in grid]
colNodes = [[] for _ in grid]
for y, row in enumerate(grid):
rowNodes = []
for x, cell in enumerate(row):
if cell: # only create nodes for when there is a 1 in the grid
node = Node(y, x, len(grid))
for neighbor in rowNodes + colNodes[x]:
addedge(node, neighbor)
rowNodes.append(node)
colNodes[x].append(node)
self.nodes[y][x] = node
def findpath(self, y, x):
def recur(node, neighbors):
visited.add(node)
longest = [node.coord]
# always visit "special" neighbors
# (i.e. those on diagonal or all vert/horiz when node is on diagonal)
for neighbor in node.neighbors[2] + neighbors:
if not neighbor in visited:
# toggle direction when going further
path = recur(neighbor, node.neighbors[1-int(node.x == neighbor.x)])
if len(path) >= len(longest):
longest = [node.coord] + path
visited.remove(node)
return longest
node = self.nodes[y][x]
if not node:
raise "Cannot start from that position"
visited = set()
# look in both directions of starting node
return recur(node, node.neighbors[0] + node.neighbors[1])
grid = [
[0, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[1, 1, 1, 0, 0],
[0, 0, 0, 0, 0],
[1, 0, 0, 1, 0]
]
maze = Maze(grid)
path = maze.findpath(2, 0)
print(path) # output: [(2, 0), (2, 2), (2, 1), (1, 1)]
path = maze.findpath(4, 3)
print(path) # output: [(4, 3), (4, 0), (2, 0), (2, 2), (2, 1), (1, 1)]
Note that the coordinates in this solution are zero-based, so the first row has number 0, ...etc.
See it run on repl.it
You can use recursion with a generator:
d = [[0, 0, 0, 0, 0], [0, 1, 0, 0, 0], [1, 1, 1, 0, 0], [0, 0, 0, 0, 0], [1, 0, 0, 1, 0]]
_d = {1:lambda a, b:'f' if b[-1] > a[-1] else 'b', 0:lambda a, b:'u' if b[0] > a[0] else 'd'}
def paths(start, _dir, c = []):
yield c
_options = [(a, b) for a in range(len(d)) for b in range(len(d[0])) if (a, b) not in c and d[a][b]]
if _options:
for a, b in _options:
if a == start[0] or b == start[-1]:
r = _d[a == start[0]](start, (a, b))
if _dir is None or r != _dir:
yield from paths((a, b), r, c+[(a, b)])
print(max(list(paths((4, 3), None, [(4, 3)])), key=len))
Output:
[(4, 3), (4, 0), (2, 0), (2, 2), (2, 1), (1, 1)]

Categories