[Python] dx dy 조건에 따른 방향 변환. 문자가 주어진 경우. 숫자 채우기

2024. 11. 6. 15:15파이썬(Python)

반응형

1. 2차원 배열 - 벽에 닿으면 반대 방향 전환

조건에 따라 방향을 반대로 전환하기 위해서는 dxs와 dys를 다음과 같이 설정한다.

dxs, dys = [0, 1, -1, 0], [1, 0, 0, -1] # 우, 하, 상, 좌

 

x, y가 현재 좌표라고 하면 x+dxs [i], y+dys [i]은 i가 0이면 오른쪽, 1이면 아래쪽, 2이면 위쪽, 3이면 왼쪽으로 이동한다.

이때 i가 0과 3 그리고 1과 2는 서로 반대 방향을 가진다. 

따라서 방향을 뒤집기 위해서는 3에서 방향 번호를 빼면 된다.

예를 들어 현재 방향이 위쪽이라고 하면 위쪽은 2이므로 위쪽의 반대 방향은 3-2=1, 즉 아래쪽이 된다.

 

def in_range(x,y):
    return x>=0 and x<n and y>=0 and y<n

n, m=map(int, input().split())  # n은 배열의 행과 열. m은 반복 횟수

arr = [[0]*n for _ in range(n)]
dxs, dys = [0, 1, -1, 0], [1, 0, 0, -1] # 우, 하, 상, 좌

x, y = map(int, input().split())
d = int(input())
print('='*20)
for i in range(m):
    nx, ny = x+dxs[d], y+dys[d]

    if not in_range(nx, ny):
        d = 3-d  # 방향 바꾸기
        nx, ny = x+dxs[d], y+dys[d]
    
    x, y = nx, ny
    print(x, y)

 

위 코드에서 n과 m을 입력해서 n*n의 2차원 배열을 만들고 m만큼 작업을 반복한다.

방향 배열인 dxs, dys를 정의한다.

현재 좌표인 x, y를 입력하고 방향인 d를 입력한다. (0이면 오른쪽, 1이면 아래쪽, 2이면 위쪽, 3이면 왼쪽)

이동한 좌표인 nx, ny는 in_range 함수를 배열 안에 있는 좌표인지 확인한다.

만약 배열 밖에 있다면 이동 전인 x, y는 배열 모서리 부분에 있는 것이므로 더 이상 현재 방향으로 갈 수 없다.

따라서 방향을 반대로 전환하여 전환 후 이동한 좌표를 nx, ny에 할당한다.

 

>> 3 5
>> 1 1
>> 1
====================
2 1
1 1
0 1
1 1
2 1

 

※ 방향을 문자로 주어진 경우

움직이는 방향이 'Right', 'Down', 'Up', 'Left'처럼 문자로 주어진다면 각각 방향 index를 0, 1, 2, 3으로 변환해 주어야 한다.

이때 딕셔너리 (dict)를 사용하면 편리하다. 딕셔너리를 사용하면 key-value 형태로 저장할 수 있는데 key로 string, int 등 다양한 type이 올 수 있다.

따라서 여기서는 key를 string, value를 int로 지정하여 사용한다. 아래 코드는 딕셔너리를 사용한 코드이다.

d_map = {'Right':0, 'Down':1, 'Left':2, 'Up':3}

 

문자를 사용하여 좌표를 이동하는 코드는 다음과 같다.

dxs, dys = [0, 1, 0, -1], [1, 0, -1, 0]
d_map = {'Right':0, 'Down':1, 'Left':2, 'Up':3}

curr_dir = 'Left'
move_dir = d_map[curr_dir]

x, y = map(int, input().split())
new_x, new_y = x + dxs[move_dir], y + dys[move_dir]

print(new_x, new_y)

 

방향 리스트인 dxs, dys를 지정하고 문자열과 index를 매핑한 딕셔너리 d_map을 정의한다.

현재 방향을 설정하면 현재 방향을 key 값으로 가지는 value를 move_dir에 저장한다.

현재 위치를 입력받아 move_dir에 해당하는 dxs, dys를 더해 새로운 위치인 new_x, new_y를 계산한다.

 

