Algorithm/파이썬 풀이

#7_ Forming a Magic Square by python(파이썬으로 마방진 만들기)

Tigercow.Door 2018. 4. 16. 20:39

안녕하세요. 문범우입니다.

오랜만에 재미난 알고리즘 문제를 풀게되어 포스팅하게 되었습니다.

문제의 출처는 HackerRank 이며 아래의 주소입니다.


https://www.hackerrank.com/challenges/magic-square-forming/problem


문제설명에 대한 전문은 위의 링크에서 확인하실 수 있으며, 제 github에서도 확인하실 수 있습니다.

https://github.com/doorBW/hacker_rank_algorithm_practice


문제에 대해서 간단히 설명드리면 아래와 같습니다.


- Forming a Magic Square


먼저, Magic Square는 마방진이라고 이야기하겠고, 마방진에 대한 규칙은 다음과 같습니다.

3*3 형태를 가지는 마방진 행렬에서는 1부터 9까지 9개의 숫자를 이용하며, 모든 열과 행, 대각선의 합이 같아야합니다.


그럼 문제에 대해서 알아봅니다. 문제에서 input 값으로 3*3 행렬이 들어옵니다.

근데 이때 들어오는 행렬은 마방진의 규칙을 만족할 수도 만족하지 않을 수도 있습니다. 이러한 행렬을 마방진의 규칙에 만족시키기 위해서는 특정 숫자의 값을 바꿔야 하겠죠? 

즉, 아래와 같은 행렬이 input으로 들어온다고 생각해봅시다.


4 9 2

3 5 7

8 1 5


위의 행렬은 5가 두번쓰였을 뿐아니라, 세번째 열, 세번째 행, 왼쪽위에서 시작하는 대각선의 합이 14입니다.

이때 오른쪽 아래의 숫자 5를 6으로 바꿔주면 마방진의 규칙을 만족하게 됩니다.

이렇게 우리가 한 행동은 5 -> 6 으로 바꿔주는 것인데, 이때 1만큼의 변경을 cost 1로 계산합니다.

예를 들어 우리가 마방진을 만들기 위해 특정 위치에 있는 숫자들에 대하여 다음과 같은 변화를 주었다면,


2 -> 3, 7 -> 4


이때는 cost가 총 4가 되는 것입니다.

하지만 마방진은 단 하나만 존재하는 것이 아닐 것입니다.

이렇게 입력으로 주어진 행렬을 마방진으로 만들때 발생하는 최소 cost를 구하는 것이 문제입니다.



- Solving


먼저 전체적인 코드는 하단에 작성해두었습니다.

코드에서 주석을 통해 간단하게 나마 설명을 써두었지만, 조금 더 자세히 이야기해보려고 합니다.


처음 문제를 접근했을때, 문제를 어떻게 해결할 것인가 생각해보았습니다.

주어진 행렬에 대해서 특정 위치의 값을 변화시키며 그 행렬이 마방진의 규칙을 만족하게끔 하는 방법이 떠올랐으나, 이는 경우의 수가 너무 많았습니다.

그리고 어떤 위치의 숫자를 변화시켜야 할지 기준을 잡는 것도 너무 어렵다고 느껴졌습니다.


그리고 생각했던 방법은, 마방진 규칙을 만족하는 모든 행렬을 찾아내고, 입력받은 행렬과 하나씩 비교해가며 그 중 최소의 cost를 찾아내는 방법이었습니다.

물론 이 방법에서는 과연 마방진 규칙을 만족하는 모든 행렬이 몇개나 될까가 미지수였지만 그래도 처음에 생각했던 방법보다는 경우의 수가 훨씬 적을 것 같아서 해당 방법으로 풀이를 시작했습니다.


그리고 문제에서 주어진 것보다 더 많은 내용을 참고하기 위해 magic square, 마방진이라는 개념에 대해서 찾아보았습니다. 자세히 검색해본 것은 아니지만 대체로 마방진을 만드는 몇가지 원리에 대해서 이야기를 많이 하고 계셨습니다.

하지만 우리에게 실질적으로 필요했던건, 마방진의 원리 그 자체를 알아서 3*3에서 존재할 수 있는 모든 마방진이 필요했습니다.


