table {
        width: 236px;
    }

    table, tr, td {
        border: 0px;
    }

In a grid of 4 by 4 squares you want to place a skyscraper in each square with only some clues:

The height of the skyscrapers is between 1 and 4
No two skyscrapers in a row or column may have the same number of floors
A clue is the number of skyscrapers that you can see in a row or column from the outside
Higher skyscrapers block the view of lower skyscrapers located behind them


Can you write a program that can solve this puzzle?


Example:


To understand how the puzzle works, this is an example of a row with 2 clues. Seen from the left side there are 4 buildings visible while seen from the right side only 1:




 4
    
    
    
    
 1



There is only one way in which the skyscrapers can be placed. From left-to-right all four buildings must be visible and no building may hide behind another building:




 4
 1
 2
 3
 4
 1



Example of a 4 by 4 puzzle with the solution:




  
    
    
 1
 2
  


  
  
  
  
  
  


  
  
  
  
  
 2


 1
  
  
  
  
  


  
  
  
  
  
  


  
  
  
 3
  
  





  
  
  
 1
 2
  


  
 2
 1
 4
 3
  


  
 3
 4
 1
 2
 2


 1
 4
 2
 3
 1
  


  
 1
 3
 2
 4
  


  
  
  
 3
  
  



Task:



Finish:

Difficulty: interview

Starter code:

def solve_puzzle (clues):
	

Solutions:
from itertools import permutations, chain

def solve_puzzle (clues):
    size = 4
    for poss in permutations(permutations(list(range(1, size+1)), size), size):
        for i in range(size):
            if len(set(row[i] for row in poss)) < size:
                break
        else:
            cols_top = [[row[i] for row in poss] for i in range(size)]
            rows_right = [list(reversed(row)) for row in poss]
            cols_btm = [[row[i] for row in reversed(poss)] for i in reversed(list(range(size)))]
            rows_left = list(reversed(poss))
            for i, row in enumerate(chain(cols_top, rows_right, cols_btm, rows_left)):
                if not clues[i]:
                    continue
                visible = 0
                for j, v in enumerate(row):
                    visible += v >= max(row[:j+1])
                if visible != clues[i]:
                    break
            else:
                return poss


from itertools import chain, product


def unique_products(sets, excluding=frozenset()):
    """
    Calculates cartesian product between the sets passed as parameter, only keeping products with unique numbers.
    """
    if not sets:
        yield ()
        return
    for x in sets[0] - excluding:
        for rest in unique_products(sets[1:], excluding | {x}):
            yield (x,) + rest

def solve_column(column, clue):
    # First calculates all valid fillings with the given column, than merges the solutions in a single list where each
    # cell is the set containing the values in the same position of each solution.
    # [column=[{1, 2, 3}, {1, 2, 3}, {1, 2}, {4}], clue=2] -> [(3, 1, 2, 4), (3, 2, 1, 4)] -> [{3}, {1, 2}, {1, 2}, {4}]
    combos = [set(x) for x in list(zip(*[x for x in unique_products(column) if see(x) == clue]))]
    for i in range(len(column)):
        column[i] -= column[i] - combos[i]


def see(column):
    """
    Calculates how many buildings can be viewed.
    """
    visible = tallest = 0
    for building in column:
        visible += building > tallest
        tallest = max(building, tallest)
    return visible

def simplify(column, clue, n):
    if clue is 1:
        column[0] -= set(range(1, n))  # the highest building must be in the first cell
    elif clue > 0:  # if clue is 0 means no hint
        for i in range(clue - 1):
            column[i] -= set(range(n, n + i + 1 - clue, -1))
        solve_column(column, clue)


def solve_cross(city, x, y):
    """
    Removes from the same row and column the number found at (x, y), if the cell contains only one number.
    :type city: list of (list of set)
    """
    if len(city[x][y]) is not 1:
        solve_uniqueness(city, x, y)
        return
    for i in chain(range(x), range(x + 1, len(city))):
        city[i][y] -= city[x][y]
    for j in chain(range(y), range(y + 1, len(city))):
        city[x][j] -= city[x][y]


def solve_uniqueness(city, x, y):
    """
    Checks if one of the numbers in the cell city[x][y] compares only once in the same row / column.
    """
    for n in city[x][y]:  # checks if a number appears only once in the row / column
        if not any([n in city[i][y] for i in chain(range(x), range(x + 1, len(city)))]) or \
                not any([n in city[x][j] for j in chain(range(y), range(y + 1, len(city)))]):
            city[x][y].clear()
            city[x][y].add(n)
            solve_cross(city, x, y)
            break