결과는 다음과 같다.

>> 2 3
2 2

 

Q) 공의 위치는?

n행 n열의 격자 안에 공이 놓여 있다. 공은 위(U), 아래(D), 오른쪽(R), 왼쪽(L) 방향 중 특정 방향으로 1칸 움직인다.

1칸 움직일 때마다 1초가 소요된다. 또한 공이 벽에 부딪히면 움직이는 방향이 반대로 뒤집힌다. 이때 방향을 바꾸는데 1초가 소요된다.

n과 t를 입력하여 격자의 크기와 시간을 지정하고 처음 위치와 방향이 주어졌을 때 t초가 지난 이후 공의 위치와 벽에 부딪힌 횟수를 구하시오.

(격자는 1행 1열 ~ n행 n열까지 있다.)

 

def in_range(x, y):
    return 0<x<=n and 0<y<=n

n, t = map(int, input().split()) # n: 격자 크기, t: 시간 (초)
curr_x, curr_y = map(int, input().split()) # 현재 위치 (x, y)
curr_dir = input() # 현재 방향

dir_map = {'R':0, 'D':1, 'U':2, 'L':3}
dxs, dys = [0, 1, -1, 0], [1, 0, 0, -1]  # 우, 하, 상, 좌

dir_index = dir_map[curr_dir]
cnt = 0
for _ in range(t):
    new_x, new_y = curr_x+dxs[dir_index], curr_y+dys[dir_index]
    if not in_range(new_x, new_y):
        dir_index = 3 - dir_index
        cnt += 1
    else:
        curr_x, curr_y = new_x, new_y

print(f'현재 위치는 ({curr_x}, {curr_y})')
print(f'벽에 부딪힌 횟수 {cnt}')

 

격자 크기 (n), 시간 (t), 현재 위치 (curr_x, curr_y), curr_dir을 입력받는다.

in_range 함수를 통해 위치가 격자 내에 있는지 확인한다. 이때 격자는 1행 1열부터 n행 n열까지 있다는 것을 생각한다.

dir_map에서 각 방향에 대한 번호를 부여한다. 번호를 기반으로 dxs, dys에서 좌표의 이동 변화를 알 수 있다.

dir_index는 현재 방향에 대한 번호를 반환한다.

 

t초 동안 공이 움직이면서 위치를 확인한다.

new_x, new_y는 현재 좌표에서 dxs, dys에 따른 새로운 좌표이다.

in_range(new_x, new_y)가 false이면 좌표가 격자를 벗어났다 즉 벽에 부딪히는 것이므로 방향을 반대로 바꾼다. 그리고 벽에 부딪혔으므로 cnt를 1 증가시킨다.

in_range(new_x, new_y)가 true이면 벽에 부딪히지 않았으므로 이동한 좌표를 현재 위치로 갱신한다.

 

※ 다른 풀이

def in_range(x, y):
    return 0<x<=n and 0<y<=n

n, t = map(int, input().split()) # n: 격자 크기, t: 시간 (초)
curr_x, curr_y = map(int, input().split()) # 현재 위치 (x, y)
curr_dir = input() # 현재 방향

dir_map = {'R': (0,1), 'D': (1,0), 'U':(-1,0), 'L':(0, -1)}
dx, dy = dir_map[curr_dir]
cnt = 0
for _ in range(t):
    new_x, new_y = curr_x+dx, curr_y+dy
    if not in_range(new_x, new_y):
        dx, dy = -dx, -dy
        cnt += 1
    else:
        curr_x, curr_y = new_x, new_y

print(f'현재 위치는 ({curr_x}, {curr_y})')
print(f'벽에 부딪힌 횟수 {cnt}')

 

위 풀이 방법에서는 dir_map에서 각 방향에 대해 바로 dx, dy에 할당한다.

예를 들어, 'R'은 0, 1로 저장되어 있어 dx, dy에 바로 0, 1에 할당한다.

 

반대 방향으로 전환하는 코드에서 dx, dy를 반대로 바꾼다. x, y를 반전시켜 방향을 바꾸는 방식을 사용했다.

 

2. 숫자 채우기

