Python

[python] 달팽이 배열 풀기

콧등치기국수 2020. 11. 22. 16:31

요즘 퇴근하고 그리고 주말에 파이썬 알고리즘 문제를 풀고 있어요.

12월부터 앱을 하나 만들어볼까 하는데 그전에 알고리즘 문제는 어떤 건지 알고싶어서 하고 있는데

재미가 있어서 계속 하다보니 11월 안에는 알고리즘 기초책 한권을 다 끝낼 것 같아요!ㅎㅎ

(실력이 상승했는지는 답 못함^^;)

 

알고리즘에도 여러 유형이 있지만 오늘은 복습할 겸 달팽이 배열에 대해 쓰도록 하겠습니다!

책 '코딩강화파이썬'(저자 이규호 (2019)) p.237,238 참고하여 풀었습니다.

 

 

1. 문제

사용자에게 n을 입력받아 다음과 같이 달팽이 모양으로 자연수를 출력하는 프로그램을 작성하라. 

단, 재귀 호출은 사용하지 않는다.(힌트: 2차원 리스트 사용)

n 1 2 3 4
모양 1 1  2 
4  3
1  2  3
8  9  4
7  6  5
1    2    3   4
12  13  14   5
11  16  15   6
10   9   8    7

 

 

2. 달팽이 배열?

위 문제에서처럼 n×n 배열에 부터  까지의 자연수를 달팽이 집 모양으로 채우는 배열을

달팽이 배열이라고 합니다. 아래 그림을 참고하시면 더 쉽게 이해하실 수 있을 거에요

사진 출처:  https://codepractice.tistory.com/81  [코딩 연습]

 

3. 문제 풀이

1)

어떤 n을 입력하여도 달팽이배열이 될 수 있도록 하려면 공통된 규칙을 코드로 옮겨줘야 하는데요

처음에 규칙을 찾기가 쉽지가 않아요

저는 이 문제만 몇 시간 가량 혼자 끙끙대다가 풀었어요ㅋㅋㅋㅋ

머리 속으로 생각하시기 보다는 n=3, n=4, n=5 일때를 공책에 손으로 적어보시면서 찾는 걸 적극 추천합니다!

 

저는 아래 그림의 규칙을 이용해서 행렬좌표를 이용해 풀었습니다.

사진 출처: Javaking :: 자바킹 - IT정복 블로그

2) 행렬좌표를 이용한 규칙 찾기

n =4 일때를 예시로 각 회전마다

숫자가 적히는 행렬좌표 및 행렬의 변화, 적히는 숫자 수를 알아보겠습니다.

n = 4
1    2    3   4
12  13  14   5
11  16  15   6
10   9   8    7

표1: snail[행][열] , s, k, value : 아래 코드에서 쓴 변수입니다.

      아래 전체 코드에서도 쓰니까 유심히 봐주세요!!

회전 횟수 행렬좌표(snail[행][열]) 행렬 변화 (s) 적히는 숫자 수 (k) 숫자(value)
1회전 (0,0)  (0,1)  (0,2)  (0,3) 열 +1 4개 1,2,3,4
2회전 (1,3)  (2,3)  (3,3) 행 +1 3개 5,6,7
3회전 (3,2)  (3,1)  (3,2) 열 -1 3개 8,9,10
4회전 (2,0)  (1,0) 행 -1 2개 11,12
5회전 (1,1)  (1,2) 열 +1 2개 13,14
6회전 (2,2) 행 +1 1개 15
7회전 (2,1) 열 -1 1개 16

2-1) 적히는 숫자 수

n 은 한번, 그 후로는 1씩 감소하여 2번씩 반복하다가 1이 2번 적힌 후 끝나는 걸 볼 수있습니다.

 

4 -> 3 -> 3-> 2-> 2-> 1-> 1

n -> n-1 -> n-1 -> n-2 -> n-2 -> n-3 -> n-3 ... ->1 -> 1

 

2-2) 행렬변화

행과 열이 교차하여 변하네요

1회전을 제외하면 행+1,열-1(2,3회전) / 행-1,열+1(4,5회전) / 행+1, 열-1(6,7회전) 이 반복됨을 볼 수 있습니다.

