efficient way to find domino's board order - python

I have a small domino's game that works this way: I'm given a N*N 4-tiles, and I need to order them so that every two adjacent tiles have the same number. The tiles may be rotated. For example, here is my 2*2 board:
a,b,c,d = [1,2,3,4], [7,9,6,2], [6,8,8,5], [3,5,0,0]
They can be visualized by:
print(print_2_tiles(a,b,'a','b'))
print(print_2_tiles(d,c,'d','c'))
##############
#**1**##**7**#
#4*A*2##2*B*9#
#**3**##**6**#
##############
##############
#**3**##**6**#
#0*D*5##5*C*8#
#**0**##**8**#
##############
It can be seen, that the only way to "win" this board, is the way I ordered the tiles, since <a,b> are only connected via 2, <a,d> only via 3, and so on... <a,c>,<b,d> are not connected at all. No rotation or movement of any of the tiles will get a "win".
I wrote functions to:
find connections between any given 2 tiles
figure out how many rotations are needed to connect given 2 tiles
check all possibilities and find the correct order
However, this was only a simple case with 16*12*8 options, where I could rule out many options since there were unique connectors (i.e. '2' that connected <a,c> was not present in other tiles). If I get a bigger board (bigger alphabet could also complicate things...), say, 5*5, the number of options will be 100*96*92... and brute-forcing will not cut it.
How can I find the right order (the board is guaranteed to have exactly one correct order) efficiently?
Here are my efforts:
import numpy as np
from itertools import combinations, product
# returns list of [<connector element>, <indices of element in a>, <indices of element in b>]
def find_connections(a,b):
intersected_elem = np.array(list(set(a).intersection(b)))
possible_connections = []
for val in intersected_elem:
x = list(np.where(np.array(a) == val)[0])
y = list(np.where(np.array(b) == val)[0])
possible_connections.append([val,x,y])
return possible_connections
def str_tile(t, name):
template = '''#######
#**{}**#
#{}*{}*{}#
#**{}**#
#######'''
up,right,down,left = t
return template.format(up,left,name.upper(),right,down)
def print_2_tiles(a,b, name_a, name_b):
res = ''
for line in zip(str_tile(a,name_a).splitlines(), str_tile(b,name_b).splitlines()):
res += ''.join(line)
res += '\n'
return res[:-1]
def find_final_connections(tiles_ls):
tiles_combinations = list(combinations(tiles_ls, 2))
a_idx,b_idx = 0,1
final_connections = []
for comb in tiles_combinations:
connections = find_connections(comb[0], comb[1])
print('({},{})'.format(a_idx,b_idx), connections, end='\t')
if len(connections):
print('this meants {},{} are connected via {} in directions {},{}'.format(a_idx,b_idx, connections[0][0], connections[0][1][0], connections[0][2][0]))
final_connections.append((a_idx,b_idx))
else:
print()
# is there a neater way, using enumerate on itertools.combinations?
b_idx += 1
if b_idx == len(tiles_ls):
a_idx += 1
b_idx = a_idx + 1
print(final_connections)
a,b,c,d = [1,2,3,4], [7,9,6,2], [6,8,8,5], [3,5,0,0]
tiles_ls = [a,b,c,d]
find_final_connections(tiles_ls) # returns a 4-elem list -> success
print('#'*30)
a,b,c,d = [1,2,3,4], [7,9,6,2], [6,8,8,5], [0,5,0,0]
tiles_ls = [a,b,c,d]
find_final_connections(tiles_ls) # returns a 3-elem list -> fail

Is it so sure that brute-forcing can't do ?
I would try a systematic solution where you pick every domino in turn and place it top-left, trying all four rotations. Then pick another domino and place it in the second position, trying all four rotations and checking if there is a match with the first one.
And so on, at any stage you pick a domino from the remaining ones, try it with the four rotations and check compatibility with the known neighbors.
This is better written as a recursive procedure.