숫자 채우기

 

그림처럼 결과를 출력하기 위해서는 시계 방향 순서로 숫자를 채워야 한다.

방향은 오른쪽, 아래쪽, 왼쪽, 위쪽 다시 오른쪽, 아래쪽, 왼쪽, 위쪽.... 을 반복하게 된다.

또한 이동하려는 좌표가 격자를 벗어나거나 숫자가 채워져 있으면 90도 회전하여 이동을 해야 한다.

 

2-1. 방향 설정

방향의 번호 0~3은 각각 오른쪽, 아래쪽, 왼쪽, 위쪽으로 정의한다.

 

d = [(0,1), (1,0), (0, -1), (-1, 0)]

 

2-2. 방향 회전 조건

1. 격자를 벗어남.

이동 후 x, y가 격자 내에 판단하는 in_range 함수를 사용한다.

 

def in_range(x, y):
    return 0<=x<n and 0<=y<m

 

2. 이미 방문을 했는지

2차원 격자를 생성할 때 모든 원소를 0으로 만든다면 이동 후 (x, y)의 자리에 0이 아닌 값이라면 이미 방문을 했다고 판단할 수 있다.

 

따라서 두 가지 조건 중 하나를 충족하면 방향을 회전시킨다.

if not in_range(new_x, new_y) or arr[new_x][new_y]:
    dir_num = (dir_num+1)%4

 

전체 코드는 다음과 같다.

def in_range(x, y):
    return 0<=x<n and 0<=y<m

n, m = map(int, input().split())
arr = [[0]*m for _ in range(n)]

d = [(0,1), (1,0), (0, -1), (-1, 0)]
dir_num = 0
x, y = 0, 0
arr[x][y] = 1

for i in range(2, n*m+1):
    dx, dy = d[dir_num]
    new_x, new_y = x+dx, y+dy
    
    if not in_range(new_x, new_y) or arr[new_x][new_y]:
        dir_num = (dir_num+1)%4
        dx, dy = d[dir_num]
    
    x, y = x+dx, y+dy
    arr[x][y] = i

for row in arr:
    print(*row)

 

>> 4 3
1 2 3
10 11 4
9 12 5
8 7 6

 

2차원 격자의 행 크기 (n), 열 크기 (m)를 입력받는다. 모든 값을 0으로 초기화한다.

방향을 설정하고 현재 위치를 x, y로 지정한다. x, y의 첫 위치는 항상 (0, 0)에서 시작되고 1부터 시작된다.

방향(dir_num)은 오른쪽(0)부터 시작된다.

 

반복문을 통해 2부터 n*m까지 숫자를 격자에 채운다.

dir_num에 따라 x, y의 변화를 나타내는 dx, dy를 지정하고 다음으로 이동할 좌표를 구한다.

격자의 범위를 벗어나거나 이미 방문을 했다면 방향을 바꾼다. 

이동할 좌표를 갱신하고 해당 좌표에 숫자를 채운다.

 

Q) 바운스 볼

N*N 격자 안에 각 칸마다 벽이 있다. 벽은 '/' 또는 '\'이다. 첫 번째 행에 있는 N개의 칸에 각각 N개의 공을 떨어뜨려 각각의 공이 벽에 튕기는 횟수를 구하라.

예를 들어, N이 3이면 3개의 칸에서 3개의 공을 떨어뜨린다.

N=3 일 때 예시

 

코드 1

n = int(input())
arr = [list(input()) for _ in range(n)]

d = [(1, 0), (0, -1), (-1, 0), (0, 1)] # 아래 왼 위 오른

for i in range(n):
  x, y, d_num, cnt = 0, i, 0, 0
  curr_x, curr_y = x, y

  while 0 <= curr_x < n and 0 <= curr_y < n:
    if arr[x][y] == '\\':
      d_num = 3 - d_num

    else:
      d_num = d_num + 1 if d_num % 2 == 0 else d_num - 1
    
    dx, dy = d[d_num]
    curr_x, curr_y = curr_x + dx, curr_y + dy
    cnt += 1

  print(f'({x+1}, {y+1}) {cnt}')

 

