DFS solving grid puzzle problem - extremely slow - python

I borrowed some code from here in order to solve a puzzle game where you must pass over each square on a grid only once and avoid obstacles.
It works fine for anything less than an 8x8 grid but the problem I want to solve is a fairly complex 10x10 grid
Any help would be really appreciated!!! Thank you!
import numpy as np
# Function for dfs.
# i, j ==> Current cell indexes
# vis ==> To mark visited cells
# ans ==> Result
# z ==> Current count 0s visited
# z_count ==> Total 0s present
current_pos = 1
def dfs(i, j, grid, vis, ans, z, z_count):
n = len(grid)
m = len(grid[0])
global grid_test
global current_pos
# Mark the block as visited
vis[i][j] = 1
if ans < 1:
if (grid[i][j] == 0):
# Update the count
z += 1
current_pos += 1
grid_test[i][j] = current_pos
if (z == z_count):
ans += 1
print('worked')
vis[i][j] = 0
return grid, vis, ans
'''if (grid[i][j] == 200):
# If path covered all the non-
# obstacle blocks
if (z == z_count):
ans += 1
vis[i][j] = 0
return grid, vis, ans'''
# Up
if (i >= 1 and not vis[i - 1][j] and
grid[i - 1][j] != -1):
grid, vis, ans = dfs(i - 1, j, grid,
vis, ans, z,
z_count)
# Down
if (i < n - 1 and not vis[i + 1][j] and
grid[i + 1][j] != -1):
grid, vis, ans = dfs(i + 1, j, grid,
vis, ans, z,
z_count)
# Left
if (j >= 1 and not vis[i][j - 1] and
grid[i][j - 1] != -1):
grid, vis, ans = dfs(i, j - 1, grid,
vis, ans, z,
z_count)
# Right
if (j < m - 1 and not vis[i][j + 1] and
grid[i][j + 1] != -1):
grid, vis, ans = dfs(i, j + 1, grid,
vis, ans, z,
z_count)
# Unmark the block (unvisited)
vis[i][j] = 0
current_pos -= 1
return grid, vis, ans
def uniquePaths(grid):
# Total 0s present
z_count = 0
n = len(grid)
m = len(grid[0])
ans = 0
vis = [[0 for j in range(m)]
for i in range(n)]
x = 0
y = 0
for i in range(n):
for j in range(m):
# Count non-obstacle blocks
if grid[i][j] == 0:
z_count += 1
elif (grid[i][j] == 1):
# Starting position
x = i
y = j
grid, vis, ans = dfs(x, y, grid,
vis, ans, 0,
z_count)
print(np.matrix(grid))
print(np.matrix(grid_test))
return ans
The grid that it tries to solve is below, the -1s being the obstacles and the 0s the unvisited grid
# Driver code
if __name__ == '__main__':
grid = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, -1, 0, 0, 0, 0, 0],
[0, 0, -1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, -1, 0, 0, -1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
print(uniquePaths(grid))
The output for a simple 8x8 grid like this doesn't take too long
[[ 1 0 0 0 0 0 0 0]
[ 0 0 -1 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 -1 0]
[ 0 0 0 0 0 0 0 0]]
--->>>
Output:
[[ 1 16 17 18 35 36 53 54]
[ 2 15 -1 19 34 37 52 55]
[ 3 14 21 20 33 38 51 56]
[ 4 13 22 31 32 39 50 57]
[ 5 12 23 30 41 40 49 58]
[ 6 11 24 29 42 47 48 59]
[ 7 10 25 28 43 46 -1 60]
[ 8 9 26 27 44 45 62 61]]

Related

How do you make a checkerboard?

I need to make a checkerboard using only 1 and 0's by using lists to make a grid. I have created a code that creates an 8 by 8 grid of 0's. Then I attempt to use a nested for loop to fill it in, what am I doing wrong?
board = []
def print_board(board):
for i in range(8):
board.append([0]*8)
for i in range(len(board)):
for j in range(len(board[i])):
if i == 0 or i == 2 or i == 4 or i == 6 and j == 1 or j == 3 or j==5 or j==7:
board[i][j] = 1
elif i == 1 or i ==3 or i == 5 or i == 7 and j == 0 or j == 2 or j==4 or j==6:
board[i][j] = 1
for i in range(len(board)):
print(board[i])
print_board(board)
Why are the if and elif statements not working?
To make a checkerboard, a simple strategy is to check the parity of rows/cols.
If identical, set to 1 (or 0), if different, set the other way around.
This can be achieved with a simple list comprehension:
def board(n=8):
return [[int(i%2==j%2) for i in range(n)] # converting True to 1 directly
for j in range(n)]
output:
board(8)
[[1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1]]
board(5)
[[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[1, 0, 1, 0, 1]]
The reason that the code doesn't work is that the interpreter treats:
i == 0 or i == 2 or i == 4 or i == 6 and j == 1 or j == 3 or j==5 or j==7:
as
((i == 0 or i == 2 or i == 4 or i == 6) and j == 1) or j == 3 or j==5 or j==7:
i.e. it starts from the left and evaluates each operator one by one.
You can correct your code by replacing:
if i == 0 or i == 2 or i == 4 or i == 6 and j == 1 or j == 3 or j==5 or j==7:
board[i][j] = 1
elif i == 1 or i ==3 or i == 5 or i == 7 and j == 0 or j == 2 or j==4 or j==6:
board[i][j] = 1
With:
if (i == 0 or i == 2 or i == 4 or i == 6) and (j == 1 or j == 3 or j==5 or j==7):
board[i][j] = 1
elif (i == 1 or i ==3 or i == 5 or i == 7) and (j == 0 or j == 2 or j==4 or j==6):
board[i][j] = 1
Or replace it with this condition
if i % 2 == 0 and j % 2 != 0:
board[i][j] = 1
elif i % 2 != 0 and j % 2 == 0:
board[i][j] = 1
A better way to do this would be:
board = []
for i in range(0, 8):
board.append([])
for j in range(0, 8):
board[i].append((i + j) % 2)
print(board[-1])
Or
def board(k=8):
return [[int(i%2!=j%2) for i in range(k)] for j in range(k)]

how to create an array of (100,19) size with each row as a vector of 19 values [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0] in python?

I need to create an array of size (100,19) in python where each row is a fixed 19 valued vector of value [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]?
Any solution suggested?
a = np.zeros((100,19))
a[:,11] = 1
a = [0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
b = np.array(a)
c = np.tile(a,(100,1))
c.shape
Output:
(100, 19)
You can do it with np.zeros
array0 = np.zeros((100,19))
array0[:,11] = 1
On the other hand, if you want to have all one element
array1 = np.ones((100,19))
array1[:,11] = 0
np.full is a useful function for this purpose:
a = [0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
result=np.full((100,19),a )
result.shape
output:
(100,19)

how to count negative and positive numbers in each set from a list?

I have list which contains 30 numbers
list = [-21,-22,-33,-55,-454,65,48,-516,614,6,2,-64,-64,-87,6,45,87,15,11,03,-34,-6,-68,-959,-653,24,658,68,9,-2181]
Now first I want to count the number of continuous 3 positive or negative numbers. For that I am using this program:
list = [-21,-22,-33,-55,-454,65,48,-516,614,6,2,-64,-64,-87,6,45,87,15,11,03,-34,-6,-68,-959,-653,24,658,68,9,-2181]
counts = []
count = 0
daysCounter = 1
plus_counter = 0
minus_counter = 0
row_counter = 0
answer_counter = 1
for each in list: # for the "dev column"
if each > 0:
minus_counter = 0
plus_counter += 1
if plus_counter == 3:
count = answer_counter
row_counter = answer_counter
counts.append(count)
plus_counter = 0
answer_counter += 1
else:
counts.append(0)
elif each < 0:
plus_counter = 0
minus_counter += 1
if minus_counter == 3:
count = answer_counter
row_counter = answer_counter
counts.append(count)
minus_counter = 0
answer_counter += 1
else:
counts.append(0)
row_counter += 1
print counts
output:
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 0, 0, 7, 0, 0]
This is correct but I want to reset the counter at %10 == 0position. Basically, if the list contains 30 elements then I want to count between 0 to 10th element then 11th to 20th then 21th to 30th element.
desired output:
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0]
Basically, you will have to reset to default values for each period:
# other code
for i, each in enumerate(list): # for the "dev column"
if i % 10 == 0:
count = 0
daysCounter = 1
plus_counter = 0
minus_counter = 0
row_counter = 0
answer_counter = 1
# remaining code
Note: You should not name your list as list, because you override the built-in.
Your question didnt make sense to me, you say you want to reset on every module 10 index, yet you then go on to say in a list of 30 elements you want
0 to 10 (this is 11 elements)
11 to 20 (this is 10 elements)
21 to 30 (this is 10 elements)
The total of this would be 31 elements but you said your list has 30 elements. which would be indexed from 0 to 29. So i have made an assumption here that you do mean every 10 elelemts I.E 0 to 9, 10 to 19, 20 to 29. This makes my output out of line with yours but again i can only make an assumption here that you miscounted with your indexes.
nums = [
-21, -22, -33, -55, -454, 65, 48, -516, 614, 6,
2, -64, -64, -87, 6, 45, 87, 15, 11, 3,
-34, -6, -68, -959, -653, 24, 658, 68, 9, -2181
]
nths = 10
sequential_limit = 3
sequential_count = sequential_finds = 0
indexer = sequential_limit - 1
sequential_list = [0 for _ in range(indexer)]
skip = 0
for index, num in enumerate(nums[indexer:], indexer):
result = 0
if index % nths == 0:
sequential_count = sequential_finds = 0
skip = indexer
if skip:
skip -= 1
else:
negative = sum(1 for next_num in nums[index - indexer:index + 1] if next_num < 0)
positive = sum(1 for next_num in nums[index - indexer:index + 1] if next_num >= 0)
if sequential_limit in (positive, negative):
sequential_finds += 1
sequential_count = 0
skip = indexer
result = sequential_finds
sequential_list.append(result)
print(sequential_list)
OUTPUT
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0]
I think the above desired output you post is not correct
Add this code in the end, This code will reset between 0th to 9th, 10th to 19,20th to 29 elements of a list.
list_len = len(counts)
total_multiple = int(list_len/10)
for i in range(1, total_multiple):
count = 0
for j in range(10*i, 10*i+10):
if(counts[j] > 0):
counts[j] = count
count += 1
print(counts)
It will modify your list and prints
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]