Related

Compute counts of set partitions with mulitplicity and without order

I do have a piece of code that compute partitions of a set of (potentialy duplicated) integers. But i am interested in the set of possible partition and there multiplicity.
You can for exemple launch the follwoing code :
import numpy as np
from collections import Counter
import pandas as pd
def _B(i):
# for a given multiindex i, we defined _B(i) as the set of integers containg i_j times the number j:
if len(i) != 1:
B = []
for j in range(len(i)):
B.extend(i[j]*[j])
else:
B = i*[0]
return B
def _partition(collection):
# from here: https://stackoverflow.com/a/62532969/8425270
if len(collection) == 1:
yield (collection,)
return
first = collection[0]
for smaller in _partition(collection[1:]):
# insert `first` in each of the subpartition's subsets
for n, subset in enumerate(smaller):
yield smaller[:n] + ((first,) + subset,) + smaller[n + 1 :]
# put `first` in its own subset
yield ((first,),) + smaller
def to_list(tpl):
# the final hierarchy is
return list(list(i) if isinstance(i, tuple) else i for i in tpl)
def _Pi(inst_B):
# inst_B must be a tuple
if type(inst_B) != tuple :
inst_B = tuple(inst_B)
pp = [tuple(sorted(p)) for p in _partition(inst_B)]
c = Counter(pp)
Pi = c.keys()
N = list()
for pi in Pi:
N.append(c[pi])
Pi = [to_list(pi) for pi in Pi]
return Pi, N
if __name__ == "__main__":
import cProfile
pr = cProfile.Profile()
pr.enable()
sh = (3, 3, 3)
rez = list()
rez_sorted= list()
rez_ref = list()
for idx in np.ndindex(sh):
if sum(idx) > 0:
print(idx)
Pi, N = _Pi(_B(idx))
print(pd.DataFrame({'Pi': Pi, 'N': N * np.array([np.math.factorial(len(pi) - 1) for pi in Pi])}))
pr.disable()
# after your program ends
pr.print_stats(sort="tottime")
This code computes, for several examples of tuples of integer numbers (generated by np.ndindex) the partitions and counts i need. Everything happens in the _partition and the _Pi functions, this is were you should look at.
If you look closely at how these two functions are working, you'll see that they comput eevery potential partition and THEN count up how many times they appeared. For small problems, this is fine, but if the size of the prolbme increase, this starts to take a looooot of time. Try setting sh = (5,5,5), you'll see what i mean;
So the problem is the following :
Is there a way to compute directly the partitions and there number of occurences instead ?
Edit: I cross-posted on mathoverflow there, and they propose a solution in this article, in corrolary 2.10 (page 10 of the pdf). The problem could be solved by implmenting the sets p(v,r) in this corrolary.
I was hoping, as in the univariate case, that those sets would have a nice recursive expression but i ould not find one yet.
More Edit : This problem is equivalent to finding all (multiset)-partitions of a multiset. If the solution for finding (set)-partitions of a set is given by Bell partial polynomials, here we need multivariate version of these polynomials.

Random mutation