>> 4
>> \\//
>> \\\/
>> /\\/
>> \///
(1, 1) 7
(1, 2) 2
(1, 3) 2
(1, 4) 5

 

코드 실행 예시

 

배열의 크기 n과 n*n 배열의 리스트를 입력한다.

이동 방향을 나타내는 리스트 d를 정의한다. (dx, dy) 형식으로 각 방향으로 이동할 때의 x, y 변화량을 나타낸다.

d의 요소는 각각 아래쪽, 왼쪽, 위쪽, 오른쪽으로 이동한다.

 

0번 열부터 n-1번 열까지 탐색을 반복한다. 각 열의 시작 좌표는 (0, i)이다.

d_num은 d의 index 번호이며 방향을 나타낸다. 초기 방향은 0이고 아래쪽으로 이동한다.

cnt는 튕기는 횟수이다.

 

현재 좌표가 배열 범위를 벗어나면 반복문이 종료되고 열의 시작 좌표와 공이 튕기는 횟수를 출력한다.

※ 방향 전환

1. 배열 값이 "\"일 때

\ 일 때 4가지 경우

 

d의 값은 각각 아래쪽, 왼쪽, 위쪽, 오른쪽을 나타낸다. 이들의 index는 0, 1, 2, 3이다.

1-1. 아래쪽으로 이동하면 오른쪽으로 이동한다. (0 -> 3)

1-2. 왼쪽으로 이동하면 위쪽으로 이동한다. (1 -> 2)

1-3. 위쪽으로 이동하면 왼쪽으로 이동한다. (2 -> 1)

1-4. 오른쪽으로 이동하면 아래쪽으로 이동한다. (3 -> 0)

 

따라서 index가 a -> b라고 한다면 4가지의 공통점은 a+b가 3이라는 것을 알 수 있고 b=3-a라고 할 수 있다.

"\"에서 방향 전환하는 코드는 다음과 같다.

d_num = 3 - d_num

 

"\"은 Escape String이 필요하므로 "\\"로 해야 올바르게 작동한다.

 

2. 배열 값이 "/"일 때

/ 일 때 4가지 경우의 수

 

2-1. 아래쪽으로 이동하면 왼쪽으로 이동한다. (0 -> 1)

2-2. 왼쪽으로 이동하면 아래쪽으로 이동한다. (1 -> 0)

2-3. 위쪽으로 이동하면 오른쪽으로 이동한다. (2 -> 3)

2-4. 오른쪽으로 이동하면 위쪽으로 이동한다. (3 -> 2)

 

따라서 index가 a -> b라고 한다면 a가 짝수이면 1 증가하여 b=a+1, 짝수이면 1 감소하여 b=a-1이다.

"/"에서 방향 전환하는 코드는 다음과 같다.

d_num = d_num + 1 if d_num % 2 == 0 else d_num - 1

 

d_num을 통해 방향에 따른 dx, dy를 결정하고 현재 좌표 (curr_x, curr_y)를 갱신한다.

이동할 때마다 공이 "/" 또는 "\"에 튕겨지므로 1씩 증가한다.

 

코드 2

n = int(input())
arr = [list(input()) for _ in range(n)]

d = [(1, 0), (0, -1), (-1, 0), (0, 1)] # 아래 왼 위 오른

for i in range(n):
  x, y, d_num, cnt = 0, i, 0, 0
  curr_x, curr_y = x, y

  while 0 <= curr_x < n and 0 <= curr_y < n:
    d_num = 3 - d_num if arr[curr_x][curr_y] == '\\' else d_num ^ 1
    dx, dy = d[d_num]
    curr_x, curr_y = curr_x + dx, curr_y + dy
    cnt += 1

  print(f'({x+1}, {y+1}) {cnt}')

 

변경한 코드 

d_num = 3 - d_num if arr[curr_x][curr_y] == '\\' else d_num ^ 1

 

배열 값이 "/"일 때 -> XOR(비트) 연산

XOR 연산은 같은 값이면 0 다른 값이면 1이 되는 연산이다.

 

xor 비트 연산

 

빙글빙글 알파벳

