Related
I have the following pandas DataFrame:
df = pd.DataFrame([
[3, 2, 5, 2],
[8, 5, 4, 2],
[9, 0, 8, 6],
[9, 2, 7, 1],
[1, 9, 2, 3],
[8, 1, 1, 6],
[8, 8, 0, 0],
[0, 1, 3, 0],
[2, 4, 5, 3],
[4, 0, 9, 7]
])
I am trying to write a recursive function that extracts all the possible paths up until 3 iterations:
and saves them into a list. Several attempts but no results to post.
Desired Output:
[
[0, 3, 9, 4],
[0, 3, 9, 0],
[0, 3, 9, 9],
[0, 3, 9, 7],
[0, 3, 2, 9],
[0, 3, 2, 0],
...
]
Represented as a tree, this is how it looks like:
Since you use numeric naming for both rows and columns in your dataframe, it's faster to convert the frame to a 2-D numpy array. Try this;
arr = df.to_numpy()
staging = [[0]]
result = []
while len(staging) > 0:
s = staging.pop(0)
if len(s) == 4:
result.append(s)
else:
i = s[-1]
for j in range(4):
staging.append(s + [arr[i, j]])
I wanna get values from multiple(?) list, which is not 0(I think filter can be one of solutions).
list as below:
>>ls = [[i for i in np.random.randint(0, 5, 5)] for _ in range(7)]
>>ls
>>
[[2, 3, 3, 0, 0],
[4, 2, 4, 3, 2],
[1, 2, 4, 2, 4],
[2, 3, 4, 3, 1],
[0, 1, 0, 3, 0],
[3, 4, 4, 4, 3],
[3, 4, 3, 3, 2]]
Expected result is:
[2,3,3,4,2,4,3,2,1,2,4,2,4,2,3,4,3,1,1,3,3,4,4,4,3,3,4,3,3,2]
I tried using filter function, wanted expand this idea, but I failed:
>> [elem for elem in filter(lambda x: x if x != 0 else False, ls[0])]
>>
[2, 3, 3]
I wanna find fastest way to get expected result, not using for loop.
Would you suggest any good idea?
Edit:
Oops, Sorry for confusing you.
I saying 'not usting for loop' means, I wanna use list comprehension instead of for loop, because I heard list comprehension fater than for loop.
Use a nested list comprehension:
[j for i in ls for j in i if j != 0]
ls = [[i for i in np.random.randint(0, 5, 5)] for _ in range(7)]
[[1, 0, 3, 0, 0],
[1, 2, 2, 3, 0],
[1, 1, 1, 4, 3],
[1, 0, 3, 0, 4],
[2, 0, 3, 0, 2],
[1, 0, 4, 4, 0],
[2, 4, 1, 1, 2]]
[j for i in ls for j in i if j != 0]
# [1, 3, 1, 2, 2, 3, 1, 1, 1, 4, 3, 1, 3, 4, 2, 3, 2, 1, 4, 4, 2, 4, 1, 1, 2]
If you want to avoid any explicit looping here's an option using itertools.chain and filter:
from itertools import chain
list(filter(lambda x: x != 0, chain(*ls)))
# [1, 3, 1, 2, 2, 3, 1, 1, 1, 4, 3, 1, 3, 4, 2, 3, 2, 1, 4, 4, 2, 4, 1, 1, 2]
Looks like you're also using NumPy for creating the list. Note that this would be way simpler and more efficient using np.nonzero:
import numpy as np
a = np.random.randint(0, 5, (7,5))
a[np.nonzero(a)]
# [1, 3, 1, 2, 2, 3, 1, 1, 1, 4, 3, 1, 3, 4, 2, 3, 2, 1, 4, 4, 2, 4, 1, 1, 2]
Adding to #yatu's answer, since it's 0, you can just do if x:
print([x for i in ls for x in i if x])
Or without loop:
print(np.array(ls).flatten()[np.array(ls).flatten() != 0].tolist())
Both output:
[1, 4, 4, 2, 1, 3, 3, 4, 4, 1, 3, 3, 2, 2, 2, 1, 4, 2, 2, 1, 4, 1, 2, 3, 2, 4]
I had an idea a while ago of making a program that would solve a Sudoku board, so I made the code below. The code receives as input a 9x9 integer list, where an incomplete cell is represented by the number 0.
def checkSolutions(grid, i, j):
"""
Given a Sudoku board and the position of an
incomplete cell, it returns a list with all
the possible numbers that this position can occupy.
"""
digits = [1, 2, 3, 4, 5, 6, 7, 8, 9]
solutions = []
solutions1x9 = [grid[x][j] for x in range(9)]
solutions9x1 = [grid[i][x] for x in range(9)]
rowGrid = i // 3
columnGrid = j // 3
solutions3x3 = [grid[i][j] for i in range(3*rowGrid, 3*rowGrid+3)
for j in range(3*columnGrid, 3*columnGrid+3)]
solutions = solutions + [i for i in digits if i not in solutions1x9]
solutions = solutions + [i for i in digits if i not in solutions9x1]
solutions = solutions + [i for i in digits if i not in solutions3x3]
solutions = list(set(solutions))
solutions = [i for i in solutions if i not in solutions1x9]
solutions = [i for i in solutions if i not in solutions9x1]
solutions = [i for i in solutions if i not in solutions3x3]
return solutions
def checkSudoku(grid):
"""
Given a Sudoku board, it returns True if it is
a board that follows the rules of the game and
returns False otherwise.
"""
digits = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in range(9):
if sorted(grid[i]) != digits:
return False
for i in range(9):
column = [grid[j][i] for j in range(9)]
if sorted(column) != digits:
return False
for i in range(3):
for j in range(3):
grid3x3 = [grid[x][y] for x in range(3*i, 3*i+3)
for y in range(3*j, 3*j+3)]
if sorted(grid3x3) != digits:
return False
return True
def sudoku(grid):
"""
Given an incomplete Sudoku board, it prints on
the screen the solution of that game.
"""
for i in range(9):
for j in range(9):
if grid[i][j] == 0:
solutions = checkSolutions(grid, i, j)
if len(solutions) == 1:
grid[i][j] = solutions[0]
continue
for k in solutions:
auxGrid = [x.copy() for x in grid]
auxGrid[i][j] = k
sudoku(auxGrid)
if checkSudoku(grid):
print(grid)
My problem is: if the function sudoku receives as input the following list
grid1 = [[0, 3, 7, 6, 0, 1, 5, 8, 4],
[8, 0, 0, 3, 0, 4, 9, 2, 0],
[6, 0, 9, 2, 5, 0, 3, 7, 1],
[9, 8, 0, 5, 6, 7, 1, 0, 0],
[0, 6, 0, 4, 1, 2, 0, 9, 0],
[0, 0, 1, 8, 3, 9, 0, 6, 5],
[7, 9, 6, 0, 4, 3, 8, 0, 2],
[0, 5, 8, 7, 0, 6, 0, 0, 9],
[1, 2, 4, 9, 0, 5, 6, 3, 0]]
it returns the result in less than one second, which is
[[2, 3, 7, 6, 9, 1, 5, 8, 4],
[8, 1, 5, 3, 7, 4, 9, 2, 6],
[6, 4, 9, 2, 5, 8, 3, 7, 1],
[9, 8, 2, 5, 6, 7, 1, 4, 3],
[5, 6, 3, 4, 1, 2, 7, 9, 8],
[4, 7, 1, 8, 3, 9, 2, 6, 5],
[7, 9, 6, 1, 4, 3, 8, 5, 2],
[3, 5, 8, 7, 2, 6, 4, 1, 9],
[1, 2, 4, 9, 8, 5, 6, 3, 7]]
But if it receives as input the list:
grid2 = [[1, 6, 8, 0, 0, 0, 9, 0, 2],
[0, 0, 0, 3, 0, 1, 0, 0, 0],
[0, 3, 0, 6, 2, 0, 0, 0, 0],
[0, 0, 9, 0, 0, 0, 1, 0, 6],
[0, 0, 1, 0, 0, 0, 3, 7, 0],
[0, 4, 3, 5, 0, 0, 0, 0, 9],
[0, 0, 0, 8, 0, 2, 6, 0, 0],
[0, 0, 0, 9, 0, 5, 0, 2, 3],
[2, 0, 6, 0, 3, 0, 7, 0, 0]]
it should returns
[[1, 6, 8, 4, 5, 7, 9, 3, 2],
[5, 7, 2, 3, 9, 1, 4, 6, 8],
[9, 3, 4, 6, 2, 8, 5, 1, 7],
[8, 2, 9, 7, 4, 3, 1, 5, 6],
[6, 5, 1, 2, 8, 9, 3, 7, 4],
[7, 4, 3, 5, 1, 6, 2, 8, 9],
[3, 9, 5, 8, 7, 2, 6, 4, 1],
[4, 1, 7, 9, 6, 5, 8, 2, 3],
[2, 8, 6, 1, 3, 4, 7, 9, 5]]
but the program takes so long to run that I don't even know if it returns something (I waited 30 minutes before closing the code execution). So my doubts are:
is there a mistake in my code for certain input types?
how can I improve my code to accept entries with more empty cells?
my code works perfectly fine and is it normal to take longer for entries with more empty cells?
Thanks for any help!
You can get your program to solve the second puzzle by adding a return statement to your sudoku() function at the end of the nested loops. The code below has that fix and some other rework ideas:
DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9]
def checkSolutions(grid, i, j):
"""
Given a Sudoku board, and the position of an
incomplete cell, it returns a list with all
the possible numbers that can occupy this position.
"""
solutions1x9 = [grid[x][j] for x in range(9)]
solutions9x1 = [grid[i][x] for x in range(9)]
rowGrid = 3 * (i // 3)
columnGrid = 3 * (j // 3)
solutions3x3 = [grid[i][j] for i in range(rowGrid, rowGrid + 3) for j in range(columnGrid, columnGrid + 3)]
return [digit for digit in DIGITS if digit not in solutions1x9 and digit not in solutions9x1 and digit not in solutions3x3]
def checkSudoku(grid):
"""
Given a Sudoku board, it returns True if it is
a board that follows the rules of the game and
returns False otherwise.
"""
for i in range(9):
if sorted(grid[i]) != DIGITS:
return False
for j in range(9):
column = [grid[i][j] for i in range(9)]
if sorted(column) != DIGITS:
return False
for i in range(3):
for j in range(3):
grid3x3 = [grid[x][y] for x in range(3 * i, 3 * i + 3) for y in range(3 * j, 3 * j + 3)]
if sorted(grid3x3) != DIGITS:
return False
return True
def sudoku(grid):
"""
Given an incomplete Sudoku board, it prints on
the screen the solution of that game.
"""
for i in range(9):
for j in range(9):
if grid[i][j] == 0:
solutions = checkSolutions(grid, i, j)
if len(solutions) == 1:
grid[i][j] = solutions[0] # permanent change to *this* reality
continue
for k in solutions:
auxGrid = [x.copy() for x in grid] # spawn a new reality
auxGrid[i][j] = k
sudoku(auxGrid)
return # already solved it recursively or no solution in *this* reality
if checkSudoku(grid):
print(grid)
grid2 = [[1, 6, 8, 0, 0, 0, 9, 0, 2],
[0, 0, 0, 3, 0, 1, 0, 0, 0],
[0, 3, 0, 6, 2, 0, 0, 0, 0],
[0, 0, 9, 0, 0, 0, 1, 0, 6],
[0, 0, 1, 0, 0, 0, 3, 7, 0],
[0, 4, 3, 5, 0, 0, 0, 0, 9],
[0, 0, 0, 8, 0, 2, 6, 0, 0],
[0, 0, 0, 9, 0, 5, 0, 2, 3],
[2, 0, 6, 0, 3, 0, 7, 0, 0]]
sudoku(grid2)
OUTPUT
> python3 test.py
[[1, 6, 8, 4, 5, 7, 9, 3, 2],
[5, 7, 2, 3, 9, 1, 4, 6, 8],
[9, 3, 4, 6, 2, 8, 5, 1, 7],
[8, 2, 9, 7, 4, 3, 1, 5, 6],
[6, 5, 1, 2, 8, 9, 3, 7, 4],
[7, 4, 3, 5, 1, 6, 2, 8, 9],
[3, 9, 5, 8, 7, 2, 6, 4, 1],
[4, 1, 7, 9, 6, 5, 8, 2, 3],
[2, 8, 6, 1, 3, 4, 7, 9, 5]]
>
Your solver is a brute-force solver which uses few smarts about the game itself. So, I can't promise there won't be a puzzle that again takes too long to finish. A more efficient solver might try all the tricks a human would to place digits before resorting to brute-force.
The modification I made may prevent your code from finding multiple solutions if they exist.
As I commented, this is a hard sudoku, so you have to guess several cells to be able to solve it. You can check my hard sudoku solver I programmed some time ago, if that helps:
def sudoku(grid):
sudoku_dict = {}
r = 'ABCDEFGHI'
c = '123456789'
for i in range(9):
for j in range(9):
sudoku_dict[r[i]+c[j]] = str(grid[i][j]) if grid[i][j] != 0 else c
square = [[x+y for x in i for y in j] for i in ('ABC','DEF','GHI') for j in ('123','456','789')]
peers = {}
for key in sudoku_dict.keys():
value = [i for i in square if key in i][0]
row = [[x+y for x in i for y in j][0] for i in key[0] for j in c]
col = [[x+y for x in i for y in j][0] for i in r for j in key[1]]
peers[key] = set(x for x in value+row+col if x != key)
for i in range(9):
sudoku_dict = Check(sudoku_dict,peers)
sudoku_dict = search(sudoku_dict, peers)
solution = []
for i in r:
solution.append([])
for j in c:
solution[r.find(i)].append(int(sudoku_dict[i+j]))
return solution
def Check(sudoku_dict, peers):
for k,v in sudoku_dict.items():
if len(v) == 1:
for s in peers[k]:
sudoku_dict[s] = sudoku_dict[s].replace(v,'')
if len(sudoku_dict[s])==0:
return False
return sudoku_dict
def search(sudoku_dict,peers):
if Check(sudoku_dict,peers)==False:
return False
if all(len(sudoku_dict[s]) == 1 for s in sudoku_dict.keys()):
return sudoku_dict
n,s = min((len(sudoku_dict[s]), s) for s in sudoku_dict.keys() if len(sudoku_dict[s]) > 1)
res = []
for value in sudoku_dict[s]:
new_sudoku_dict = sudoku_dict.copy()
new_sudoku_dict[s] = value
ans = search(new_sudoku_dict, peers)
if ans:
res.append(ans)
if len(res) > 1:
raise Exception("Error")
elif len(res) == 1:
return res[0]
>>> l1
[1, 2, 0, 3, 4, 0, 5, 6, 0, 8, 9, 0]
output expected is as below
[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9]
i have tried myself as below but looking for solution without using l2 list as shown in my example
for i in l1:
if i==0:
l1.remove(0)
l2.append(i)
>>> l1[1, 2, 3, 4, 5, 6, 8,9]
>>>l2[0, 0, 0, 0]
Final output
>>> l2+l1
[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9]
l1 = [1,2,0,3,4,0,5,6,0,8,9,0]
def nonzero(a):
return sorted(a, key=lambda x: x != 0)
print (nonzero(l1))
output:
[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9]
solution with for loop:
l1 = [1,2,0,3,4,0,5,6,0,8,9,0]
for i in range(len(l1)):
if(l1[i] == 0):
l1 = [l1[i]] + l1[:i] + l1[i+1:]
print (l1)
output:
[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9]
I don't have a real reason for doing this, other than to gain understanding, but I'm trying to create a list of lists of lists using list comprehension.
I can create a list of lists just fine:
In[1]: [j for j in [range(3,k) for k in [k for k in range(5,10)]]]
Out[1]: [[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
And I can create a list of lists of lists from either the results of that, for example:
In [2]: [range(0,i) for i in [3,4]]
Out[2]: [[0, 1, 2], [0, 1, 2, 3]]
In [3]: [range(0,i) for i in j]
Out[3]:
[[0, 1, 2],
[0, 1, 2, 3],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5, 6],
[0, 1, 2, 3, 4, 5, 6, 7]]
But when I try to combine it into a single statement it goes awry:
In [4]: [range(0,i) for i in [j for j in [range(3,k) for k in [k for k in range(5,10)]]]]
---------------------------------------------------------------------------
TypeError: range() integer end argument expected, got list.
Am I missing some brackets somewhere?
Try the following:
[[range(0, j) for j in range(3, i)] for i in range(5, 10)]
This results in the following list of lists of lists:
>>> pprint.pprint([[range(0, j) for j in range(3, i)] for i in range(5, 10)])
[[[0, 1, 2], [0, 1, 2, 3]],
[[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]],
[[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5]],
[[0, 1, 2],
[0, 1, 2, 3],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5, 6]],
[[0, 1, 2],
[0, 1, 2, 3],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5, 6],
[0, 1, 2, 3, 4, 5, 6, 7]]]
The best way to understand what is happening in a list comprehension is to try to roll it out into normal for loops, lets try that with yours and then mine to see what the difference is:
x = [range(0,i) for i in [j for j in [range(3,k) for k in [k for k in range(5,10)]]]]
# equivalent to
a, b, c, x = [], [], [], []
for k in range(5, 10):
a.append(k)
for k in a:
b.append(range(3, k))
for j in b:
c.append(j)
for i in c:
x.append(range(0, i))
At the end of this x would be equivalent to your list comprehension, however of course this code will not work because b (and c) will be lists of lists, so i will be a list and range(0, i) will cause an error. Now obviously this is not what you intended to do, since what you would really like to see is those for loops nested instead of one after the other.
Lets look at how mine works:
x = [[range(0, j) for j in range(3, i)] for i in range(5, 10)]
# equivalent to
x = []
for i in range(5, 10):
a = []
for j in range(3, i):
a.append(range(0, j)):
x.append(a)
Hope this helped to clarify!
From your question:
In[1]: [j for j in [range(3,k) for k in [k for k in range(5,10)]]]
Out[1]: [[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
range takes integer parameters
So when you do
[range(0,i) for i in [j for j in [range(3,k) for k in [k for k in range(5,10)]]]]
it is the equivalent of saying
L = []
for j in [[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]:
L.append(range(0,i))
Of course, this will fail because each i is a list and range doesn't take list parameters.
Other answers here show you how to fix your error. This response was to explain what went wrong with your initial approach
Hope this helps
To troubleshoot this, I ran each step of the list comprehension at a time.
>>> [k for k in range(5,10)]
[5, 6, 7, 8, 9]
>>> [range(3,k) for k in [k for k in range(5,10)]]
[[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
Your problem is here, because it feeds lists to the next range() instead of ints:
>>> [j for j in [range(3,k) for k in [k for k in range(5,10)]]]
[[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
You can flatten this list using lambda and reduce:
# reduce(lambda x,y: x+y,l)
>>> reduce(lambda x,y: x+y, [range(3,k) for k in [k for k in range(5,10)]])
[3, 4, 3, 4, 5, 3, 4, 5, 6, 3, 4, 5, 6, 7, 3, 4, 5, 6, 7, 8]
But you will still only nest two lists deep:
>>> [range(0,i) for i in reduce(lambda x,y: x+y, [range(3,k) for k in [k for k in range(5,10)]])]
[[0, 1, 2], [0, 1, 2, 3], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6, 7]]
If you want to go three lists deep, you need to reconsider your program flow. List comprehensions are best suited for working with the outermost objects in an iterator. If you used list comprehensions on the left side of the for statement as well as the right, you could nest more deeply:
>>> [[[range(j, 5) for j in range(5)] for i in range(5)] for k in range(5)]
That's because:
[range(0,i) for i in [j for j in [range(3,k) for k in [k for k in range(5,10)]]]]
^here i == j == range(3,k) - it's a range, not integer
You probably wanted to do:
[range(0,j) for i in [j for j in [range(3,k) for k in [k for k in range(5,10)]]] for j in i]
^ i is still a list, iterate it with j here
You do of course know that your first statement can be reduced to :
[range(3,k) for k in range(5,10)]
??
[range(0,i) for i in [j for j in [range(3,k) for k in [k for k in range(5,10)]]]]
Because i is a list, you need list comprehend on that too:
[[range(0,h) for h in i] for i in [...]]
In your code:
print [
[range(0,h) for h in i] for i in [ # get a list for i here. Need to iterate again on i!
j for j in [ # [[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
range(3,k) for k in [
k for k in range(5,10) # [5,6,7,8,9]
]
]
]
]
# output:
[[[0, 1, 2], [0, 1, 2, 3]], [[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]], [[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5]], [[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6, 7]]]
Also, you have one unnecessary comprehension in there. This will produce the same result:
print [
[range(0,i) for i in j] for j in [ # get a list for j here. Need to iterate again on j!
range(3,k) for k in [ # [[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
k for k in range(5,10) # [5,6,7,8,9]
]
]
]
As an aside, this is about the point where a list comprehension becomes less desirable than a recursive function or simple nested for loops, if only for the sake of code readability.
Apparently, in the first range (range(0,i)), i is [[3, 4], [3, 4, 5], [3, 4, 5, 6], [3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
You probably need to flatten that list before you call the range function with it.