1회전을 추가하면 짝수가 아닌 홀수가 되므로 2회전부터 규칙으로 생각하였습니다.

그렇게 하면 2회전과 3회전을 쌍으로 묶어 규칙으로 볼 수 있으니까요!

 

 

3) 내가 푼 코드 전체!

def num_snail():
    n = int(input("n: "))                                  # 사용자 입력으로 n 받음
    snail = [[0 for j in range(n)] for i in range(n)]      # j(열) 가 먼저 변함
    value = 1                                              # 1 ~ n**2 까지 숫자
    i,j = 0, n-1                                           # i 행, j 열
    s = +1                                                 # s : 행렬 변화 (+1/-1)
    k = n-1                                                # 회전시 적히는 숫자 수(n,n-1,n-1,n-2,n-2...)
    for f in range(n):                                     # 1회전 숫자 채우기(좌->우  n번)
        snail[0][f] = value
        value += 1

    while k > 0:
        for _ in range(k):
            i = i+s
            snail[i][j] =value
            value += 1
        for _ in range(k):
            j = j-s
            snail[i][j] = value
            value += 1
        k -= 1
        s = s*(-1)

    print_snail(snail,n)

def print_snail(snail,n):
    for i in range(n):
        for j in range(n):
            print("%4d" %snail[i][j], end ="")
        print()

num_snail()

 

갑자기 코드가 등장해서 '아 갑자기 뭐야'이런 맘이 들면서 어려워 보이실 수도 있겠지만

전혀 어렵지 않으니 안심하세요!!

맨 처음부터 차근차근 자세히 뜯어볼 거니까 편하게 쭉 봐주세요:)

 

n = int(input("n: "))
snail = [[0 for j in range(n)] for i in range(n)]     # 2차원 리스트 사용

만약 n =4라면?
n: 4
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

input()의 결과값은 str이므로 int()를 이용해 정수형으로 바꿔줍니다.

n =4일때를 생각해보면 

snail = [ [0 for j in range(4)] for i in range(4)] 가 되겠죠

 

[0 for j in range(4)]

: range(4) 는 0,1,2,3이니 총 4번 반복을 하겠네요.

for문 앞에 0이 있으므로 0을 4번 채워달라가 되겠네요!

(다른 예: [ int(i) for i in lst_a ]는 lst_a의 모든 원소들을 int정수형으로 바꿔달라가 되겠죠?) 

그럼 j가 4번 반복하여 [0,0,0,0] 이 만들어지면

 

[ [ j ] for i in range(4) ]

이 작동합니다. 이제 보니 i도 4번 반복을 하는데 앞에서 j로 만들어진 리스트([0,0,0,0])로 4번 반복을 시켜주는거네요

 

그래서 결과적으로 

snail = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] 이 됩니다.

이차원 리스트를 이용해 행렬을 만들었어요

이렇게 만들어진 행렬에 순서대로 숫자를 채워넣을 겁니다.

 

 

  for f in range(n):                                     # 1회전 숫자 채우기(좌->우  n번)
        snail[0][f] = value
        value += 1

range(4) => 0,1,2,3 이렇게 반복이 되겠네요

처음에 value를 1로 지정해놓았던 것도 기억해주세요!

1~n**2까지 모든 수가 들어갈 수 있도록 value값은 반복 후 마다 +1을 해줍니다.

f 행렬좌표 value
0 snail[0][0] 1
1 snail[0][1] 2
2 snail[0][2] 3
3 snail[0][3] 4

앞에서 보신 것 같은 기분이 드시는가요?!

이 부분은 위에 표1 에서 정리해둔 1회전을 나타내는 코드였습니다ㅎㅎ

 

 

그래서 여기 아래부터는 2회전 후부터를 나타내줍니다.

 while k > 0:                             # n=4일때 k=3,3,2,2,1,1 이므로 k가 0이상일때까지만 반복
        for _ in range(k):
            i = i+s                       # 행 + 행렬변화 변수(s)
            snail[i][j] =value            # 행렬좌표에 숫자 넣기
            value += 1                    
        for _ in range(k):
            j = j-s                       # 열 + 행렬변화 변수(s)
            snail[i][j] = value
            value += 1
        k -= 1
        s = s*(-1)

 

