2

I'm coding a program that reads a line in a file and determines whether or not the line makes a Lo Shu Magic square. In this magic square, the sum of the rows, sum of the columns, and sum of the diagonals have to equal 15, and each number 1-9 can only occur once in the square. This is what I have so far:

def main():
    for line in open("Magic Square Input.txt"):
        items = line.split(" ")
        items = [int(x) for x in items]
        result = [items[0:3], items[3:6], items[6:9]]
        isMagic(result)

def isMagic(result):
    checks1 = ''
    for x in result:
        for y in range(3):
            if sum (result[y][y] for y in range(3)) == 15:
                if sum(x[y] for x in result) == 15:
                    checks1 = checkDupe(result)
                else:
                    checks1 = 'Invalid'
            else:
                checks1 = 'Invalid'

    print(checks1)

def checkDupe(result):
    checks1 = ''
    for i in range(0,8):
        counter = 0
        for j in result:
            if (j == i):
                counter += 1
        if counter > 0:
            checks1 = 'Invalid'
        else:
            checks1 = 'Valid'
    return checks1
main()

the contents of my text file are as follows:

4 3 8 9 5 1 2 7 6
8 3 4 1 5 9 6 7 2
6 1 8 7 5 3 2 9 4
6 9 8 7 5 3 2 1 4
6 1 8 7 5 3 2 1 4
6 1 3 2 9 4 8 7 5
5 5 5 5 5 5 5 5 5

The first three numbers in each line represent the top row of the square, the next three are the middle row, and the last three are the bottom row. The problem im having is that the first three squares ARE valid, and the last four are supposed to be invalid. But what my code keeps printing out for me is

Valid
Valid
Valid
Valid
Valid
Invalid
Valid

Could somebody show me where I'm screwing up here? I'm fairly new to python and I've been staring at this for hours trying to make sense of it.

TheBudderBomb
  • 73
  • 1
  • 3
  • 14
  • 1
    maybe first check only rows, later only columns, and finally diagonals - it can be easier to control it. And it can be easier without spliting into rows - row: `sum(items[0:3])`, column: `sum([items[0], items[3], items[6]])`, diagonal: `sum([items[0], items[4], items[8]])` – furas Feb 05 '17 at 19:51
  • and you can use `len(set(items)) = 9` to check if numbers don't repeat – furas Feb 05 '17 at 19:52
  • or you can check if numbers 1-9 are `items` - `for x in range(1, 10): if x not in items: Invalid` – furas Feb 05 '17 at 19:57
  • BTW: you check numbers 0-7,not 1-9. – furas Feb 05 '17 at 19:58
  • if you have problem then use many `print()` to check values in variables and which part of code is executed - it helps to find problem. – furas Feb 05 '17 at 19:59
  • using `print()` I found that `j == i` compares ie. `[4, 3, 8] == 0` – furas Feb 05 '17 at 20:24

6 Answers6

3

This problem is much easier to think about if you start with a flat list:

[4, 3, 8, 9, 5, 1, 2, 7, 6]

and then work out which indexes you need to check. There are only eight in all:

indexes = (
    (0, 1, 2), (3, 4, 5), (6, 7, 8), # rows
    (0, 3, 6), (1, 4, 7), (2, 5, 8), # cols
    (0, 4, 8), (2, 4, 6),            # diag
    )

With that set up, the check function becomes very simple:

def main():
    for line in open('Magic Square Input.txt'):
        square = [int(n) for n in line.split()]
        if len(set(square)) != len(square):
            print('Invalid: Duplicates')
        else:
            for idx in indexes:
                if sum(square[i] for i in idx) != 15:
                    print('Invalid: Sum')
                    break
            else:
                print('Valid')
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
2

My version without spliting items into rows

data = '''4 3 8 9 5 1 2 7 6
8 3 4 1 5 9 6 7 2
6 1 8 7 5 3 2 9 4
6 9 8 7 5 3 2 1 4
6 1 8 7 5 3 2 1 4
6 1 3 2 9 4 8 7 5
5 5 5 5 5 5 5 5 5'''

def main():
    for line in data.split("\n"):
        # create list with all numbers
        items = list(map(int, line.split()))
        print(is_magic(items))

def is_magic(items):

    # --- dups ---

    #print('dups')
    #print(len(set(items)) == 9)
    #for x in range(1, 10):
    #    print(x, x in items)
    if len(set(items)) != 9:
        return 'Invalid'

    # --- rows ---

    #print('rows')
    for x in range(0, 9, 3):
        l = items[x:x+3]
        #print(l, sum(l) == 15)
        if sum(l) != 15:
            return 'Invalid'

    # --- cols ---

    #print('cols')
    for x in range(3):
        l = [items[x], items[x+3], items[x+6]]
        #print(l, sum(l) == 15)
        if sum(l) != 15:
            return 'Invalid'

    # --- diags ---

    #print('diags')
    l = [items[0], items[4], items[8]]
    #print(l, sum(l) == 15)
    if sum(l) != 15:
        return 'Invalid'

    l = [items[2], items[4], items[6]]
    #print(l, sum(l) == 15)
    if sum(l) != 15:
        return 'Invalid'

    # --- OK ---

    return 'Valid'