I am a beginner python coder and I am writing a code to generate random mutation at random position.
I have written a function which includes:
The sequence where mutation happens
A List of nucleotide from which a nucleotide is selected randomly and replaced to the nucleotide of the original sequence.
Basic concept of the code:
Say we have to pick one ball from (A) basket and replace with another ball from another basket (B). The colors of the two balls need to be different.
I know I need to use while loop but I am not able to do it.
def random(s)
length = len(s)
seq = list(s)
nucl = "ATGC" ## pick one nucleotide from this list
lengthnucl= len(nucleotide_list)
position_orgseq = np.random.choice(range(0,length))
position_nucl = np.random.choice(range(0,lengthnucl))
#while c < length:
##if the two nucleotides chosen are not equaul then:
#two nucleotides are from
# TTTTGGGCCCCAAA - original seq, ATGC = nucloetide list
if seq[position_orgseq] != nucleotide_list[position_nucl]:
seq[position_orgseq] = nucleotide_list[position_nucl]
final = "".join(seq)
return s,final
actual_seq, mut_seq = random("TTTTGGGCCCCAAA")
print(actual_seq)
print(mut_seq)
First, as #Error - Syntatical Remorse pointed out in the comment, there is no need to import numpy, use built in random instead (specifically, you can use random.randint()).
Your code as is, doesn't run, you have misnamed variables. Other than that, you are close. Your hunch to using a while loop is correct. You can simply keep looping until your two random values don't give the same nucleotide in the two lists. Like so:
from random import randint
def random(s):
length = len(s)
seq = list(s)
nucl = "ATGC"
lengthnucl = len(nucl)
position_orgseq = randint(0, length - 1)
position_nucl = randint(0, lengthnucl - 1)
while seq[position_orgseq] == nucl[position_nucl]:
position_orgseq = randint(0, length - 1)
position_nucl = randint(0, lengthnucl - 1)
seq[position_orgseq] = nucl[position_nucl]
final = "".join(seq)
return s, final
actual_seq, mut_seq = random("TTTTGGGCCCCAAA")
print(actual_seq)
print(mut_seq)
This may be optimized further.

Sorting from smallest to biggest