Count string elements on a map based on where strings are and fill grid with the counts

I have a list called my_map that contains two different kinds of string values '.' and '&'. Now, for each value [x][y] that is a '.' I want to count the number of times an '&' was found in any of the eight directions next to the '.'
I created a grid to store the counts but I am just not able to formulate my conditions correctly. I can not use numpy arrays.
Note: 'S' and 'E' are treated like '.'
my_map = ['................' '....&...........' '..........E.....'
'&&..&...........' '....&&&.........' '......&&&&..&&..'
'................' '.......&........' '.....&.&........'
'....S...........' '.......&.&&.....']
def create_grid(my_map):
grid = [[0]*(len(my_map[0])) for x in range(len(my_map))]
return grid
grid = create_grid(my_map)
for x, y in [(x,y) for x in range(len(my_map)) for y in range(len(my_map[0]))]:
#any '&' north ?
if my_map[x][y+1]== '&' and my_map[x][y]=='.':
grid[x][y]+= 1
#any '&' west ?
if my_map[x-1][y]== '&' and my_map[x][y]=='.':
grid[x][y]+=1
#any '&' south ?
if my_map[x][y-1]== '&'and my_map[x][y]=='.':
grid[x][y]+=1
#any '&' east ?
if my_map[x+1][y]== '&'and my_map[x][y]=='.':
grid[x][y]+=1
#any '&' north-east ?
if my_map[x+1][y+1] == '&'and my_map[x][y]=='.':
grid[x][y]+=1
#any '&' south-west ?
if my_map[x-1][y-1] == '&'and my_map[x][y]=='.':
grid[x][y]+=1
#any '&' south-east ?
if my_map[x+1][y-1]== '&'and my_map[x][y]=='.':
grid[x][y]+=1
#any '&' north-west?
if my_map[x-1][y+1]== '&'and my_map[x][y]=='.':
grid[x][y]+=1
#desired output for first 3 rows
grid = [[0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0],[2,2,1,1,2,1,0,0,0,0,0,0,0,0,0,0]]
At the moment, I get an 'IndexError: string index out of range'. I dont know how to limit the range so it will still be correct.The only thing I managed so far was a grid displaying 1s for all '.' and 0s for all '&'.
I don't think the nested conditionals are appropriate here; each outer conditional must be true for the inner ones to be evaluated. They should be independent of each other and sequential.
It's also a lot of work and error-prone to enumerate every conditional by hand. For each cell, there are up to 8 directions in which a neighbor might live, and we do the exact same check on each direction. A loop is the appropriate construct for doing this; each loop iteration checks one neighboring cell, determining whether it's in bounds and of the appropriate character.
Furthermore, since your grid has few &, it makes sense to only perform neighbor checks for & characters. For each one, increment counts for neighboring .s. Do the opposite if the grid is predominantly & characters.
my_map = [
'................',
'....&...........',
'..........E.....',
'&&..&...........',
'....&&&.........',
'......&&&&..&&..',
'................',
'.......&........',
'.....&.&........',
'....S...........',
'.......&.&&.....'
]
grid = [[0] * len(x) for x in my_map]
directions = [
[-1, 0], [1, 0], [0, 1], [0, -1],
[-1, -1], [1, 1], [1, -1], [-1, 1]
]
for row in range(len(my_map)):
for col in range(len(my_map[row])):
if my_map[row][col] == "&":
for x, y in directions:
y += row
x += col
if y < len(my_map) and y >= 0 and \
x < len(my_map[y]) and x >= 0 and \
my_map[y][x] != "&":
grid[y][x] += 1
for row in grid:
print(row)
Output:
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 2, 0, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[2, 2, 1, 2, 0, 0, 0, 4, 3, 2, 1, 1, 2, 2, 1, 0]
[0, 0, 0, 1, 2, 4, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 1, 3, 4, 4, 2, 1, 1, 2, 2, 1, 0]
[0, 0, 0, 0, 1, 1, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 3, 2, 3, 2, 2, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 0]
And a version that overlays counts with the original map Minesweeper-style:
0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0
0 0 0 1 & 1 0 0 0 0 0 0 0 0 0 0
2 2 1 2 2 2 0 0 0 0 0 0 0 0 0 0
& & 1 2 & 4 2 1 0 0 0 0 0 0 0 0
2 2 1 2 & & & 4 3 2 1 1 2 2 1 0
0 0 0 1 2 4 & & & & 1 1 & & 1 0
0 0 0 0 0 1 3 4 4 2 1 1 2 2 1 0
0 0 0 0 1 1 3 & 2 0 0 0 0 0 0 0
0 0 0 0 1 & 3 & 2 0 0 0 0 0 0 0
0 0 0 0 1 1 3 2 3 2 2 1 0 0 0 0
0 0 0 0 0 0 1 & 2 & & 1 0 0 0 0
Try it!

