[Python] 특정 방향으로 이동. dx dy. 방향 회전. 2차원 배열 활용. in_range 함수

2024. 8. 10. 21:32파이썬(Python)

반응형

1. dx, dy를 이용하기

- 어떤 특정한 위치에서 숫자에 따라 한 칸 이동한다.

- 0은 위쪽, 1은 오른쪽, 2는 아래쪽, 3은 왼쪽이다.

 

num = int(input('숫자를 입력하세요: '))
pos_x, pos_y = 0, 0

if num == 0:
    pos_y += 1
elif num == 1:
    pos_x += 1
elif num == 2:
    pos_y -= 1
else:
    pos_x -= 1

print(f'이동한 후 위치는 {pos_x}, {pos_y}이다.')

 

숫자를 입력하세요: 0
이동한 후 위치는 0, 1이다.

숫자를 입력하세요: 1
이동한 후 위치는 1, 0이다.

숫자를 입력하세요: 2
이동한 후 위치는 0, -1이다.

숫자를 입력하세요: 3
이동한 후 위치는 -1, 0이다.

 

- 위 코드에서 비슷한 조건문이 반복되는 것을 알 수 있다.

- 특정 방향으로 이동하는 경우 좌, 우를 움직이는 dx, 상, 하를 움직이는 dy를 각각 리스트로 정의하여 사용할 수 있다.

- dx, dy 리스트를 정의하면 코드는 다음과 같다.

 

num = int(input('숫자를 입력하세요: '))
pos_x, pos_y = 0, 0
dx, dy = [0, 1, 0, -1], [1, 0, -1, 0]

pos_x, pos_y = pos_x+dx[num], pos_y+dy[num]

print(f'이동한 후 위치는 {pos_x}, {pos_y}이다.')

 

숫자를 입력하세요: 0
이동한 후 위치는 0, 1이다.

숫자를 입력하세요: 1
이동한 후 위치는 1, 0이다.

숫자를 입력하세요: 2
이동한 후 위치는 0, -1이다.

숫자를 입력하세요: 3
이동한 후 위치는 -1, 0이다.

 

 

Q) 방향에 따른 이동

- 이동 명령의 개수 n초기 위치 x, y를 입력한다.

- n번의 이동 방향과 이동 거리를 입력한다.

- 방향은 N, E, S, W이고 각각 위, 오른, 아래, 왼쪽을 의미한다.

- 예를 들어, N 3이면 위쪽으로 3칸 움직인다는 의미이다.

- n번의 이동 끝에 최종 위치를 출력하라.

 

n = int(input())
x, y = map(int, input().split())
dx, dy = [0, 1, 0, -1], [1, 0, -1, 0]
directions = {'N': 0, 'E': 1, 'S': 2, 'W': 3}

for _ in range(n):
    d, distance = input().split()
    distance = int(distance)
    x, y = x+dx[directions[d]]*distance, y+dy[directions[d]]*distance

print(x, y)

 

>> 5
>> 2 3
>> N 4
>> S 2
>> E 6
>> W 3
>> S 1
5 4

 

 

- x축 방향(좌, 우)의 변화량 dx, y축 방향(위, 아래)의 변화량 dy를 리스트로 나타낸다.

- 딕셔너리를 사용해 이동 방향을 나타내는 문자에 대응하는 숫자를 저장한다.

- 이동 방향에 따른 숫자가 dx, dy의 index가 되고, 방향을 결정하여 distance만큼 이동한다.

 

2. 방향 회전

- 예를 들어 dx, dy를 시계 방향으로 적으면 다음과 같다.

 

dx, dy = [0, 1, 0, -1], [1, 0, -1, 0]
d_num = 0
x, y = 0, 0

print(f'{x+dx[d_num]}, {y+dy[d_num]}')

d_num += 1
print(f'{x+dx[d_num]}, {y+dy[d_num]}')

d_num += 1
print(f'{x+dx[d_num]}, {y+dy[d_num]}')