main()
furas
  • 134,197
  • 12
  • 106
  • 148
2

    def magic_square(n):
        num=(n*((n*n)+1))/2
        print('\nThe Magic Number Is:-',num,'\n')
        f=[]
        for i in range(0,n):
            a=[]
            for j in range(0,n):
                a.append(0)
            f.append(a)
        (x,i,p,q)=(n*n,1,int(n/2),n-1)
        while x!=0:
            if x==0:
                (f[p][q],i,p,q,x)=(i,i+1,p-1,q+1,x-1)
                continue
            else:
                if p==-1 and q==n:
                    p=0
                    q=n-2
                    if f[p][q]==0:
                        (f[p][q],i,p,q,x)=(i,i+1,p-1,q+1,x-1)
                        continue
                    else:
                        p=p+1
                        q=q-2
                        f[p][q]=i
                        i=i+1
                        p=p-1
                        q=q+1
                        x=x-1
                        continue
                if p==-1:
                    p=n-1
                    if f[p][q]==0:
                        (f[p][q],i,p,q,x)=(i,i+1,p-1,q+1,x-1)
                        continue
                    else:
                        p=p+1
                        q=q-2
                        f[p][q]=i
                        i=i+1
                        p=p-1
                        q=q+1
                        x=x-1
                        continue
                if q==n:
                    q=0
                    if f[p][q]==0:
                        (f[p][q],i,p,q,x)=(i,i+1,p-1,q+1,x-1)
                        continue
                    else:
                        p=p+1
                        q=q-2
                        f[p][q]=i
                        i=i+1
                        p=p-1
                        q=q+1
                        x=x-1
                        continue
                else:
                    if f[p][q]==0:
                        (f[p][q],i,p,q,x)=(i,i+1,p-1,q+1,x-1)
                        continue
                    else:
                        p=p+1
                        q=q-2
                        f[p][q]=i
                        i=i+1
                        p=p-1
                        q=q+1
                        x=x-1
                        continue
        for i in range(len(f)):
            for j in range(len(f[i])):
                print(f[i][j] ,end = "   ")
            print("\n")

INPUT

magic_square(5)

OUTPUT

The Magic Number Is:- 65.0

9 3 22 16 15

2 21 20 14 8

25 19 13 7 1

18 12 6 5 24

11 10 4 23 17

Kasturi
  • 21
  • 5
  • We use spaces after %d to get gaps between outputs, we use end=" " in python for the same – Kasturi Sep 04 '18 at 16:32
  • I have not mentioned condition for 2 and less. You can just add if statement in the start for this – Kasturi Sep 04 '18 at 16:34
  • As same as 5, you can get magic square for any odd integer greater than two – Kasturi Sep 04 '18 at 16:45
  • It fails to output magic square for 4: `line 39, in magic_square f[p][q] = i IndexError: list index out of range` – Gryu Jan 06 '20 at 16:17
1

I had to make some major changes, but it seemed like your checkDupe method wasn't working right. You also only checked for one diagonal instead of both. Also, note that instead of saving whether the answer is valid or not using a checks1 variable, it simply returns 'Invalid' if anything is wrong, this generally makes code much cleaner and simplified the problem quite a bit. If 'Invalid' is never returned, then the method just returns 'Valid' at the end.

   def main():
    for line in open("Magic Square Input.txt"):
        items = line.split(" ")
        items = [int(x) for x in items]
        result = [items[0:3], items[3:6], items[6:9]]
        print isMagic(result)

def isMagic(result):
    # check duplicates
    if(checkDupe(result) == 'Invalid'):
        return 'Invalid'
    # diagonals
    if sum (result[y][y] for y in range(3)) != 15:
        return 'Invalid'
    # other digonals
    if sum (result[2 - y][2 - y] for y in range(3)) != 15:
        return 'Invalid'
    # rows and columns
    for i in range(3):
        if sum(result[i][y] for y in range(3)) != 15:
            return 'Invalid'
        if sum(result[x][i] for x in range(3)) != 15:
            return 'Invalid'
    return 'Valid'

def checkDupe(result):
    for x in range(1,9):
        if(not x in (result[0]+result[1]+result[2])):
            return 'Invalid'
        return 'Valid'
main()
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Wso
  • 192
  • 2
  • 11
1

In order to help you, I should start saying that your code is very difficult to read. Since you are new to Python, soon you will find out that one of the major benefits of Python is its clear syntax, which makes very easy to figure out what a piece of code is doing. That being said, I solve your problem, using the same logic as you did, but making the code more readable and using some of Python tricks to make the solution shorter and cleaner.

def main():

    """Open the file, parse the input and check if it is a magic cube"""

    with open("Magic Square Input.txt") as f:

        for line in f.readlines():

            numbers = line.split(" ")
            cube = [int(x) for x in numbers]            
            is_magic(cube)


def is_magic(cube):

    """Check if cube is magic.
    There are two conditions that must be satisfied:
    1 - There must not be any repetitions of the numbers
    2 - All vertical/horizontal/diagonal sums must be 15
    """

    if not dupe(cube) and check_sum(cube):        
        print ('Valid')

    else:        
        print ('Invalid')