N * M 크기의 격자에 나선형으로 대문자 알파벳을 A부터 Z까지 시계방향 또는 시계 반대 방향으로 채우는 코드를 작성해라.

Z 다음은 다시 A부터 채운다.

N, M을 입력하고 0 또는 1을 입력한다. 0을 입력하면 시계 방향으로 알파벳을 채우고 1을 입력하면 시계 반대 방향으로 알파벳을 채운다.

 

def round_fun(num, clockwise, x, y, i):
  d_num = num
  for _ in range(n * m):
    arr[x][y] = chr(ord('A') + i % 26)
    dx, dy = d[d_num]
    i += 1

    if not (0 <= x + dx < n and 0 <= y + dy < m) or arr[x + dx][y + dy] != 0:
      d_num = (d_num + clockwise) % 4
      dx, dy = d[d_num]
    
    x, y = x + dx, y + dy

  for rows in arr:
    print(*rows)

n, m = map(int, input().split())
round_num = int(input())
arr = [[0] * m for _ in range(n)]
x, y, i = 0, 0, 0

d = [(0, 1), (1, 0), (0, -1), (-1, 0)]

if round_num == 0:
  round_fun(round_num, 1, x, y, i)

else:
  round_fun(round_num, 3, x, y, i)

 

>> 5 6
>> 0
A B C D E F
R S T U V G
Q B C D W H
P A Z Y X I
O N M L K J

 

>> 5 6
>> 1
A R Q P O N
B S B A Z M
C T C D Y L
D U V W X K
E F G H I J

 

배열의 크기인 n, m을 입력하고 나선형이 시계 방향(0)인지 반시계 방향(1)인지 결정하는 round_num을 입력한다.

n*m 크기의 arr을 초기화하고 초기 좌표인 (x, y)를 (0, 0)으로 알파벳 index i를 0으로 설정한다.

d는 4가지 방향을 나타내는데 오른쪽(0, 1), 아래쪽(1, 0), 왼쪽(0, -1), 위쪽(-1, 0)을 나타낸다.

 

나선형으로 배열을 채우는 round_fun 함수를 정의한다.

d_num은 현재 방향의 index이고 초기값은 num 즉, round_num을 통해 결정된다.

round_num이 0이면 시계 방향이므로 초기 방향은 오른쪽으로 향한다.

round_num이 1이면 반시계 방향이므로 초기 방향은 아래쪽으로 향한다.

 

배열의 크기인 n*m만큼 반복문을 실행한다. 알파벳은 A부터 Z까지 26개이다.

ord('A')는 'A'의 아스키코드 값이고 그 값을 chr() 함수를 사용하여 문자로 변환한다.

이때 i=0 이면 'A'를 출력하고 i=25이면 'Z'를 출력한다. i=26 이면 다시 'A'로 변환하기 위해 나머지 연산 (%)을 사용해서 다시 0으로 돌아가서 이 과정을 반복한다.

dx, dy는 현재 방향에 따른 x, y 증가량이고 반복문을 한 번 실행할 때마다 알파벳 index를 1 증가한다.

 

현재 방향으로 이동한 좌표가 배열의 범위를 벗어나거나 알파벳으로 채워져 있으면 방향을 바꾼다.

clockwise에 따라 방향을 전환하는데 시계 방향은 1, 반시계 방향은 3으로 설정한다.

d_num을 clockwise씩 증가시키고 나머지 연산을 사용하여 d 리스트의 범위를 벗어나지 않도록 하여 방향 전환을 한다.

방향을 확인 후 dx, dy를 설정하면 현재 좌표인 x, y를 dx, dy만큼 이동하여 설정한다.

 

입력한 round_num이 0이면 clockwise를 1, round_num이 1이면 clockwise를 3으로 설정하여 round_fun을 호출한다.

 

달팽이 숫자 채우기

n*n 크기의 배열의 정가운데에서 시작하여 시계방향 또는 반시계방향으로 회전하여 숫자를 채우려 한다.

n은 항상 홀수이다. n을 입력한 후 0 또는 1을 입력한다.

0은 반시계방향 1은 시계방향으로 회전한다.

 

