[Python] 미로 저장 (배열, 리스트)

Sony·2022년 7월 25일
1

📚 자료구조

목록 보기
1/6

📌 What to do?

다음과 같은 미로가 maze1.txt라는 텍스트 파일에 작성되어있다.
이때 해당 파일을 읽어 미로를 화면에 표시하는 프로그램을 작성한다.


⚡️ 프로그램의구성

  • 파일에서 데이터를 읽는다.
  • 읽은 미로 데이터를 어디엔가 저장한다.
  • 미로 데이터를 출력함수를 이용하여 화면에표시한다.

다만 동시에 아래의 조건들도 만족시켜야한다.

⚡️ 조건

  • maze1.txt 파일의 크기는 531 바이트인데, 이 파일의 데이터(미로)를 저장하는 공간의 크기를 최소화 해야한다.
  • 즉 완성된 프로그램이 미로를 저장하는 형태는 원본 파일과 다를 수 있다.
  • 하지만 출력시엔 파일에 저장된것과 같은 형태로 화면에 표시해야한다.
  • 단, 파일에서 그대로 읽어서 그대로 표시하는 것은 “공간의 크기를 최소화”하는 조건에 부합하지 않는다.

그럼 이제 내가 작성한 프로그램을 설명해보겠다. 📍

📌 미로를 어떻게 저장할 것인가?


f = open('maze2.txt', 'r') 	# 파일 읽기 모드로 열기

line = f.readline()             # 첫 줄(ex. 9X12, 10X10)은 생략
width = int(line.split()[0])    # 미로의 가로(너비)값 저장
height = int(line.split()[1])   # 미로의 세로(높이)값 저장

우선 미로를 담고 있는 파일을 읽기 모드로 열어준 뒤, 미로의 첫번째 줄(ex. “9x12”, “10x10”)은 생략해준다. (굳이 필요 없으므로)
그리고 미로의 가로(너비)와 세로(높이)를 구하여 각각 widthheight 변수에 넣어준다.

maze = []     # 미로의 정보를 담을 리스트
line_Num = 0 # 행 번호

while True:
    disk = []
    line = f.readline().strip()   
    line_Num += 1
    if not line: break

    # 홀수 행일 경우
    # 각 행의 짝수번째 요소들만 남겨준다
    if line_Num % 2 == 1:
        for i in range(len(line))[1::2]:
            disk.append(line[i])
        disk = ("".join(disk))

    # 짝수 행일 경우
    # 각 행의 홀수번째 요소들만 남겨준다
    else:
        for i in range(len(line))[0::2]:
            disk.append(line[i])
        disk = ("".join(disk))

다음으론 미로의 데이터를 가공하여 담아 줄 리스트 maze를 선언해준다.
그 후 텍스트 파일을 한 줄씩 가져와 line에 저장한다. 이때, line_Num을 통해 각 행의 번호를 파악하고,
홀수 행일 경우에는 각 행의 짝수 번째 요소들만 남겨준다.
이렇게 하는 이유는 홀수 행의 홀수 번째 요소들은 모두 +, -, | 문자들로 이루어져 있으므로,
해당 위치가 공백이 아니라 문자로 이루어져 있다는 사실만 인지하고 있으면 되기 때문이다.
(ex. "+---+---+" -> "----")

비슷한 이유로 짝수 행일 경우, 각 행의 홀수 번째 요소들만 남겨주는데,
짝수 행의 짝수 번째 요소들은 모두 공백 문자이기 때문에 굳이 저장할 필요가 없다.
(ex. "| | | |" -> "| | | |")

str = disk.replace('-', '1')    # 각 줄의 '-', '|'기호를 '1'로 변환
str1 = str.replace('|', '1')
str2 = str1.replace(' ', '0')   # 각 줄의 공백 문자를 '0'으로 변환

이제 이렇게 가공된 행들의 -, | 문자는 숫자 1로, 공백 문자는 숫자 0으로 치환하여 각 행을 이진수 형태로 변환해준다.

dec_Num = int(str2, 2)   # 각 이진수를 10진수로 변환한 값을 저장할 변수
maze.append(dec_Num)     # 변환된 10진수를 maze에 저장

위의 과정들을 거쳐 미로 파일의 각 행(문자열)들은 하나의 이진수로 변환되었는데,
이렇게 얻어진 이진수들을 10진수 dec_Num으로 변환한 후 maze에 차례로 넣어준다.

이때 주어진 미로의 규격이 9x12이므로 각 행의 이진수들은 모두 9~10자리수 이므로 9~11비트 범위 내이다.
이는 바이트로 환산해보아도 2바이트가 채 되지 않아 short범위 내에 속한다.