d_num += 1
print(f'{x+dx[d_num]}, {y+dy[d_num]}')

 

0, 1
1, 0
0, -1
-1, 0

 

- 위치를 (0, 0)이라고 할 때, d_num이 증가하면서 각각 위쪽, 오른쪽, 아래쪽, 왼쪽 방향으로 이동할 수 있다.

- 즉, dx, dy에 방향 값을 시계 방향 순서로 정의하고 d_num을 증가하면 시계 방향으로 90도 회전을 할 수 있다.

- d_num이 3 (왼쪽) 일 때 4가 되면 index error가 발생하므로 0 (위쪽)이 돼야 한다.

- 따라서 시계 방향으로 90도 회전한 이후의 방향은 (d_num+1)%4이다.

 

- 만약 반시계 방향은 d_num에서 1을 빼주면 된다.

- 이때, d_num이 0 (위쪽) 일 때 반시계 방향으로 하면 3 (왼쪽)이 되어야 한다.

- 따라서 반시계 방향으로 90도 회전한 이후의 방향은 (d_num-1+4)%4이다.

 

Q) 방향에 따른 이동

- 초기 위치 (x, y)를 입력하고 방향 (N, E, S, W)을 입력한다.

- N, E, S, W은 각각 위쪽, 오른쪽, 아래쪽, 왼쪽을 의미한다.

- 명령을 문자열 형태로 입력한다.

- 명령은 L, R, F 중 하나이고 각각 반시계방향 회전, 시계방향 회전, 앞으로 직진을 의미한다.

- 명령을 수행한 후 최종 위치를 출력하라.

 

x, y = map(int, input('초기 위치: ').split())
curr_d = input('방향 입력: ')
ds = {'N': 0, 'E': 1, 'S': 2, 'W': 3}
d = ds[curr_d]
dx, dy = [0, 1, 0, -1], [1, 0, -1, 0]

inp_ds = input()
for val in inp_ds:
    if val == 'L':
        d = (d-1+4) % 4
    elif val == 'R':
        d = (d+1) % 4
    else:
        x, y = x+dx[d], y+dy[d]

print(x, y)

 

>> 초기 위치: 3 6
>> 방향 입력: W
>> FFLLFRRFLF
1 5

 

- 각 방향을 숫자에 매핑한 딕셔너리를 정의한다. (ds)

- 입력한 방향을 딕셔너리에서 찾아 숫자로 변환하여 d에 저장한다.

- dx, dy를 정의하여 위쪽, 오른쪽, 아래쪽, 왼쪽을 각각 0, 1, 2, 3으로 설정한다.

- 이동 명령인 inp_ds에 따라 L, R, F에 해당하는 명령을 실행한다.

- L은 반시계 방향으로 회전하고, R은 시계 방향으로 회전하여 d를 증가, 감소한다.

- F는 현재 방향으로 1만큼 이동하며, dx dy에 따라 x, y를 수정한다.

 

 

3. 격자, 2차원 배열에서 dx, dy 활용

- arr이라는 2차원 배열이 존재한다. arr [x][y]는 x행 y열에 있는 값이다.

- arr [x][y]에서 위쪽 값arr [x-1][y]이고 아래쪽 값은 arr [x+1][y]이다.

- 오른쪽 값은 arr [x][y+1]이고, 왼쪽 값은 arr [x][y-1]이다.

- 따라서, 위쪽, 오른쪽, 아래쪽, 왼쪽을 정의하는 dx, dy는 다음과 같다.

 

dx, dy = [-1, 0, 1, 0], [0, 1, 0, -1]

 

 

3-1. in_range 함수 설정

- 만약, (3, 3) 크기의 격자가 있다고 하자.

0 0 0
1 1 1
2 2 2

 

- 만약 3행 1열에 있는 2의 상, 하, 좌, 우의 값들을 알고 싶다.

- 하지만 아래쪽 방향에 있는 위치는 4행 1 열이고, 리스트의 index error가 발생한다.