Pseudo-random generation of python list containing binary values with run-length and frequency constraints

I want to pseudo-randomly create a list with 48 entries -- 24 zeros and 24 ones -- where the same value never occurs three times in a row. I have the following code:
import random
l = list()
for i in range(48):
if len(l) < 2:
l.append(random.choice([0,1]))
else:
if l[i-1] == l[i-2]:
if l[i-1] == 0:
l.append(1)
else:
l.append(0)
else:
l.append(random.choice([0,1]))
But sometimes the count of 0s and 1s is uneven.
Getting uniformity without using rejection is tricky.
The rejection approach is straightforward, something like
def brute(n):
seq = [0]*n+[1]*n
while True:
random.shuffle(seq)
if not any(len(set(seq[i:i+3])) == 1 for i in range(len(seq)-2)):
break
return seq
which will be very slow at large n but is reliable.
There's probably a slick way to take a non-rejection sample where it's almost trivial, but I couldn't see it and instead I fell back on methods which work generally. You can make sure that you're uniformly sampling the space if at each branch point, you weight the options by the number of successful sequences you generate if you take that choice.
So, we use dynamic programming to make a utility which counts the number of possible sequences, and extend to the general case where we have (#zeroes, #ones) bits left, and then use this to provide the weights for our draws. (We could actually refactor this into one function but I think they're clearer if they're separate, even if it introduces some duplication.)
from functools import lru_cache
import random
def take_one(bits_left, last_bits, choice):
# Convenience function to subtract a bit from the bits_left
# bit count and shift the last bits seen.
bits_left = list(bits_left)
bits_left[choice] -= 1
return tuple(bits_left), (last_bits + (choice,))[-2:]
#lru_cache(None)
def count_seq(bits_left, last_bits=()):
if bits_left == (0, 0):
return 1 # hooray, we made a valid sequence!
if min(bits_left) < 0:
return 0 # silly input
if 0 in bits_left and max(bits_left) > 2:
return 0 # short-circuit if we know it won't work
tot = 0
for choice in [0, 1]:
if list(last_bits).count(choice) == 2:
continue # can't have 3 consec.
new_bits_left, new_last_bits = take_one(bits_left, last_bits, choice)
tot += count_seq(new_bits_left, new_last_bits)
return tot
def draw_bits(n):
bits_left = [n, n]
bits_drawn = []
for bit in range(2*n):
weights = []
for choice in [0, 1]:
if bits_drawn[-2:].count(choice) == 2:
weights.append(0) # forbid this case
continue
new_bits_left, new_last_bits = take_one(bits_left, tuple(bits_drawn[-2:]), choice)
weights.append(count_seq(new_bits_left, new_last_bits))
bit_drawn = random.choices([0, 1], weights=weights)[0]
bits_left[bit_drawn] -= 1
bits_drawn.append(bit_drawn)
return bits_drawn
First, we can see how many such valid sequences there are:
In [1130]: [count_seq((i,i)) for i in range(12)]
Out[1130]: [1, 2, 6, 14, 34, 84, 208, 518, 1296, 3254, 8196, 20700]
which is A177790 at the OEIS, named
Number of paths from (0,0) to (n,n) avoiding 3 or more consecutive east steps and 3 or more consecutive north steps.
which if you think about it is exactly what we have, treating a 0 as an east step and a 1 as a north step.
Our random draws look good:
In [1145]: draw_bits(4)
Out[1145]: [0, 1, 1, 0, 1, 0, 0, 1]
In [1146]: draw_bits(10)
Out[1146]: [0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0]
and are quite uniform:
In [1151]: Counter(tuple(draw_bits(4)) for i in range(10**6))
Out[1151]:
Counter({(0, 0, 1, 0, 1, 0, 1, 1): 29219,
(1, 0, 1, 0, 0, 1, 0, 1): 29287,
(1, 1, 0, 0, 1, 0, 1, 0): 29311,
(1, 0, 1, 0, 1, 0, 1, 0): 29371,
(1, 0, 1, 0, 1, 1, 0, 0): 29279,
(0, 1, 0, 1, 0, 0, 1, 1): 29232,
(0, 1, 0, 1, 1, 0, 1, 0): 29824,
(0, 1, 1, 0, 0, 1, 1, 0): 29165,
(0, 1, 1, 0, 1, 0, 0, 1): 29467,
(1, 1, 0, 0, 1, 1, 0, 0): 29454,
(1, 0, 1, 1, 0, 0, 1, 0): 29338,
(0, 0, 1, 1, 0, 0, 1, 1): 29486,
(0, 1, 1, 0, 1, 1, 0, 0): 29592,
(0, 0, 1, 1, 0, 1, 0, 1): 29716,
(1, 1, 0, 1, 0, 0, 1, 0): 29500,
(1, 0, 0, 1, 0, 1, 0, 1): 29396,
(1, 0, 1, 0, 0, 1, 1, 0): 29390,
(0, 1, 1, 0, 0, 1, 0, 1): 29394,
(0, 1, 1, 0, 1, 0, 1, 0): 29213,
(0, 1, 0, 0, 1, 0, 1, 1): 29139,
(0, 1, 0, 1, 0, 1, 1, 0): 29413,
(1, 0, 0, 1, 0, 1, 1, 0): 29502,
(0, 1, 0, 1, 0, 1, 0, 1): 29750,
(0, 1, 0, 0, 1, 1, 0, 1): 29097,
(0, 0, 1, 1, 0, 1, 1, 0): 29377,
(1, 1, 0, 0, 1, 0, 0, 1): 29480,
(1, 1, 0, 1, 0, 1, 0, 0): 29533,
(1, 0, 0, 1, 0, 0, 1, 1): 29500,
(0, 1, 0, 1, 1, 0, 0, 1): 29528,
(1, 0, 1, 0, 1, 0, 0, 1): 29511,
(1, 0, 0, 1, 1, 0, 0, 1): 29599,
(1, 0, 1, 1, 0, 1, 0, 0): 29167,
(1, 0, 0, 1, 1, 0, 1, 0): 29594,
(0, 0, 1, 0, 1, 1, 0, 1): 29176})
Coverage is also correct, in that we can recover the A177790 counts by randomly sampling (and with some luck):
In [1164]: [len(set(tuple(draw_bits(i)) for _ in range(20000))) for i in range(9)]
Out[1164]: [1, 2, 6, 14, 34, 84, 208, 518, 1296]
Here's a reasonably efficient solution that gives you fairly random output that obeys the constraints, although it doesn't cover the full solution space.
We can ensure that the number of zeroes and ones are equal by ensuring that the number of single zeros equals the number of single ones, and the number of pairs of zeros equals the number of pairs of ones. In a perfectly random output list we'd expect the number of singles to be roughly double the number of pairs. This algorithm makes that exact: each list has 12 singles of each type, and 6 pairs.
Those run lengths are stored in a list named runlengths. On each round, we shuffle that list to get the sequence of run lengths for the zeros, and shuffle it again to get the sequence of run lengths for the ones. We then fill the output list by alternating between runs of zeroes and ones.
To check that the lists are correct we use the sum function. If there are equal numbers of zeroes and ones the sum of a list is 24.
from random import seed, shuffle
seed(42)
runlengths = [1] * 12 + [2] * 6
bits = [[0], [1]]
for i in range(10):
shuffle(runlengths)
a = runlengths[:]
shuffle(runlengths)
b = runlengths[:]
shuffle(bits)
out = []
for u, v in zip(a, b):
out.extend(bits[0] * u)
out.extend(bits[1] * v)
print(i, ':', *out, ':', sum(out))
output
0 : 0 0 1 0 0 1 1 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 : 24
1 : 0 1 0 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 1 0 1 : 24
2 : 0 0 1 1 0 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 : 24
3 : 0 0 1 0 1 1 0 0 1 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 0 1 1 0 1 0 1 0 1 0 1 : 24
4 : 1 1 0 0 1 0 1 0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0 1 0 0 1 0 1 0 0 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 0 : 24
5 : 1 1 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 1 0 0 : 24
6 : 1 0 1 0 0 1 0 0 1 0 0 1 1 0 0 1 1 0 1 0 0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 : 24
7 : 0 1 0 0 1 1 0 1 0 0 1 0 1 0 1 0 1 0 1 0 1 1 0 1 1 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 1 0 0 1 0 0 1 : 24
8 : 0 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 1 0 1 1 0 1 1 0 0 1 0 1 : 24
9 : 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 : 24
Here is a simple code that obeys your constraints:
import random
def run():
counts = [24, 24]
last = [random.choice([0, 1]), random.choice([0, 1])]
counts[last[0]] -= 1
counts[last[1]] -= 1
while sum(counts) > 0:
can_pick_ones = sum(last[-2:]) < 2 and counts[1] > 0.33 * (48 - len(last))
can_pick_zeros = sum(last[-2:]) > 0 and counts[0] > 0.33 * (48 - len(last))
if can_pick_ones and can_pick_zeros:
value = random.choice([0, 1])
elif can_pick_ones:
value = 1
elif can_pick_zeros:
value = 0
counts[value] -= 1
last.append(value)
return last
for i in range(4):
r = run()
print(sum(r), r)
Output
24 [1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
24 [0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1]
24 [0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1]
24 [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0]
Rationale
At each step of the while loop you can either choose 1, choose 0 or both. You can choose:
1 if the last two elements are not one and the counts of 1 is larger than 1/3 the amount of remaining slots: sum(last[-2:]) < 2 and counts[1] > 0.33 * (48 - len(last))
0 if the last two elements are not 0 and the counts of 0 is larger than 1/3 the amount of remaining slots: sum(last[-2:]) > 0 and counts[0] > 0.33 * (48 - len(last))
Both if you can choose 1 or 0
The sum of the two last elements can be 0, 1 or 2, if equals 0 it means that the last two elements were 0 so you can only pick 0 if sum(last[-2:]) > 0. If equals 2 it means that the last two elements where 1, so you can only pick 1 if sum(last[-2:]) < 2. Finally you need to check that the amount of elements of both 1 and 0 are at least a third of the remaining positions to assign, otherwise you are going to be forced to create a run of three consecutive equal elements.

Categories