즉 위의 과정들을 거쳐 각 행을 2바이트 이내의 용량으로 저장할 수 있게 된 것이고
이는 기존의 저장 방식(각 행의 용량: 19바이트)에 비해 훨씬 적은 용량으로 미로의 데이터를 저장할 수 있게 되었음을 의미한다.

미로의 저장 공간의 크기는 최대 50바이트가 될 것이다. (2바이트(short의 크기) * 25(총 행의 개수) = 50)


📌 미로를 어떻게 출력할 것인가?


# maze에 저장된 데이터를 바탕으로 미로를 다시 출력해 줄 함수
def draw_maze(maze, width, height):     
    output = []             # output list 생성
    row_C = width * 2 + 1   # 미로의 실제 가로 길이(너비)(텍스트 파일 기준)
    col_C = height * 2 + 1  # 미로의 실제 세로 길이(높이)(텍스트 파일 기준)

    for i in range(len(maze)):
        # maze에 저장되어있던 각 10진수들을 다시 2진수로 변환
        bi_Num = list(bin(maze[i])[2:].zfill(width))

우선 maze에 저장된 데이터를 바탕으로 미로를 다시 출력해 줄 함수 draw_maze를 선언하고,
maze에 저장되어 있던 각 10진수들을 다시 2진수 bi_Num으로 변환해준다.

if i % 2 == 0:
    bi_Num = "1".join(bi_Num)
    full_Bi = list(bi_Num)
    full_Bi.insert(0, '1')
    full_Bi.append('1')

이때 홀수 행의 경우, 생략했던 홀수 번째 요소들(1)을 복구시켜 full_Bi에 담아준다.

else:
    bi_Num = "0".join(bi_Num)
    full_Bi = list(bi_Num)

짝수 행의 경우, 생략했던 짝수 번째 요소들(0)을 복구시켜 full_Bi에 담아준다.
위의 과정들을 거쳐 maze에 가공된 형태로 저장되어 있던 데이터는 원래 미로의 너비와 같은 길이의 이진수로 변환된다.

output.append(full_Bi)

그리고 이렇게 복구된 이진수들을 output이라는 2차원 리스트에 차례로 담아준다.

이때 변환된 이진수들을 (ex. 1000101001) 1차원 리스트(ex. [ '1000101001' , '1010001010' ] ) 형태로 저장하지 않고
2차원 리스트(ex. [ [1,0,0,0,1,0,1,0,0,1] , [1,0,1,0,0,0,1,0,1,0] ] ) 형태로 저장하는 이유는,
한 줄의 데이터에서 짝수 번째 요소와 홀수 번째 요소를 나누어서 데이터를 변환해야 하는데, 2차원 리스트가 이에 용이하기 때문이다.

이제 출력할 차례이다.
이진수 형태로 저장된 미로를 다시 실제 미로처럼 바꾸는 과정은 다음과 같다.


1. 미로의 가장자리 벽 4변을 실제 미로 모양으로 바꾼다.

# 위쪽 가장자리 벽 정리
for j in range(row_C):
    if output[1][j] == '1':
        del output[0][j]
        output[0].insert(j, '+')
    else:
        del output[0][j]
        output[0].insert(j, '-')

    # 아래쪽 가장자리 벽 정리
    if output[-2][j] == '1':
        del output[-1][j]
        output[-1].insert(j, '+')
    else:
        del output[-1][j]
        output[-1].insert(j, '-')
  • 이때 맨 위쪽과 맨 아래쪽 벽은 -, + 로만 구성되어 있다.
  • 이 중 맨 위의 다음 줄, 그리고 맨 아래의 이전 줄에 1의 값이 저장된 경우엔 + 로 변환한다.
  • 그 외의 경우엔 모두 - 로 변환한다.

# 왼쪽 가장자리 벽 정리
for i in range(col_C)[1:-1]:
    if output[i][1] == '1':
        del output[i][0]
        output[i].insert(0, '+')
    else:
        del output[i][0]
        output[i].insert(0, '|')

    # 오른쪽 가장자리 벽 정리
    if output[i][-2] == '1':
        del output[i][-1]
        output[i].append('+')
    else:
        del output[i][-1]
        output[i].append('|')
  • 맨 왼쪽과 맨 오른쪽의 가벽의 경우는 | , + 로만 구성되어 있다.
  • 이 중 가장 왼쪽의 오른쪽 열, 그리고 가장 오른쪽의 왼쪽열에 ‘1’의 값이 저장된 경우엔 + 로 변환한다.
  • 그 외의 경우엔 - 로 변환한다.