while반복문에서 보시면 행렬변화변수(s)에 따라 행(i)이 변하는 반복문 1개, 열(j)이 변하는 반복문 1개가

세트로 되어있는걸 보실 수 있는데요

 

즉 2,3회전 / 4,5회전 / 6,7회전을 쌍으로 반복하여 문제를 풀 거에요

전체 코드를 들고오는 게 이해하기 좋을 것 같아 들고왔습니다.

def num_snail():
    n = int(input("n: "))                                  # 사용자 입력으로 n 받음
    snail = [[0 for j in range(n)] for i in range(n)]      # j(열) 가 먼저 변함
    value = 1                                              # 1 ~ n**2 까지 숫자
    i,j = 0, n-1                                           # i 행, j 열
    s = +1                                                 # s : 행렬 변화 (+1/-1)
    k = n-1                                                # 회전시 적히는 숫자 수(n,n-1,n-1,n-2,n-2...)
    for f in range(n):                                     # 1회전 숫자 채우기(좌->우  n번)
        snail[0][f] = value
        value += 1

    while k > 0:
        for _ in range(k):
            i = i+s
            snail[i][j] =value
            value += 1
        for _ in range(k):
            j = j-s
            snail[i][j] = value
            value += 1
        k -= 1
        s = s*(-1)

먼저 n = 4일때를 생각해봅시다.

value = 1
i , j = 0, 3
s = 1
k = 3

으로 변수가 초기 세팅됩니다.

그리고 for f in range(4)를 이용해 1회전을 채운 후에는 value = 5가 되어있을 겁니다.

 

 

 

while반복문은 k>0일때까지만 반복하므로

k =3 , k =2 , k =1 이렇게 3번 반복하게 되겠네요

 

1) while반복문 : k = 3 일때

1-1) 2회전: 첫번째 for문 ( i = i+s ) /  for _ in range(k)

반복횟수 s i j value 행렬좌표
1 +1 1 3 5 snail [1][3]
2 +1 2 3 6 snail [2][3]
3 +1 3 3 7 snail [3][3]

1-2) 3회전: 두번째 for문 ( j= j-s ) for _ in range(k)

반복횟수 s i j value 행렬좌표
1 +1 3 2 8 snail [3][2]
2 +1 3 1 9 snail [3][1]
3 +1 3 0 10 snail [3][0]

 

두 개의 for문을 다 돌고 나면

k -= 1
s = s*(-1)

을 만나서 k = 2 , s = -1이 됩니다.

그 후 다시 while문으로 돌아가는데 k > 0 이므로 4,5회전(for문)을 하게 됩니다.

 

 

1) while반복문 : k = 2 일때

1-1) 4회전: 첫번째 for문 ( i = i+s ) /  for _ in range(k)

반복횟수 s i j value 행렬좌표
1 -1 2 0 11 snail [2][0]
2 -1 1 0 12 snail [1][0]

1-2) 5회전: 두번째 for문 ( j= j-s ) /  for _ in range(k)

반복횟수 s i j value 행렬좌표
1 -1 1 1 13 snail [1][1]
2 -1 1 2 14 snail [1][2]

 

 

1) while반복문 : k = 1 일때

1-1) 4회전: 첫번째 for문 ( i = i+s ) /  for _ in range(k)

반복횟수 s i j value 행렬좌표
1 1 2 2 15 snail [2][0]

1-2) 5회전: 두번째 for문 ( j= j-s ) /  for _ in range(k)

반복횟수 s i j value 행렬좌표
1 1 2 1 16 snail [2][1]

 

이렇게 달팽이 배열을 완성했습니다!

푸느라고 몇 시간을 썼는지 모르겠지만 스스로 풀어서 정말 정말 뿌듯했습니당

오랜만에 보니 제가 푼 코드임에도 '내가 이렇게 풀었나'생각했었지만 그래도 좋아요

 

담에 또 복습하면서 글 올리도록 할게요!

모두 화이팅하시길 바라요:)