- 왼쪽 방향에 있는 위치는 3행 0 열이고, 이중 리스트에서 2번째의 -1번째 index이므로 구하고자 하는 값이 아니다.

- 문제를 해결하기 위해 구하고자 하는 방향의 위치가 격자 내에 있는 위치인지 확인하는 함수를 설정한다.

 

def in_range(x, y):
    return x >= 0 and x < m and y >= 0 and y < n  # m은 행, n은 열

 

- 크기가 m행 n열이라고 하면 (x, y)가 격자 안에 들어오면 True, 격자를 벗어나면 False를 반환한다.

- in_range 함수를 확인하여 격자 범위 안에 들어오는지 확인해야 한다.

 

 

Q) 인접한 1의 개수

- n을 입력하여 n행 n열의 격자를 만든다.

- 격자는 0과 1만 존재한다.

- 각 칸에서 상, 하, 좌, 우를 선택하여 1의 개수가 1개~4개의 개수를 세어 출력하라.

 

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


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

dxs, dys = [-1, 0, 1, 0], [0, 1, 0, -1]
cnts = [0, 0, 0, 0, 0]

for x in range(n):
    for y in range(n):
        cnt = 0
        for dx, dy in zip(dxs, dys):
            nx, ny = x+dx, y+dy
            if in_range(nx, ny) and arr[nx][ny] == 1:
                cnt += 1
        cnts[cnt] += 1

for idx in range(1, 5):
    print(f'인접한 1이 {idx}개인 위치는 {cnts[idx]}')

 

>> 4
>> 0 1 0 1
>> 1 0 1 0
>> 1 1 0 1
>> 0 1 1 0
인접한 1이 1개인 위치는 2
인접한 1이 2개인 위치는 6
인접한 1이 3개인 위치는 2
인접한 1이 4개인 위치는 2

 

 

 

- 그림에서 보면 노란색 칸을 기준으로 상, 하, 좌, 우에 1이 있는 개수를 의미한다.

 

- 각각 x, y의 변화량을 나타내는 리스트를 정의한다. (dxs, dys)

- 칸 주변에 1의 개수에 대한 리스트를 정의한다. (cnts)

- cnts [idx]는 주변에 있는 1의 개수가 idx만큼 있는 칸의 개수를 의미한다.

- zip을 사용해서 dxs, dys의 값을 순서대로 각각 하나씩 뽑아 방향을 설정한다.

- in_range 함수를 설정해 격자 범위 내에 있는지 확인한다.

- 주변 위치로 이동한 좌표를 nx, ny에 넣어 in_range가 True이고 해당 값이 1이면 cnt를 1 증가한다.

- cnts의 index가 cnt인 값을 1 증가시킨다.

- 해당 개수에 대한 위치에 있는 1의 개수를 출력한다.

 

Q) 땅따먹기 게임

- n, m을 입력하고 m개의 x, y를 입력한다.

- n * n 격자 내에 m번의 x, y 위치에 돌을 놓는다.

- 이때, 돌을 놓은 땅을 기점으로 인접한 4개의 칸 모두 돌이 있다면 그 땅을 점령한다.

- 점령한 땅의 개수를 구하라.

 

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

n,m = map(int, input().split())
arr = [[0] * n for _ in range(n)]
dxs, dys = [-1, 0, 1, 0], [0, 1, 0, -1]
sum_cnt = 0
for _ in range(m):
    r, c = map(int, input().split())
    arr[r-1][c-1] = 1

    cnt = sum(1 for dx, dy in zip(dxs, dys) if in_range(r-1+dx, c-1+dy) and arr[r-1+dx][c-1+dy] == 1)
    
    if cnt == 4:
        sum_cnt += 1

print(f'점령한 땅의 개수는 {sum_cnt}')

 

>> 4 8
>> 1 2
>> 2 1
>> 2 3
>> 3 2
>> 2 2
>> 4 3
>> 3 4
>> 3 3
점령한 땅의 개수는 2

 

 

- n*n의 0이 원소인 배열 arr을 생성한다.