2. 가장자리 벽 안쪽의 데이터들을 실제 미로의 모양으로 바꾼다. ⚡️ 이때 짝수 행과 홀수 행을 나눠서 변환을 진행한다.
```python # 짝수행 정리 for i in range(col_C)[1::2]: for j in range(row_C)[1:-1:]: if output[i][j] == '1': del output[i][j] output[i].insert(j, '|') else: del output[i][j] output[i].insert(j, ' ') ``` - 짝수 행: 실제 미로의 짝수 행은 | 문자와 공백문자로만 이루어져 있다. - 따라서 output에 저장된 짝수번째 데이터의 1은 | 문자로, 0은 공백문자로 변환한다.
# 홀수행 정리
# 홀수행 중 짝수열 값 정리
for i in range(col_C)[2:-1:2]:
    for j in range(row_C)[1::2]:                                    
        if output[i][j] == '1':
            del output[i][j]
            output[i].insert(j, '-')
        else:
            del output[i][j]
            output[i].insert(j, ' ')
# 홀수행 중 홀수열 값 정리
    for j in range(row_C)[2:-1:2]:
        if output[i][j-1] == '-' and output[i][j+1] == '-' \
            and output[i+1][j] == ' ' and output[i-1][j] == ' ':    
            del output[i][j]
            output[i].insert(j, '-')
        elif output[i-1][j] == '|' and output[i+1][j] == '|' \
            and output[i][j-1] == ' ' and output[i][j+1] == ' ':    
            del output[i][j]
            output[i].insert(j, '|')
        else:                                                       
            del output[i][j]
            output[i].insert(j, '+')
  • 홀수 행: 실제 미로의 홀수 행은 +, -, | , 공백, 총 4가지 요소로 이루어져 있다.
  • 이는 다시 짝수 열과 홀수 열로 나누어서 살펴본다.
  • 짝수 열의 경우 공백문자와 - 문자로만 이루어져 있다.
  • 따라서 output에 저장된 홀수번째 데이터의 짝수번째 값이 1이면 - 문자로, 0이면 공백문자로 변환한다.
  • 홀수 열의 경우 공백문자를 제외한 모든 요소들로 구성된다.
  • 따라서 output에 저장된 홀수번째 데이터의 홀수번째 값들은 모두 1이다.
  • 이 1들도 다음과 같은 규칙에 의해서 +, - , | 으로 변환된다.
    1) 홀수 행의 홀수 열을 기준으로 왼쪽과 오른쪽 열에 - 인 벽이 존재하고, 위쪽과 아래쪽 행에 공백으로 벽이 없을 경우: - 문자
    2) 홀수 행의 홀수 열을 기준으로 왼쪽과 오른쪽 열에 공백으로 벽이 없고, 위쪽과 아래쪽 행에 | 인 벽이 존재할 경우: | 문자
    3) 홀수 행의 홀수 열이 - 또는 | 로 변환되는 기준에 해당하지 않는 모든 경우: + 문자

3. 실제 미로의 모양으로 변환된 데이터를 출력한다.
# 미로 출력
for i in output:
    print("".join(i))

마지막으로 이중리스트 형태로 저장된 output을 join메소드를 통해서 하나의 문자열로 통합한 후 출력해준다.

위와 같은 방법으로 10진수로 저장되어 있던 데이터를 다시 원래 미로의 형태로 출력할 수 있었다.


📌 실행결과

📌 전체코드

f = open('maze1.txt', 'r') # 파일 읽기 모드로 열기

line = f.readline()             # 첫 줄(ex. 9X12, 10X10)은 생략
width = int(line.split()[0])    # 미로의 가로(너비)값 저장
height = int(line.split()[1])   # 미로의 세로(높이)값 저장

maze = []   # 미로의 정보를 담을 리스트
line_Num = 0 # 행 번호

while True:
    disk = []
    line = f.readline().strip()   # 2번째 줄 부터 끝까지 파일의 정보를 한 줄씩 가져 온다
    line_Num += 1
    if not line: break

    # 홀수 행일 경우
    # 각 행의 짝수번째 요소들만 남겨준다
    # why? -> 홀수 행의 홀수번째 요소들은 모두 '+'혹은 '-'문자로 이루어져 있기 때문에, 해당 위치에 문자가 존재 한다는 사실만 알고 있으면 되기 때문
    # ex. "+---+---+" -> "----"
    if line_Num % 2 == 1:
        for i in range(len(line))[1::2]:
            disk.append(line[i])
        disk = ("".join(disk))

    # 짝수 행일 경우
    # 각 행의 홀수번째 요소들만 남겨준다
    # why? -> 짝수 행의 짝수번째 요소들은 모두 공백 문자이기 때문에 굳이 저장할 필요가 없다.
    # ex. "|   |   |   |" -> "| | | |"
    else:
        for i in range(len(line))[0::2]:
            disk.append(line[i])
        disk = ("".join(disk))

    str = disk.replace('-', '1')    # 각 줄의 '-', '|'기호를 '1'로 변환
    str1 = str.replace('|', '1')
    str2 = str1.replace(' ', '0')   # 각 줄의 공백 문자를 '0'으로 변환