def fill_out_numbers(number, n, step, x, y, first_d_num):
  d_num = first_d_num
  while number <= n * n:
    for _ in range(2):
      for _ in range(step):
        if number > n * n:
          break
        dx, dy = d[d_num]
        x, y = x + dx, y + dy
        arr[x][y] = number
        number += 1
      d_num = (d_num + first_d_num + 1) % 4
    step += 1

n = int(input())
arr = [[0] * n for _ in range(n)]
is_clockwise = int(input())  # 0 반시계, 1 시계

x, y = n // 2, n // 2
arr[x][y] = 1
d = [(0, 1), (-1, 0), (0, -1), (1, 0)] # 오른 위 왼 아래
step, number = 1, 2

fill_out_numbers(number, n, step, x, y, 2) if is_clockwise else fill_out_numbers(number, n, step, x, y, 0)
  
for row in arr:
  print(*row)

 

해설

 

접근 방법

n=5, 시계방향일 때 다음과 같다.

(2, 2)에서 초기값 1을 설정한다.

이후 회전을 하며 숫자를 채우는데 처음은 왼쪽 1번, 위쪽 1번, 오른쪽 2번, 아래쪽 2번이다. (1 ~ 7)... ⓐ

다음은 왼쪽 3번, 위쪽 3번, 오른쪽 4번, 아래쪽 4번이다. (7 ~ 21)... ⓑ

마지막은 왼쪽 4번으로 마무리한다. (21 ~ 25)... ⓒ

 

n=3, 시계방향일 때 ⓐ + 왼쪽 2번이다.

n=7, 시계방향일 때 ⓐ+ ⓑ+ 왼쪽 5번, 위쪽 5번, 오른쪽 6번, 아래쪽 6번+왼쪽 6번

 

따라서 n=m, 시계방향일 때 다음과 같은 식을 만들 수 있다. 

[ ∑ {왼쪽 (2k-1) 번, 위쪽 (2k-1) 번, 오른쪽 (2k) 번, 아래쪽 (2k) 번} k=1~m//2] + 왼쪽 m-1번

 

또한 반시계방향일 때

[ ∑ {오른쪽 (2k-1) 번, 위쪽 (2k-1) 번, 왼쪽 (2k) 번, 아래쪽 (2k) 번} k=1~m//2] + 오른쪽 m-1번

 

코드 해설

초기 위치는 n을 2로 나누어 배열을 x, y 변수에 할당한다.

배열의 x행 y열에 1을 설정한다.

방향은 오른쪽, 위쪽, 왼쪽, 아래쪽 순서로 (dx, dy) 형태로 설정한다.

step은 각 방향으로 이동할 거리이다. 초기값은 1이므로 1칸 이동한다.

number은 다음에 채울 숫자로 초기값 1은 채워졌으니 2부터 시작한다.

 

is_clockwise는 반시계 방향 (0)인지 시계 방향 (1)인지 설정하는 값이다.

시계 방향이면 first_d_num을 2로 설정하고 반시계 방향이면 first_d_num을 0으로 설정하여 함수를 호출한다.

 

시계 방향이면 왼쪽부터 반시계 방향이면 오른쪽으로 시작하므로 각각 first_d_num을 2, 0으로 설정한다.

숫자는 1부터 n*n까지 입력한다. 따라서 입력할 숫자인 number가 n*n까지 반복문을 실행한다.

숫자를 채우는 패턴을 보면 왼쪽 또는 오른쪽의 방향이 설정될 때 step이 1 증가한다.

따라서 방향 회전을 하며 2번 전환되면 step을 1 증가한다.

step 만큼 해당 방향으로 숫자를 채운다. 이때 숫자를 채우면서 위치를 업데이트하고 number를 1 증가한다.

한 step이 끝나면 방향 전환을 한다.

 

결과는 다음과 같다.

>> 5
>> 0
17 16 15 14 13
18 5 4 3 12
19 6 1 2 11
20 7 8 9 10
21 22 23 24 25

 

>> 5
>> 1
13 14 15 16 17
12 3 4 5 18
11 2 1 6 19
10 9 8 7 20
25 24 23 22 21