- x, y에 돌을 놓은 위치라고 표시하기 위해 arr [x-1][y-1]1이라 표시한다. (리스트에 저장하기 위해 각 위치에 1을 뺀 x-1, y-1에 값을 저장한다.)

- 현재 위치인 x, y를 기점으로 상하좌우에 1인 값을 확인한다. (dxs, dys 사용)

- 또한 in_range를 통해 상하좌우를 탐색하는 위치가 격자 내부에 있는지 확인한다.

- 네 방향 모두 1이면 x, y의 땅을 점령할 수 있으므로 점령한 땅의 개수 sum_cnt를 1 증가하여 답을 출력한다.

 

Q) 이동하며 숫자 계산

- n, m을 입력하여 n*n 격자의 정보를 주어지고 m개의 명령을 입력한다.

- x, y를 입력하여 첫 시작의 좌표를 입력한다. (x, y는 n 이하)

- 명령은 R, L, F 중 하나이다.

- R은 현재 방향에서 시계 방향으로 회전. L은 현재 방향에서 반시계 방향으로 회전한다.

- F는 현재 방향으로 한 칸 전진하며 숫자를 더하거나 뺀다.

- 첫 시작 방향은 위쪽이고 첫 번째 전진하게 되면 기존 위치의 숫자와 전진한 후 위치의 숫자를 더한다.

- F로 숫자를 더했다면 이후에 F가 나올 때에는 숫자를 뺀다.

- 마찬가지로 F로 숫자를 뺐다면 이후에 F가 나오면 숫자를 더한다.

- 이때 모든 숫자를 더하거나 뺀 값의 결과를 출력하라.

 

>> 3 8  # n, m
>> 2 2  # x, y
>> RFFFLFLF
>> 1 2 3
>> 4 5 6
>> 7 8 9
10

 

초기 위치인 (2, 2)인 5. 초기 방향은 위쪽.

 

 

 

- 노란색은 더하고 초록색은 뺀다.

- 따라서 5+6-3+2 = 10이다.

 

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

n, m = map(int,input().split())
x, y = map(int,input().split())
commands = input()
arr = [list(map(int, input().split())) for _ in range(n)]

dxs, dys = [-1, 0, 1, 0], [0, 1, 0, -1]
i=0
ans = arr[x-1][y-1]
x, y = x-1, y-1
isplus = True

for c in commands:
    if c == 'R':
        i=(i+1)%4
    elif c == 'L':
        i=(i+3)%4
    else:
        nx, ny = x+dxs[i], y+dys[i]
        if in_range(nx, ny):
            if isplus:
                ans += arr[nx][ny]
                isplus = False
            else:
                ans -= arr[nx][ny]
                isplus = True
            x, y = nx, ny
print(ans)

 

- dxs, dys를 사용해 위, 오른, 아래, 왼쪽을 나타내는 방향벡터를 설정한다.

- i는 현재 방향의 인덱스를 나타낸다.

- ans는 결괏값을 나타내고 초기값은 현재 (x, y)의 값을 나타내기 위해 x-1, y-1 인덱스의 arr 값을 저장한다.

- 리스트의 인덱스로 만들기 위해 x, y를 각각 1을 뺀다.

- isplus는 현재 더할지 뺄지 결정하는 변수이다. True이면 더하고 False이면 뺀다.

- 명령이 R이면 인덱스를 1 증가시켜 오른쪽으로 회전한다.

- 명령이 L이면 인덱스를 3 증가시켜 왼쪽으로 회전한다.

- 4로 나눈 나머지 연산으로 각각의 회전을 구현한다.

- F를 하면 현재 방향으로 이동한다.

- 이동 후 위치가 범위 내에 있는지 확인하고 범위 내에 있으면 값을 더하거나 뺀다.

- 더한 후에는 False로 설정하여 다음 작업에 빼기 연산으로 설정한다.

- 뺀 후에는 True로 설정하여 다음 작업에 더하기 연산으로 설정한다.

- 명령이 끝난 후에는 결괏값을 출력한다.