def solve(n, clues):
    city = list(list(set(range(1, n + 1)) for i in range(n)) for j in range(n))
    while not all([len(cell) is 1 for row in city for cell in row]):
        for i in range(n):
            for j in range(n):
                simplify([city[k][j] for k in range(n)], clues[i * n + j], n)
            for s in range(n):
                for t in range(n):
                    solve_cross(city, s, t)
            city = [list(row) for row in reversed(list(zip(*city)))]  # rotates the city 90° anti-clockwise
    return tuple(tuple(cell.pop() for cell in row) for row in city)
    
solve_puzzle = lambda x: solve(4, x)

import numpy as np

#check if n appears more than once in array
def duped(l):
    for e in l:
        if l.count(e) > 1:
            return True
    return False

#find empty square to place number
def find_empty(city):
    for r in range(4):
        for c in range(4):
            if city[r][c]==0:
                return (r,c)
            
#check if clue works with n of skycrapers seen
def check_clue(clue,counter):
    if clue==0:
        return True
    return counter==clue

#check how many skyscrapers can be seen and if they are ok with clues          
def vis(arr,clues):
    l=len(arr)
    a1=0
    a2=0
    counter1=0
    counter2=0
    #count skycrapers visible from the two sides
    for k in range(l):
        if arr[k]>a1:
            a1=arr[k]
            counter1+=1
        if arr[l-k-1]>a2:
            a2=arr[l-k-1]
            counter2+=1
    #if the arr is not filled yet can't count the clues
    if l<4:
        return True
    #check the clues
    return check_clue( clues[0] , counter1 ) and check_clue( clues[1] , counter2 )
        
#check if a n placed is ok with all the clues           
def is_valid(r,c,city,clues):
    #row and column
    row=[ h for h in city[r] if h!=0]
    col=[r[c] for r in city if r[c]!=0]
    #if numbers are duplicate return
    if duped(row) or duped(col):
        return False
    #top right bottom left clues
    tc,rc,bc,lc = [clues[k][c] if k%2==0 else clues[k][r] for k in range(4)]
    #check if all clues work
    return vis(row,(lc,rc)) and vis(col,(tc,bc))
    
#recursive function
def solve(city,clues):
    #find position of empty square
    pos = find_empty(city)
    #if there is no empty square then it's solved
    if not pos:
        return True
    #row and column
    r,c=pos
    #all numbers that you can put in a 4x4
    for n in range(1,5):
        #put number in city
        city[r][c] = n
        #check if it's good
        if is_valid(r,c,city,clues):
            #recursion trigger
            if solve(city,clues):
                return True
        #delete number if it doesn't work cause of backtracking or just not valid
        city[r][c] = 0
    #if no possible solution :
    return False
    