따라서 직접 그 원리를 얕게나마 확인해보는 과정이 필요했습니다.


기본적으로 3*3 마방진에서 하나의 행 또는 열 또는 대각선의 합은 무조건 15입니다.(이를 증명하는 것은 쉬우니 스스로 해보시길 바랍니다.)

따라서 저는 먼저 1~9까지의 숫자들중 중복이 없는 3개의 숫자의 합이 15를 이루는 그룹을 만들었습니다. 예를 들면 3,5,7 과 같은 그룹입니다.

각 숫자의 순서와 상관없이 만들어 보았을때 이러한 그룹은 총 8개가 만들어졌습니다.


그리고 아래와 같은 형태의 행렬을 생각해보았습니다.


a  b  c

d  e  f

g  h  i


위의 행렬을 통해 8개의 식을 세울 수 있습니다.


a + b + c = 15

d + e + f = 15

g + h + i = 15

a + d + g = 15

b + e + h = 15

c + f + i = 15

a + e + i = 15

c + e + g = 15


그런데 이렇게 식을 세워보니 새로운 사실을 알 수 있었습니다.

위의 식에서 각 알파벳이 사용되는 횟수가 달랐습니다.

예를 들어 한가운데 존재하는 e 같은 경우 혼자서만 4번 사용되었고, 4개의 알파벳은 3번, 4개의 알파벳은 2번 사용되었습니다.

그리고 위의 식들은 숫자 위치만 적절히 조정하면 우리가 위에서 만든, 합이 15인 8개의 그룹입니다.


이러한 사실들을 통해 저는 위에서 만들었던 8개의 그룹을 바탕으로 각 숫자가 몇번 사용되었는지 카운팅 해본결과, 4번 사용되는 것은 숫자 5, 3번 사용되는 것은 2,4,6,8 이고 2번 사용되는 것은 1,3,7,9 이었습니다.

즉, 위에서 만든 알파벳 행렬이 마방진 규칙을 만족한다고 가정한다면 사실상 알파벳 e는 무조건 5의 값을 가질 것이며, 3번 사용되는 a,c,g,i는 2,4,6,8과 대응되고 나머지 알파벳은 1,3,7,9와 대응될 것입니다.


그렇다면, 우리가 만든 8개의 그룹을 한번 더 나누어 볼 수 있습니다.

숫자 5가 포함된 것은 무조건 2행에서만 사용될 수 있기 때문입니다.

따라서 저는 첫번째 행과 세번째 행에 사용될 수 있는 그룹과, 두번째 행에 사용될 수 있는 그룹을 만들었습니다.

추후 실제로 이 그룹들을 바탕으로 마방진 규칙을 만족하는 행렬을 만들 것이기에 이번에는 숫자들의 순서(위치)등을 고려하여 모든 경우의 수를 다루었습니다.

그리고 아래의 추가적인 조건을 활용하였습니다.


- 숫자 5가 포함된 그룹에서 나머지 두 숫자는 모두 1,3,7,9 중의 숫자이다.(2번 사용되는 숫자들)


이를 통해 second_lines 와 other_lines 라는 두개의 그룹을 만들었으며, 이제 이러한 두 그룹을 이용해서 행렬을 만들었습니다.

그리고 행렬을 만들면서 마방진의 규칙을 추가하여 최종적으로 마방진 규칙을 만족하는 행렬은 magic_square 이라는 리스트에 추가하였습니다.


이러한 과정을 통해 마방진 규칙을 만족하는 모든 행렬은 총 8개가 되었습니다.

걱정했던 것과는 달리 적은 개수이기에 최종적으로 cost를 구하기 위한 과정을 가졌습니다.

입력받은 행렬을 8개의 행렬에 대해 각 위치별 숫자의 차이값을 구해서 최소의 cost를 구하여 반환하였습니다.


위의 과정을 통해 작성한 코드는 HackerRank에서 100% 통과를 받았습니다.


전체 코드는 아래와 같습니다.



코드가 완성도 있거나, 완벽하다고는 생각하지 않습니다.

코드에 대한 피드백은 언제든지 환영이며, 추가적으로 궁금하신 점은 댓글이나 이메일을 이용해주시면 감사하겠습니다.

728x90