#==================== 위의 과정을 거쳐 각 줄(문자열)은 이진수로 변환된다. =======================#

    dec_Num = int(str2, 2)   # 각 이진수를 10진수로 변환한 값을 저장할 변수
    maze.append(dec_Num)     # 변환된 10진수를 maze에 저장
f.close()
#============================ maze에 미로 데이터 입력 완료 =================================#

def draw_maze(maze, width, height):     # maze에 저장된 데이터를 바탕으로 미로를 다시 출력해 줄 함수
    output = []             # output list 생성
    row_C = width * 2 + 1   # 미로의 실제 가로 길이(너비)(텍스트 파일 기준)
    col_C = height * 2 + 1  # 미로의 실제 세로 길이(높이)(텍스트 파일 기준)

    for i in range(len(maze)):
        # 1. maze에 저장되어있던 각 10진수들을 다시 2진수로 변환
        bi_Num = list(bin(maze[i])[2:].zfill(width))

        # 2. 홀수행의 경우, 홀수번째 요소들을 복구시킨다.
        if i % 2 == 0:
            bi_Num = "1".join(bi_Num)
            full_Bi = list(bi_Num)
            full_Bi.insert(0, '1')
            full_Bi.append('1')

        # 3. 짝수행의 경우, 짝수번째 요소들을 복구시킨다.
        else:
            bi_Num = "0".join(bi_Num)
            full_Bi = list(bi_Num)
        # 즉, 위의 과정들을 거쳐 maze에 가공되어 저장되어 있던 데이터들은 원래 미로의 너비와 같은 길이의 이진수의 형태로 변환된다.

        # 4. 복구된 리스트들을 output이라는 2중 리스트에 담아준다.
        output.append(full_Bi)

    # 미로의 첫번째 줄 정리
    for j in range(row_C):
        if output[1][j] == '1':
            del output[0][j]
            output[0].insert(j, '+')
        else:
            del output[0][j]
            output[0].insert(j, '-')

        # 미로의 마지막 줄 정리
        if output[-2][j] == '1':
            del output[-1][j]
            output[-1].insert(j, '+')
        else:
            del output[-1][j]
            output[-1].insert(j, '-')

    # 왼쪽 가벽 정리
    for i in range(col_C)[1:-1]:
        if output[i][1] == '1':
            del output[i][0]
            output[i].insert(0, '+')
        else:
            del output[i][0]
            output[i].insert(0, '|')

        # 오른쪽 가벽 정리
        if output[i][-2] == '1':
            del output[i][-1]
            output[i].append('+')
        else:
            del output[i][-1]
            output[i].append('|')

    #=========== 가벽 안의 값들 정리 =============#
    # 짝수 행 정리
    for i in range(col_C)[1::2]:
        for j in range(row_C)[1:-1:]:
            if output[i][j] == '1':
                del output[i][j]
                output[i].insert(j, '|')
            else:
                del output[i][j]
                output[i].insert(j, ' ')

    # 홀수 행 정리 (2가지 과정)
    # 홀수 행 중 짝수열 값 정리
    for i in range(col_C)[2:-1:2]:
        for j in range(row_C)[1::2]:                                    #홀수 행의 짝수 열이 '1'이면 '-' 아니면 공백
            if output[i][j] == '1':
                del output[i][j]
                output[i].insert(j, '-')
            else:
                del output[i][j]
                output[i].insert(j, ' ')
    # 남은 홀수 행 중 홀수열 값 정리
        for j in range(row_C)[2:-1:2]:
            if output[i][j-1] == '-' and output[i][j+1] == '-' \
                and output[i+1][j] == ' ' and output[i-1][j] == ' ':    #홀수 행의 홀수 열 중 '-'가 되는 조건
                del output[i][j]
                output[i].insert(j, '-')
            elif output[i-1][j] == '|' and output[i+1][j] == '|' \
                and output[i][j-1] == ' ' and output[i][j+1] == ' ':    #홀수 행의 홀수 열 중 '|'가 되는 조건
                del output[i][j]
                output[i].insert(j, '|')
            else:                                                       #나머지는 '+'로 표시
                del output[i][j]
                output[i].insert(j, '+')

    # 미로 출력
    for i in output:
        print("".join(i))

draw_maze(maze, width, height)          # 가공하여 저장했던 미로 다시 출력
profile
Bamboo Tree 🎋 : 대나무처럼 성장하고 싶은 개발자 Sony입니다.

1개의 댓글

comment-user-thumbnail
2022년 11월 28일

#Superplate #devSony

답글 달기