def dupe(cube):

    """Check if there are repetitions in the cube."""

    if len(cube) == len(set(cube)):        
        return False
    return True



def check_sum(cube):

    """Check if all vertical/horizontal/diagonal sums are 15"""

    if vertical_check(cube) and horizontal_check(cube) and diagonal_check(cube):        
        return True


def vertical_check(cube):

    if sum(cube[0:9:3]) == sum(cube[1:9:3]) == sum(cube[2:9:3]) == 15:      
        return True    
    return False


def horizontal_check(cube):

    if sum(cube[0:3]) == sum(cube[3:6]) == sum(cube[6:9]) == 15:      
        return True    
    return False


def diagonal_check(cube):

    if sum(cube[0:9:4]) == sum(cube[2:7:2]) == 15:      
        return True    
    return False

main()

I hope you can understand the solution from the comments in the code. If this is not the case, please post here again.

Arthur
  • 553
  • 1
  • 4
  • 10
  • Thank you so much friend, this helped me a ton! – TheBudderBomb Feb 05 '17 at 21:35
  • Well wait, in my original code, result HAS to be a 2d array after being read, like if the line in the text file is 1 2 3 4 5 6 7 8 9, its turned into [[1,2,3],[4,5,6],[7,8,9]] – TheBudderBomb Feb 05 '17 at 21:54
  • Oh, I did not know that the a 2D array was a necessity in the processing stage. I used a simple list because is the natural way of solving this problem. Moreover, you can reformat the result at the end, if you want it. Just out of curiosity, why you need a 2D array, my friend? – Arthur Feb 05 '17 at 23:05
  • It just seemed more categorical i guess, was a tad easier for me to visualize how to do this with the 2d array – TheBudderBomb Feb 06 '17 at 22:58
  • I agree with you. Since we are talking about a cube, the natural way would be thinking in a 2D array. Nevertheless, when thinking in computer science terms, this is not true. Having a compound list [[1, 2, 3], [4, 5, 6], [7, 8, 9]] it is more clumsy than having a simple list. It requires double loops to iterate through all elements, making the solution less efficient and harder do understand and debug. Furthermore, all operations required to check if the cube is magic are natural to a simple list. So in terms of efficiency and code maintenance is better to use a simple list. – Arthur Feb 07 '17 at 13:20
  • True, true. Thanks for all your input! – TheBudderBomb Feb 07 '17 at 20:55
0

Here I have created sample method to solve this issue. In this method, we are maintaining following lists

  • lstAvailableNumbers: list of available numbers (in your case it would be 1-9)
  • lstTraversedNumbers: list of traversed numbers (once a item is traversed, it is moved to this list)
  • list of duplicate numbers (all the duplicate numbers will be added to this list)
  • duplicate number index (it will store the index of the duplicate number so that it can used to replace the non-existing number
  • It also return the cost of replacement

For debugging purpose, I have also added print statement to check the value of different list

def createMagicSquare(s):
    lstAvailableNumbers = list()
    lstAvailableNumbers=[1,2,3,4,5,6,7,8,9]   
    lstTraversedNumbers=set()
    lstDuplicateNumbers=list()
    dictDuplicateNumberIndex = dict()
    lstMissingNumbers=set()
    cost=0
    for item in range(len(s)):
        for colItem in range(len(s[item])):
            num= s[item][colItem]
            #print('Print traversed number - ' )
            #print(num)
            #print(lstTraversedNumbers)

            if(num in lstAvailableNumbers):
                #print('Inside if condition for  num in lstAvailableNumbers ' )
                lstAvailableNumbers.remove(num)
                #print(num)

                if(num in lstTraversedNumbers):
                    #print('Inside if condition for  num in lstTraversedNumbers ' )
                    #print(num)                    
                    lstDuplicateNumbers.append(num)
                    lstIndexPosition =[]
                    lstIndexPosition.append(item)
                    lstIndexPosition.append(colItem)                        
                    dictDuplicateNumberIndex[num]=lstIndexPosition
                lstTraversedNumbers.add(num)
                #print(lstTraversedNumbers)
            else:
                lstDuplicateNumbers.append(num)
                lstIndexPosition =[]
                lstIndexPosition.append(item)
                lstIndexPosition.append(colItem)                        
                dictDuplicateNumberIndex[num]=lstIndexPosition

    i=0
    #print("Available Numbers -")
    #print(lstAvailableNumbers)
    #print("Traversed Numbers -")
    #print(lstTraversedNumbers)
    #print("Duplicate Numbers -")
    #print(lstDuplicateNumbers)
    #print("Duplicate Number index -")
    #print(dictDuplicateNumberIndex)

    for item in lstAvailableNumbers:        
        itemToReplace= lstDuplicateNumbers[i]
        value= dictDuplicateNumberIndex[itemToReplace]
        s[value[0]][value[1]] = item
        i+=1
        cost += abs(itemToReplace - item)
        #print(cost)    
    return cost 
Nilesh Kumar
  • 287
  • 2
  • 4