I would like to sort several points from smallest to biggest however.
I will wish to get this result:
Drogba 2 pts
Owen 4 pts
Henry 6 pts
However, my ranking seems to be reversed for now :-(
Henry 6 pts
Owen 4 pts
Drogba 2 pts
I think my problem is with my function Bubblesort ?
def Bubblesort(name, goal1, point):
swap = True
while swap:
swap = False
for i in range(len(name)-1):
if goal1[i+1] > goal1[i]:
goal1[i], goal1[i+1] = goal1[i+1], goal1[i]
name[i], name[i+1] = name[i+1], name[i]
point[i], point[i + 1] = point[i + 1], point[i]
swap = True
return name, goal1, point
def ranking(name, point):
for i in range(len(name)):
print(name[i], "\t" , point[i], " \t ")
name = ["Henry", "Owen", "Drogba"]
point = [0]*3
goal1 = [68, 52, 46]
gain = [6,4,2]
name, goal1, point = Bubblesort( name, goal1, point )
for i in range(len(name)):
point[i] += gain[i]
ranking (name, point)
In your code:
if goal1[i+1] > goal1[i]:
that checks if it is greater. You need to swap it if the next one is less, not greater.
Change that to:
if goal1[i+1] < goal1[i]:
A bunch of issues:
def Bubblesort - PEP8 says function names should be lowercase, ie def bubblesort
You are storing your data as a bunch of parallel lists; this makes it harder to work on and think about (and sort!). You should transpose your data so that instead of having a list of names, a list of points, a list of goals you have a list of players, each of whom has a name, points, goals.
def bubblesort(name, goal1, point): - should look like def bubblesort(items) because bubblesort does not need to know that it is getting names and goals and points and sorting on goals (specializing it that way keeps you from reusing the function later to sort other things). All it needs to know is that it is getting a list of items and that it can compare pairs of items using >, ie Item.__gt__ is defined.
Instead of using the default "native" sort order, Python sort functions usually let you pass an optional key function which allows you to tell it what to sort on - that is, sort on key(items[i]) > key(items[j]) instead of items[i] > items[j]. This is often more efficient and/or convenient than reshuffling your data to get the sort order you want.
for i in range(len(name)-1): - you are iterating more than needed. After each pass, the highest value in the remaining list gets pushed to the top (hence "bubble" sort, values rise to the top of the list like bubbles). You don't need to look at those top values again because you already know they are higher than any of the remaining values; after the nth pass, you can ignore the last n values.
actually, the situation is a bit better than that; you will often find runs of values which are already in sorted order. If you keep track of the highest index that actually got swapped, you don't need to go beyond that on your next pass.
So your sort function becomes
def bubblesort(items, *, key=None):
"""
Return items in sorted order
"""
# work on a copy of the list (don't destroy the original)
items = list(items)
# process key values - cache the result of key(item)
# so it doesn't have to be called repeatedly
keys = items if key is None else [key(item) for item in items]
# initialize the "last item to sort on the next pass" index
last_swap = len(items) - 1
# sort!
while last_swap:
ls = 0
for i in range(last_swap):
j = i + 1
if keys[i] > keys[j]:
# have to swap keys and items at the same time,
# because keys may be an alias for items
items[i], items[j], keys[i], keys[j] = items[j], items[i], keys[j], keys[i]
# made a swap - update the last_swap index
ls = i
last_swap = ls
return items
You may not be sure that this is actually correct, so let's test it:
from random import sample
def test_bubblesort(tries = 1000):
# example key function
key_fn = lambda item: (item[2], item[0], item[1])
for i in range(tries):
# create some sample data to sort
data = [sample("abcdefghijk", 3) for j in range(10)]
# no-key sort
assert bubblesort(data) == sorted(data), "Error: bubblesort({}) gives {}".format(data, bubblesort(data))
# keyed sort
assert bubblesort(data, key=key_fn) == sorted(data, key=key_fn), "Error: bubblesort({}, key) gives {}".format(data, bubblesort(data, key_fn))
test_bubblesort()
Now the rest of your code becomes
class Player:
def __init__(self, name, points, goals, gains):
self.name = name
self.points = points
self.goals = goals
self.gains = gains
players = [
Player("Henry", 0, 68, 6),
Player("Owen", 0, 52, 4),
Player("Drogba", 0, 46, 2)
]
# sort by goals
players = bubblesort(players, key = lambda player: player.goals)
# update points
for player in players:
player.points += player.gains
# show the result
for player in players:
print("{player.name:<10s} {player.points:>2d} pts".format(player=player))
which produces
Drogba 2 pts
Owen 4 pts
Henry 6 pts

different result from recursive and dynamic programming

Working on below problem,
Problem,
Given a m * n grids, and one is allowed to move up or right, find the different paths between two grid points.
I write a recursive version and a dynamic programming version, but they return different results, and any thoughts what is wrong?
Source code,
from collections import defaultdict
def move_up_right(remaining_right, remaining_up, prefix, result):
if remaining_up == 0 and remaining_right == 0:
result.append(''.join(prefix[:]))
return
if remaining_right > 0:
prefix.append('r')
move_up_right(remaining_right-1, remaining_up, prefix, result)
prefix.pop(-1)
if remaining_up > 0:
prefix.append('u')
move_up_right(remaining_right, remaining_up-1, prefix, result)
prefix.pop(-1)
def move_up_right_v2(remaining_right, remaining_up):
# key is a tuple (given remaining_right, given remaining_up),
# value is solutions in terms of list
dp = defaultdict(list)
dp[(0,1)].append('u')
dp[(1,0)].append('r')
for right in range(1, remaining_right+1):
for up in range(1, remaining_up+1):
for s in dp[(right-1,up)]:
dp[(right,up)].append(s+'r')
for s in dp[(right,up-1)]:
dp[(right,up)].append(s+'u')
return dp[(right, up)]
if __name__ == "__main__":
result = []
move_up_right(2,3,[],result)
print result
print '============'
print move_up_right_v2(2,3)
In version 2 you should be starting your for loops at 0 not at 1. By starting at 1 you are missing possible permutations where you traverse the bottom row or leftmost column first.
Change version 2 to:
def move_up_right_v2(remaining_right, remaining_up):
# key is a tuple (given remaining_right, given remaining_up),
# value is solutions in terms of list
dp = defaultdict(list)
dp[(0,1)].append('u')
dp[(1,0)].append('r')
for right in range(0, remaining_right+1):
for up in range(0, remaining_up+1):
for s in dp[(right-1,up)]:
dp[(right,up)].append(s+'r')
for s in dp[(right,up-1)]:
dp[(right,up)].append(s+'u')
return dp[(right, up)]
And then:
result = []
move_up_right(2,3,[],result)
set(move_up_right_v2(2,3)) == set(result)
True
And just for fun... another way to do it:
from itertools import permutations
list(map(''.join, set(permutations('r'*2+'u'*3, 5))))
The problem with the dynamic programming version is that it doesn't take into account the paths that start from more than one move up ('uu...') or more than one move right ('rr...').
Before executing the main loop you need to fill dp[(x,0)] for every x from 1 to remaining_right+1 and dp[(0,y)] for every y from 1 to remaining_up+1.
In other words, replace this:
dp[(0,1)].append('u')
dp[(1,0)].append('r')
with this:
for right in range(1, remaining_right+1):
dp[(right,0)].append('r'*right)
for up in range(1, remaining_up+1):
dp[(0,up)].append('u'*up)

How to make a function determining the winner of Tic-Tac-Toe more concise

I'm writing a Python script which is supposed to allow human and computer players to play Tic Tac Toe. To represent the board, I'm using a 3x3 Numpy array with 1 and 0 for the marks of the players (instead of "X" and "O"). I've written the following function to determine the winner:
import numpy as np
class Board():
def __init__(self, grid = np.ones((3,3))*np.nan):
self.grid = grid
def winner(self):
rows = [self.grid[i,:] for i in range(3)]
cols = [self.grid[:,j] for j in range(3)]
diag = [np.array([self.grid[i,i] for i in range(3)])]
cross_diag = [np.array([self.grid[2-i,i] for i in range(3)])]
lanes = np.concatenate((rows, cols, diag, cross_diag))
if any([np.array_equal(lane, np.ones(3)) for lane in lanes]):
return 1
elif any([np.array_equal(lane, np.zeros(3)) for lane in lanes]):
return 0
So for example, if I execute
board = Board()
board.grid = np.diag(np.ones(3))
print board.winner()
I get the result 1. What bothers me slightly is the repetition of the any statements. I would think there would be a more concise, DRY way of coding this. (I was thinking of a switch/case as in MATLAB but this doesn't exist in Python). Any suggestions?
Another option is to check the sum of lanes.
s = np.sum(lanes, axis=1)
if 3 in s:
return 1
elif 0 in s:
return 0
I have made a loop instead, and return only once, to conform with PEP8 and to be honest to my personal coding standards :)
enumerate in the correct order will yield 0,zeromatrix then 1,onematrix
rval = None
for i,m in enumerate([np.zeros(3),np.ones(3)]):
if any([np.array_equal(lane, m) for lane in lanes]):
rval = i; break
return rval
I found out one way, by using a Lambda function:
any_lane = lambda x: any([np.array_equal(lane, x) for lane in lanes])
if any_lane(np.ones(3)):
return 1
elif any_lane(np.zeros(3)):
return 0
This adds an extra line to the code but makes it more legible overall, I reckon.
This can be done in two lines, starting from the board (grid): simple sums along columns, rows and the two main diagonals gives you a value of 0 or 3 depending on who is winning (or some intermediate values only if nobody is winning). You can thus calculate something like:
# Score along each column, row and both main diagonals:
scores = (grid.sum(axis=0).tolist() + grid.sum(axis=1).tolist()
+[grid.trace(), np.flipud(grid).trace()])
# If there is no winner, None is declared the winner:
print "Winner:", 1 if 3 in scores else 0 if 0 in scores else None
where flipud() transforms the diagonal into the anti-diagonal (the diagonal at 90° from the main diagonal) by flipping the array horizontally, so that a simple trace() gives the total value along the anti-diagonal.

Categories

Resources