#initial function
def solve_puzzle (clues):
    #list of clues with the last 2 reversed to make indexing easy
    clues = [list(clues[k:k+4]) if k < 5 else list(reversed(clues[k:k+4])) for k in range(0,13,4)] #top right bottom left
    #initialize city matrix
    city=[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
    #solve
    solve(city,clues)
    return tuple([tuple(a) for a in city])


from itertools import permutations,chain
from math import factorial

def num_visible(row):
    visible = 0
    max_height = 0
    for i in row:
        if i > max_height:
            visible = visible + 1
            max_height = i
    return visible
            

def check_solution(solution,clues):

    solution = list(solution)
    solution = list(chain.from_iterable(solution))
    
    front_facing_key = {
    0:[0,4,8,12], 1:[1,5,9,13], 2:[2,6,10,14], 3:[3,7,11,15], 4:[3,2,1,0], 5:[7,6,5,4],
    6:[11,10,9,8],7:[15,14,13,12], 8:[15,11,7,3], 9:[14,10,6,2], 10:[13,9,5,1],
    11:[12,8,4,0], 12:[12,13,14,15], 13:[8,9,10,11], 14:[4,5,6,7], 15:[0,1,2,3]}
    
    front_facing_values = {
    0:[0,0,0,0], 1:[0,0,0,0], 2:[0,0,0,0], 3:[0,0,0,0], 4:[0,0,0,0], 5:[0,0,0,0],
    6:[0,0,0,0], 7:[0,0,0,0], 8:[0,0,0,0], 9:[0,0,0,0], 10:[0,0,0,0], 11:[0,0,0,0],
    12:[0,0,0,0], 13:[0,0,0,0], 14:[0,0,0,0],15:[0,0,0,0],}
    
    f_len = len(front_facing_values)
    
    for i in range(f_len):
        for j in range(4):
            front_facing_values[i][j] = solution[front_facing_key[i][j]]
    
    for i in range(f_len):
        if num_visible(front_facing_values[i]) != clues[i] and clues[i] !=0:
            return False
    return True
            
def solve_puzzle (clues):
    
    counter = 1
    for i in list(permutations([1,2,3,4])):
        for j in list(permutations([1,2,3,4])):
            for k in list(permutations([1,2,3,4])):
                for l in list(permutations([1,2,3,4])):
                    solution = (i,j,k,l)
                    
                    trip = False
                    for m in range(4):
                        test = [i[m],j[m],k[m],l[m]]
                        if len(set(test)) !=4:
                            trip = True
                    if trip == False:
                        if(check_solution(solution,clues)):
                            return solution
                
    return "No Solution"


N = 4


def count(lst):
    l = 0
    c = 0
    for v in lst:
        if v > l:
            l = v
            c += 1
    return c


def check_clues(clues, curr_soln):
    visible = []
    for j in range(N):
        c = count([curr_soln[i][j] for i in range(N)])
        visible.append(c)
    for i in range(N):
        c = count([curr_soln[i][j] for j in range(N-1, -1, -1)])
        visible.append(c)
    for j in range(N-1, -1, -1):
        c = count([curr_soln[i][j] for i in range(N-1, -1, -1)])
        visible.append(c)
    for i in range(N-1, -1, -1):
        c = count([curr_soln[i][j] for j in range(N)])
        visible.append(c)
    for viz, clue in zip(visible, clues):
        if clue != 0:
            if viz != clue:
                return False
    return True


def r_solve_puzzle(clues, curr_soln, col_avail, row_avail, i, j):
    if i == N:
        if check_clues(clues, curr_soln):
            return tuple(tuple(row) for row in curr_soln)
        else:
            return None
    ni = i+1 if j == N-1 else i
    nj = 0 if j == N-1 else j+1
    for v in range(1, N+1):
        if v in col_avail[j] and v in row_avail[i]:
            col_avail[j].remove(v)
            row_avail[i].remove(v)
            curr_soln[i][j] = v
            res = r_solve_puzzle(clues, curr_soln, col_avail, row_avail, ni, nj)
            curr_soln[i][j] = 0
            col_avail[j].add(v)
            row_avail[i].add(v)
            if res is not None:
                return res


def solve_puzzle (clues):
    curr_soln = [[0 for j in range(N)] for i in range(N)]
    col_avail = [set(range(1, N+1)) for i in range(N)]
    row_avail = [set(range(1, N+1)) for i in range(N)]
    return r_solve_puzzle(clues, curr_soln, col_avail, row_avail, 0, 0)


import itertools as it

rows = tuple (it.permutations (list(range(1,5))))

visibles = [{r:reduce (lambda (m,s),e: (e,1+s) if e > m else (m,s), r,       (r[ 0],1))[1] for r in rows},
            {r:reduce (lambda (m,s),e: (e,1+s) if e > m else (m,s), r[::-1], (r[-1],1))[1] for r in rows}]

def matches (puzzle, clues):
    return (    all (clues[15-i] == visibles[0][      puzzle [i]] for i in range(4) if clues[15-i] > 0)
            and all (clues[ 4+i] == visibles[1][      puzzle [i]] for i in range(4) if clues[ 4+i] > 0)
            and all (clues[   i] == visibles[0][list(zip(*puzzle))[i]] for i in range(4) if clues[   i] > 0)
            and all (clues[11-i] == visibles[1][list(zip(*puzzle))[i]] for i in range(4) if clues[11-i] > 0))

def valid (puzzle):
    return all (r in rows for r in zip (*puzzle))

def solve_puzzle (clues):
    return next (puzzle for puzzle in it.product (rows, repeat=4) if valid (puzzle) and matches (puzzle, clues))


#Solving the grid can be broken down into the following main steps:
#1) Using the outside view to remove certain options or place known numbers
#2) Look for block that only has one remaining option (step repeated each time grid is adjusted)
#3) Look for lines with two blocks with restriction of two same options remove these options for other blocks in line
#3) Look for lines with 2 blocks remaining. Create 2 options from these and compare to the view from both sides

#Practical routes taken:
#1)gridComplete is an array used to indicate if number was placed or not. This helps confirm if number is "placed" or just last remaining option. As when it is placed the same number needs to be removed from line and adjacent line
#2)Clues where placed into array with pairs horizonal and vertical. So that the can be looped and assigned easily

import numpy as np

def solve_puzzle (clues):
    
    nonlocal grid
    grid = np.array([("1234","1234","1234","1234"),("1234","1234","1234","1234"),("1234","1234","1234","1234"),("1234","1234","1234","1234")])
    
    nonlocal gridComplete
    gridComplete = np.array([(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0)])
    
    nonlocal cluesN
    cluesN = list(map(str,clues))
    clues = list(map(str,clues))
    
    nonlocal hor
    nonlocal vert
    hor = [(clues[0]+clues[11]),(clues[1]+clues[10]),(clues[2]+clues[9]),(clues[3]+clues[8])]
    vert =[(clues[15]+clues[4]),(clues[14]+clues[5]),(clues[13]+clues[6]),(clues[12]+clues[7])]
    
#1) Removing known options and placing according to view and standard practise
#----------------------------------------------------------------------------
    #1: M1:
    #    -1 place 4
    #    -4 place 1234
    #    -2V remove [1] 3
    #    -2V remove [0] 4
    #    -3V remove [0] 3
    
    #M1 - 4 place 1234
    for z in range(len(cluesN)):
        if cluesN[z] == "4" and z<=3:
            placeNum(1,0,z, check = False)
            placeNum(2,1,z, check = False)
            placeNum(3,2,z, check = False)
            placeNum(4,3,z, check = False)
        elif cluesN[z] =="4" and 4 <= z <= 7:
            placeNum(1,z-4,3, check = False) 
            placeNum(2,z-4,2, check = False)
            placeNum(3,z-4,1, check = False)
            placeNum(4,z-4,0, check = False)
        elif cluesN[z] == "4" and 8 <= z <= 11:
            placeNum(1,3,11-z, check = False)  # changed this from placeNum(4,11-z,3)
            placeNum(2,2,11-z, check = False)
            placeNum(3,1,11-z, check = False)
            placeNum(4,0,11-z, check = False)
        elif cluesN[z] == "4" and 12 <= z <= 15:
            placeNum(1,15-z,0, check = False)
            placeNum(2,15-z,1, check = False)
            placeNum(3,15-z,2, check = False)
            placeNum(4,15-z,3, check = False)
            
        #M1 - 1 place 4
        if cluesN[z] == "1" and z<=3:
            placeNum(4,0,z, check = False)
        elif cluesN[z] =="1" and 4 <= z <= 7:
            placeNum(4,z-4,3, check = False) 
        elif cluesN[z] == "1" and 8 <= z <= 11:
            placeNum(4,3,11-z, check = False) 
        elif cluesN[z] == "1" and 12 <= z <= 15:
            placeNum(4,15-z,0, check = False)

    #M1 - 2v remove[1] 3 
    removeM1(2,1,3)
    
    #M1 - 2V remove [0] 4
    removeM1(2,0,4)
    
    #M1 - 3V remove [0] 3
    removeM1(3,0,3)
    
#-------------------------------------------------------------------
    
#2) - Check if any single options are left in a cell
    lookforsingle()
    
#3) - look for any paired options in a line and remove that pair from other cells
    double2()

#4) look for 2 cells left in line and create 2 options and compare to view
    lookforspace2() #one direction
    lookforspace2REV() #Reverse direction
    
    #Convert string to int in list for return
    grid = grid.astype(np.integer)
    #Return truple answer
    return tuple(map(tuple, grid))

def lookforspace2():
    
    #Look for 2 unknown cells calculate options and compare to view
    
    first = True
    first2 = True
    for y in range(4):
        view = vert[y][0]   #VIEW on left
        view1 = hor[y][0]   #VIEW from top
        lstOption1 = np.copy(grid[y])
        lstOption2 = np.copy(grid[y])
        lstVertOption1 = np.copy(grid[::,y])
        lstVertOption2 = np.copy(grid[::,y])
        
        for x in range(4):
            
            #horizontal check
            if len(str(lstOption1[x])) == 2:
    
                #remove first paired number and then the next remove second
                if first == True: 
                    optionPair = str(lstOption1[x])
                    lstOption2[x] = (str(lstOption2[x])).replace(str(optionPair[0]),"") 
                    lstOption1[x] = (str(lstOption1[x])).replace(str(optionPair[1]),"")
                    first = False
                elif first == False: 
                    lstOption1[x] = str(lstOption1[x]).replace(optionPair[0],"")
                    lstOption2[x] = str(lstOption2[x]).replace(optionPair[1],"")
                    first = True
                
            #Vertical check
            if len(str(lstVertOption1[x])) == 2:
    
                #remove first paired number and then the next remove second
                if first2 == True: 
                    optionPair2 = str(lstVertOption1[x])
                    lstVertOption2[x] = (str(lstVertOption2[x])).replace(str(optionPair2[0]),"") 
                    lstVertOption1[x] = (str(lstVertOption1[x])).replace(str(optionPair2[1]),"")
                    first2 = False
                elif first2 == False: 
                    lstVertOption1[x] = str(lstVertOption1[x]).replace(optionPair2[0],"")
                    lstVertOption2[x] = str(lstVertOption2[x]).replace(optionPair2[1],"")
                    first2 = True
        
        #calculate the view of each option
        calc1 = viewCalc(lstOption1.tolist())   
        calc2 = viewCalc(lstOption2.tolist())
        calc3 = viewCalc(lstVertOption1.tolist())
        calc4 = viewCalc(lstVertOption2.tolist())
        
        #if option correct then place option in grid
        if int(calc3) == int(view1) and int(calc3) != int(calc4):
            grid[::,y] = lstVertOption1
                #place answer
        elif int(calc4) == int(view1) and int(calc3) != int(calc4):
            grid[::,y] = lstVertOption2
        
        if int(calc1) == int(view) and int(calc1) != int(calc2):
            grid[y] = lstOption1
                #place answer
        elif int(calc2) == int(view) and int(calc1) != int(calc2):
            grid[y] = lstOption2
    
    lookforsingle() #clear out placed numbers from adjacent lines

def lookforspace2REV():
    
    #Look for 2 unknown cells calculate options and compare to view
    #Reversed direction
    
    first = True
    first2 = True
    for y in range(4):
        view = vert[y][1]   #VIEW on right
        view1 = hor[y][1]   #VIEW from bottom
        lstOption1 = (np.copy(grid[y]))[::-1]
        lstOption2 = (np.copy(grid[y]))[::-1]
        lstVertOption1 = (np.copy(grid[::,y]))[::-1]
        lstVertOption2 = (np.copy(grid[::,y]))[::-1]
        
        for x in range(4):
            
            if len(str(lstOption1[x])) == 2:
    
                #remove first and then the next remove second
                if first == True: 
                    optionPair = str(lstOption1[x])
                    lstOption2[x] = (str(lstOption2[x])).replace(str(optionPair[0]),"") 
                    lstOption1[x] = (str(lstOption1[x])).replace(str(optionPair[1]),"")
                    first = False
                elif first == False: 
                    lstOption1[x] = str(lstOption1[x]).replace(optionPair[0],"")
                    lstOption2[x] = str(lstOption2[x]).replace(optionPair[1],"")
                    first = True
                
            if len(str(lstVertOption1[x])) == 2:
    
                #remove first and then the next remove second
                if first2 == True: 
                    optionPair2 = str(lstVertOption1[x])
                    lstVertOption2[x] = (str(lstVertOption2[x])).replace(str(optionPair2[0]),"") 
                    lstVertOption1[x] = (str(lstVertOption1[x])).replace(str(optionPair2[1]),"")
                    first2 = False
                elif first2 == False: 
                    lstVertOption1[x] = str(lstVertOption1[x]).replace(optionPair2[0],"")
                    lstVertOption2[x] = str(lstVertOption2[x]).replace(optionPair2[1],"")
                    first2 = True
        
        calc1 = viewCalc(lstOption1.tolist())   #grid list row
        calc2 = viewCalc(lstOption2.tolist())
        calc3 = viewCalc(lstVertOption1.tolist())
        calc4 = viewCalc(lstVertOption2.tolist())
        
        if int(calc3) == int(view1) and int(calc3) != int(calc4):
            grid[::,y] = lstVertOption1[::-1]
        elif int(calc4) == int(view1) and int(calc3) != int(calc4):
            grid[::,y] = lstVertOption2[::-1]
        
        if int(calc1) == int(view) and int(calc1) != int(calc2):
            grid[y] = lstOption1[::-1]
        elif int(calc2) == int(view) and int(calc1) != int(calc2):
            grid[y] = lstOption2[::-1]
            
    
    lookforsingle()


    
def removeM1(viewpos, coord, removeNum):
    
    #Given a coordinate as location from the viewpos (viewpoint outside of grid linked to clues)
    #remove the option(removeNum) from the list
    
    lstCord = []
    for z in range(len(cluesN)):
        if cluesN[z] == str(viewpos):
            if z <= 3: lstCord = [coord,z]
            if 4 <= z <= 7: lstCord = [z-4,3-coord] 
            if 8 <= z <= 11: lstCord = [3-coord,11-z] 
            if 12 <= z <= 15: lstCord = [15-z,coord] 
            
            grid[lstCord[0]][lstCord[1]] = str(grid[lstCord[0]][lstCord[1]]).replace(str(removeNum),"")
    
        
def double2():
    
    #look for two pairs in line and remove from other options in adjacent lines
    
    dbl = False
    for y in range(4):
        for x in range(4):
            lst = gridComplete.sum(axis = 1)
            if len(grid[y][x]) == 2:
                for x2 in range(x+1,4):
                    if grid[y][x] == grid[y][x2]:
                        
                        for g in range(4):
                            try:
                                if len(str(grid[y][g])) > 1 and str(grid[y][g]) !=  grid[y][x2]: #already they are equal and checked that is 2
                                    grid[y][x2]= str(grid[y][x2]).replace((str(grid[y][x]))[0],"")
                                    dbl = True
                            except:
                                pass
                            try:
                                if len(str(grid[y][g])) > 1 and str(grid[y][g]) !=  grid[y][x2]: 
                                    grid[y][x2]= str(grid[y][x2]).replace((str(grid[y][x]))[1],"")
                                    dbl = True
                            except:
                                pass
                            
    if dbl == True:
        lookforsingle()
        double2()
    
def lookforsingle():
    #Look for single option in cell or where only one occurance of that option in line and place it
    count = 0
    pos = []
    placed = False
    posC = np.array([(0,0),(0,0),(0,0),(0,0)])
    countC=[0,0,0,0]
    for p in range(1,5):
        for a in range(4):
            if placed == False: 
                for o in range(4):
                    if str(p) in str(grid[a][o]) and len(str(grid[a][o])) > 1 and gridComplete[a][o] == 0:
                        count += 1
                        countC[a] = countC[a] + 1
                        posC[a] = [a,o]
                        pos = [a,o]
                        
                    elif len(str(grid[a][o])) == 1 and gridComplete[a][o] == 0:
                        placeNum(str(grid[a][o]), a, o,check = False)
                        placed = True
                        break
        
                if count == 1 and placed == False:
                    placeNum(p, pos[0], pos[1], check = False)
                    placed = True
                    break
                count == 0
        
        #coloumb single occurances
        for u in range(4):
            if placed == True: 
                break
            if countC[u] == 1:
                placeNum(p,posC[u][0],posC[u][1], check = False)
                placed = True
                
        countC = [0,0,0,0]
    
    if placed == True: lookforsingle()
        
    

def viewCalc(lstCalc): #provide a list of 4 integers
    
    #return the a calculation of amount of building can be seen from list
    
    prev = 0
    count = 0
    for i in range(4):
        if int(lstCalc[i]) > prev:
            count += 1
            prev = int(lstCalc[i])
    return count
        
def placeNum(num ,ypos, xpos, check = True):

    #places given number in coordinates of grid and then removes these options from
    #adjacent lines.
    #after which can be checked for single options left or not checked for placements to continue before checking
    
    for y in range(4):
        try:
            if len(str(grid[ypos][y])) > 1: grid[ypos][y]= str(grid[ypos][y]).replace(str(num),"") 
        except:
            pass
        try:
            if len(str(grid[y][xpos])) > 1: grid[y][xpos]= str(grid[y][xpos]).replace(str(num),"")
        except:
            pass
            
    grid[ypos][xpos] = num
    gridComplete[ypos][xpos] = 1
    if check == True:lookforsingle()