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]
Related
I would like to ask you how to copy the rows to the next rows in matrix by using python. I tried however the result is not as expected. Here is the coding and the expected result. Thank you in advance.
import numpy as np
Total_T = 6
Total_P = 8
M0 = np.array([[0,0,0,0,0,1,1,1]])
M = np.random.randint(10,size = (Total_T, Total_P))
for k in range(0,Total_T):
for l in range (0,Total_P):
if k == Total_T-1:
M[Total_T-1][l] = M0[0][l]
#if k == 0:
#M[0][l] = M0[0][l]
else:
M
M_prev = np.empty((Total_T, Total_P), dtype = object)
if M is not None:
for k in range(0,Total_T):
for l in range (0,Total_P):
if k == 0:
M_prev[0][l] = M0[0][l]
else:
M_prev = M[:].copy()
The result of M:
array([[4, 8, 1, 6, 2, 4, 9, 6],
[3, 9, 1, 6, 9, 2, 7, 1],
[9, 4, 5, 9, 3, 5, 1, 2],
[8, 3, 5, 2, 8, 5, 4, 8],
[6, 3, 4, 3, 7, 9, 8, 4],
[0, 0, 0, 0, 0, 1, 1, 1]])
The expected result of M_prev:
array([[0, 0, 0, 0, 0, 1, 1, 1],
[4, 8, 1, 6, 2, 4, 9, 6],
[3, 9, 1, 6, 9, 2, 7, 1],
[9, 4, 5, 9, 3, 5, 1, 2],
[8, 3, 5, 2, 8, 5, 4, 8],
[6, 3, 4, 3, 7, 9, 8, 4]])
However the result of M_prev from the coding:
array([[4, 8, 1, 6, 2, 4, 9, 6],
[3, 9, 1, 6, 9, 2, 7, 1],
[9, 4, 5, 9, 3, 5, 1, 2],
[8, 3, 5, 2, 8, 5, 4, 8],
[6, 3, 4, 3, 7, 9, 8, 4],
[0, 0, 0, 0, 0, 1, 1, 1]])
It looks like you're overcomplicating things a lot…
import numpy as np
Total_T = 6
Total_P = 8
M0 = np.array([[0,0,0,0,0,1,1,1]])
# for reproducibility
np.random.seed(0)
# generate random 2D array
M = np.random.randint(10, size=(Total_T, Total_P))
# replace last row with M0
M[-1] = M0
# roll the array
M_prev = np.roll(M, 1, axis=0)
output:
# M
array([[5, 0, 3, 3, 7, 9, 3, 5],
[2, 4, 7, 6, 8, 8, 1, 6],
[7, 7, 8, 1, 5, 9, 8, 9],
[4, 3, 0, 3, 5, 0, 2, 3],
[8, 1, 3, 3, 3, 7, 0, 1],
[0, 0, 0, 0, 0, 1, 1, 1]])
# M_prev
array([[0, 0, 0, 0, 0, 1, 1, 1],
[5, 0, 3, 3, 7, 9, 3, 5],
[2, 4, 7, 6, 8, 8, 1, 6],
[7, 7, 8, 1, 5, 9, 8, 9],
[4, 3, 0, 3, 5, 0, 2, 3],
[8, 1, 3, 3, 3, 7, 0, 1]])
>>> 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 need help indexing a 3 dimensional array (an RGB/BGR image) using a 2 dimensional array of indices. All values are either 0, 1, or 2 for different color channels. The result should be a 2D array of color values. If anyone can tell me the syntax for this in python that would be great!
For a context of what I am trying to do (also read TLDR below):
I am essentially trying to convert the following code from normal for loop syntax which is very very slow to a more efficient python/numpy syntax:
colorIndices = np.zeros((height,width)); # an array which has the index of the outstanding color
colorIndices -= 1; # all -1's
for x in range(0,width):
for y in range(0,height):
pix = img[y,x]; # get the pixel, a 1D array of length 3
colorID = np.argmax(pix); #get which index has max value (candidate for outstanding color)
if(pix[colorID]>np.average(pix)+np.std(pix)): # if that value is more than one std dev away from the overall pixel's value, the pixel has an outstanding color
colorIndices[y,x] = colorID;
I then would like to access the outstanding color channels in each pixel using something like:
img[:,:,:]=0;
img[colorIndices] = 255;
TLDR: I want to set a pixel to pure blue, green, or red if it is a shade of that color. The way I define if a pixel is shade red is if the R value of the pixel is more than one std above the average of the overall distribution of {R, G, B}.
The broken code I have so far:
colorIDs = np.argmax(img, axis=2);
averages = np.average(img, axis=2);
stds = np.std(img, axis=2);
cutoffs = averages + stds;
print(img[colorIDs]);
I think you want to apply the 2d indexing mask from argmax to the 2nd axis:
In [38]: img=np.random.randint(0,10,(16,16,3))
In [39]: ids = np.argmax(img, axis=2)
In [40]: ids
Out[40]:
array([[0, 1, 2, 1, 2, 0, 0, 0, 2, 2, 1, 0, 1, 2, 1, 0],
[0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 0, 0, 1, 2, 0, 2, 2, 1, 2, 1, 1, 0],
[2, 0, 1, 2, 0, 0, 1, 1, 0, 2, 2, 1, 1, 1, 1, 2],
[2, 2, 1, 1, 0, 1, 0, 2, 1, 0, 0, 2, 2, 0, 1, 2],
[1, 0, 2, 1, 0, 2, 0, 1, 0, 1, 1, 2, 1, 1, 0, 2],
[1, 0, 0, 0, 1, 2, 1, 0, 1, 2, 1, 1, 1, 2, 0, 0],
[1, 2, 2, 2, 0, 0, 1, 1, 0, 1, 0, 2, 2, 1, 1, 0],
[0, 2, 2, 1, 0, 0, 1, 0, 2, 1, 1, 0, 2, 1, 1, 0],
[1, 0, 2, 1, 2, 0, 1, 1, 0, 2, 2, 2, 1, 1, 0, 1],
[1, 1, 1, 1, 1, 2, 1, 1, 0, 2, 1, 0, 0, 1, 0, 0],
[1, 2, 1, 0, 2, 2, 2, 1, 0, 1, 2, 1, 2, 0, 2, 1],
[2, 0, 2, 1, 2, 0, 1, 1, 2, 2, 2, 2, 1, 0, 2, 1],
[0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 0, 2, 0, 2, 0, 1],
[0, 1, 2, 1, 1, 0, 1, 2, 0, 1, 0, 0, 2, 1, 0, 2],
[0, 0, 2, 2, 2, 2, 2, 1, 0, 0, 0, 2, 0, 0, 1, 1]])
In [41]: I,J = np.ix_(np.arange(16), np.arange(16))
In [42]: img[I,J,ids]
Out[42]:
array([[5, 9, 9, 8, 8, 8, 5, 7, 1, 9, 9, 5, 5, 9, 6, 8],
[6, 7, 5, 8, 5, 6, 9, 6, 7, 7, 7, 8, 3, 7, 9, 5],
[7, 6, 8, 7, 6, 9, 6, 8, 9, 5, 8, 8, 9, 7, 9, 6],
[8, 9, 3, 4, 7, 5, 8, 4, 4, 9, 1, 4, 9, 9, 9, 7],
[9, 8, 9, 7, 9, 8, 7, 5, 8, 9, 9, 6, 9, 5, 8, 8],
[7, 9, 8, 8, 9, 3, 6, 9, 8, 6, 8, 7, 7, 7, 7, 7],
[8, 8, 5, 8, 9, 8, 8, 2, 8, 7, 8, 9, 5, 5, 6, 7],
[9, 6, 6, 9, 5, 3, 6, 4, 7, 6, 8, 8, 6, 3, 9, 9],
[7, 8, 9, 7, 5, 7, 5, 9, 6, 4, 7, 7, 8, 5, 7, 8],
[9, 7, 6, 4, 8, 9, 3, 8, 9, 2, 6, 9, 6, 7, 9, 7],
[9, 8, 6, 6, 5, 9, 3, 9, 2, 4, 9, 5, 9, 9, 6, 9],
[8, 7, 8, 3, 8, 8, 9, 7, 9, 5, 9, 8, 6, 9, 7, 8],
[8, 2, 7, 7, 4, 5, 9, 8, 8, 8, 6, 5, 3, 9, 9, 6],
[6, 8, 8, 5, 8, 8, 8, 9, 3, 7, 7, 8, 5, 4, 2, 9],
[3, 7, 9, 9, 8, 5, 9, 8, 9, 7, 3, 3, 9, 5, 5, 9],
[8, 4, 3, 6, 4, 9, 9, 9, 9, 9, 9, 7, 9, 7, 5, 8]])
Recent numpy versions have a function that does this for us
np.take_along_axis(img, ids[:,:,None], 2)[:,:,0]
and to set values np.put_along_axis.
You can convert a numerical index along a given axis into values, you can use np.take_along_axis or fancy indexing. When using a fancy index, you need to index along all axes with arrays whose shapes broadcast to the size of the final result. np.ogrid helps with this. For an MxNx3 array img (M, N, _ = img.shape), if you had ix = np.argmax(img, axis=2), the index would look like this:
r, c = np.ogrid[:M, :N]
maxes = img[r, c, ix]
Using take_along_axis saves you a step and some temp arrays:
maxes = np.take_along_axis(img, ix, 2)
Now create your mask:
significant = np.abs(maxes - img.mean(axis=2) > img.std(axis=2))
At this point you have a 2D boolean mask and an integer index in the third dimension. The simplest thing is probably to turn everything into a linear index:
r, c = np.where(significant)
Now you can construct the output:
color_index = np.zeros_like(img)
color_index[r, c, ix[significant]] = 255
While tempting, np.put_along_axis can not be used here in a straightforward manner. The issue is that masking ix with significant would invalidated its shape similarity. You could, however, create an intermediate 2D array containing 255 at the locations marked by significant, and use that with put_along_axis:
values = np.zeros(significant.shape, dtype=img.dtype)
values[significant] = 255
color_index = np.zeros_like(img)
np.put_along_axis(color_index, ix, values, 2)
All combined:
ix = np.argmax(img, axis=2)
significant = np.abs(np.take_along_axis(img, ix, 2) - img.mean(axis=2)) > img.std(axis=2)
color_index = np.zeros_like(img)
color_index[(*np.where(significant), ix[significant])] = 255
So I want to see if there is a possible way for me to maximize space in a list. For example:
lists = [3, 3, 3, 3, 3]
Maximum Value = 4 #from user input
Output = [3, 0, 4, 4, 4]
Process in each step:
Start: [3, 3, 3, 3, 3] #start
[3, 3, 3, 2, 4] #takes 1 number from lists[3] and puts it in lists[4] so that that is equal to 4
[3, 3, 1, 4, 4] #takes 2 numbers from(since 4 - lists[3] = 2 lists[2] and puts it in lists[3]
Finish:[3, 0, 1, 4, 4] #puts 3 numbers from lists[1] and puts them in lists[2](4 - lists[2] = 3) and now that there is a zero it should stop the program
Basically, the code should start at the back and fill in space in each value going down the list until one of the values is equal to 4 or 0 or until it reaches the start of the list.
1 last thing:
This should work with any list length and even if there are 0's after the last number. For example:
list = [3, 3, 5, 2, 7, 2, 3, 0, 0, 0, 0, 4]
MaximumValue = 4
Step 1: [3, 3, 5, 2, 7, 2, 3, 0, 0, 0, 0, 4]
[3, 3, 5, 2, 7, 1, 4, 0, 0, 0, 0, 4]
[3, 3, 5, 2, 4, 4, 4, 0, 0, 0, 0, 4]
[3, 3, 3, 4, 4, 4, 4, 0, 0, 0, 0, 4]
[3, 2, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4]
Finish: [1, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4] #reached lists[0] so stop program
You don't need all those intermediate steps. You can produce the list directly with easy division and remainder calculations.
>>> l = [3, 3, 5, 2, 7, 2, 3, 0, 0, 0, 0, 4]
>>> m = 4
>>> [m] * (sum(l) / m) + [sum(l) % m] + [0] * (len(l) - sum(l) / m - 1)
[4, 4, 4, 4, 4, 4, 4, 1, 0, 0, 0, 0]
>>>
This is sample code, maybe has better way.
lis = [3, 3, 5, 2, 7, 2, 3, 0, 0, 0, 0, 4]
MaximumValue = 4
start_index = 0
for i,v in enumerate(lis):
start_index = i
if v == 0:
start_index = i - 1
break
for i in range(start_index, 0, -1):
diff = MaximumValue - lis[i]
if diff >= 0:
lis[i] += diff
lis[i-1] -= diff
if lis[i-1] == 0:
break
print(lis)
Output:
[1, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4]
I'm looking for a Python magic method to pack a list of indexes of that sort
[0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4]
into this, with each index grouped in a specific list :
[[0, 1, 2, 3, 4], [5, 6, 7], [8, 9], [10], [11, 12, 13]]
I have already done it with a list comprehension plus an append loop like the following, but I feel like there's a Python one-liner that could do that. I'm working on lists that sometimes reach 10000+ items, so performance is important.
li = [0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4]
result = [[] for _ in xrange(max(li)+1)]
for i in xrange(len(li)):
result[li[i]].append(i)
You can use itertools.groupby to group the values. Then calculate the indices based on the lengths of each group, and keep a running count of the starting index for that group.
from itertools import groupby
def index_list(l):
temp = 0
index_list = []
for key, group in groupby(l):
items = len(list(group))
index_list.append([i+temp for i in range(items)])
temp += items
return index_list
Example
>>> l = [0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4]
>>> index_list(l)
[[0, 1, 2, 3, 4], [5, 6, 7], [8, 9], [10], [11, 12, 13]]
Not sure if this is better than the other answers, but I found it interesting to work it out nonetheless:
li = [0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4]
from collections import Counter
result = []
last = 0
for k,v in sorted(Counter(li).items()):
result.append(list(range(last, last + v)))
last += v
This can be done with the following expression:
>>> li = [0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4]
>>> [[i for i, n in enumerate(li) if n == x] for x in sorted(set(li))]
[[0, 1, 2, 3, 4], [5, 6, 7], [8, 9], [10], [11, 12, 13]]
My implementation:
li = [0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4]
lout = []
lparz = []
prev = li[0]
for pos, el in enumerate(li):
if el == prev:
lparz.append(pos)
else:
lout.append(lparz)
lparz = [pos,]
prev = el
lout.append(lparz)
print lout
outputs
[[0, 1, 2, 3, 4], [5, 6, 7], [8, 9], [10], [11, 12, 13]]
as required.