7:50 입실
어제 밤에 샤워하는데 화장실 벽에서 모기 발견
그 한 마리였음. 어제는 모기 없이 편하게 잠.
나오다가 습관적으로 방 키 뽑아들고 왔는데 룸메 키였음.
내려왔다가 다시 올라가서 꽂아놓고 옴.
오늘은 알고리즘 테스트..
자신은 없다.
시험 보기 전까지 '하' 문제 위주로 복습할 예정
깔끔하게 혼자 짜는 거 성공!
import sys
from collections import defaultdict
N = int(sys.stdin.readline().strip())
edges = []
for _ in range(N):
root, left, right = sys.stdin.readline().strip().split()
if left == '.':
left = None
if right == '.':
right = None
edges.append((root, left, right))
tree = defaultdict(list)
for edge in edges:
root, left, right = edge
tree[root] = ((left, right))
def dfs_preorder(tree, v, order=None, visited=None):
if order is None:
order = []
if visited is None:
visited = set()
if v:
left, right = tree[v]
order.append(v)
dfs_preorder(tree, left, order, visited)
dfs_preorder(tree, right, order, visited)
return order
def dfs_inorder(tree, v, order=None, visited=None):
if order is None:
order = []
if visited is None:
visited = set()
if v:
left, right = tree[v]
dfs_inorder(tree, left, order, visited)
order.append(v)
dfs_inorder(tree, right, order, visited)
return order
def dfs_postorder(tree, v, order=None, visited=None):
if order is None:
order = []
if visited is None:
visited = set()
if v:
left, right = tree[v]
dfs_postorder(tree, left, order, visited)
dfs_postorder(tree, right, order, visited)
order.append(v)
return order
print("".join(dfs_preorder(tree, 'A')))
print("".join(dfs_inorder(tree, 'A')))
print("".join(dfs_postorder(tree, 'A')))
기존 postorder=None 처리하는 방식 대신 extend를 써보려다가 90%정도 혼자 풀고 나머지 GPT 도움 받음.
extend는 None을 반환하는데 자꾸 반환값을 postorder에다가 할당하려고 그래서 생긴 문제. 그냥 extend를 했으면 해결 됐음.
import sys
sys.setrecursionlimit(10 ** 6)
preorder = []
while True:
try:
preorder.append(int(sys.stdin.readline().strip()))
except:
break
def pre_to_post(preorder):
postorder = []
if preorder:
root = preorder[0]
i = 1
while i < len(preorder):
if preorder[i] > root:
break
i += 1
postorder.extend(pre_to_post(preorder[1:i]))
postorder.extend(pre_to_post(preorder[i:]))
postorder.append(root)
return postorder
딕셔너리도 정렬할 수 있음. sorted를 하면 key값을 기준으로 오름차순 정렬됨.
오.. 이제 DFS, BFS그냥 후다다닥 코딩할 수 있음.(기본 문제만.. 응용 문제는 좀 어렵..)
import sys
from collections import defaultdict, deque
sys.setrecursionlimit(10 ** 6)
N, M, V = map(int, sys.stdin.readline().strip().split())
edges = []
for _ in range(M):
u, v = map(int, sys.stdin.readline().strip().split())
edges.append((u, v))
graph = defaultdict(list)
for edge in edges:
u, v = edge
graph[u].append(v)
graph[v].append(u)
def dfs_recursion(graph, v, order=None, visited=None):
if order is None:
order = []
if visited is None:
visited = set()
if v not in visited:
order.append(v)
visited.add(v)
for adj_node in sorted(graph[v]):
dfs_recursion(graph, adj_node, order, visited)
return order
def dfs_loop(graph, start_v):
order = []
visited = set()
stack = [start_v]
while stack:
current_node = stack.pop()
if current_node not in visited:
order.append(current_node)
visited.add(current_node)
for adj_node in sorted(graph[current_node], reverse=True):
stack.append(adj_node)
return order
def bfs_loop(graph, start_v):
order = []
visited = set()
queue = deque([start_v])
while queue:
current_node = queue.popleft()
if current_node not in visited:
order.append(current_node)
visited.add(current_node)
for adj_node in sorted(graph[current_node]):
queue.append(adj_node)
return order
print(" ".join(map(str, dfs_recursion(graph, V))))
# print(" ".join(map(str, dfs_loop(graph, V))))
print(" ".join(map(str, bfs_loop(graph, V))))
2문제 풀었음. 완전 만족.
(정석대로 푼 건 아니라 답 맞추기에 급급했지만 김코치님께서 '우선 돌아가는 게 좋은 코드다. 최적화는 그 다음이다.'고 하셨으니까..)
2번 문제(2667 단지 번호 붙이기)가 익숙해 보여서 이거 먼저 했는데 70분 정도 걸림.=
로 할당해야 하는데==
쓰고 있어서 무한 루프 돌고 그랬음.
2번 문제 먼저 풀고 20분 밖에 없어서
1번 문제(1388 바닥장식)은 부호가 복잡해 보여서 거의 포기했는데,
세련된 기법은 아니지만 이중 반복문으로 11:29에 '맞았습니다!' 뜸!
쾌감 미쳤음.. 1분 남기고 풀다니..
김코치님 조언
눈 감고 모든 알고리즘 코드를 술술술 쓰지 못해도 괜찮음.
지금의 최소 기준은 '이 알고리즘이 어떤 것인지'를 인식하는 것.
'내가 무엇을 모르는지를 알는 게 가장 중요하다'
좋은 프로그램이란 '돌아가는 프로그램이다.' 현업에서도 우선 돌아가게 만들고 최적화는 다음에 한다.
3번 문제 발표하신 분 덕분에 이거 알게 됨.
PriorityQueue
가 heapq
를 통해 구현되었다고 함.
둘 중 뭐가 좋은건 없지만, 자유도가 더 높은 건 heapq
from queue import PriorityQueue
import sys
N, M = map(int, sys.stdin.readline().strip().split())
floor = []
for _ in range(N):
floor.append(list(sys.stdin.readline().strip()))
left_to_right = 0
up_to_down = 0
for i in range(N):
temp = 0
for j in range(M):
if floor[i][j] == '-':
temp = 1
else:
left_to_right += temp
temp = 0
left_to_right += temp
for i in range(M):
temp = 0
for j in range(N):
if floor[j][i] == '|':
temp = 1
else:
up_to_down += temp
temp = 0
up_to_down += temp
print(left_to_right + up_to_down)
import sys
from collections import deque
import copy
N = int(sys.stdin.readline().strip())
city_map = []
for _ in range(N):
city_map.append(list(map(int, sys.stdin.readline().strip())))
# start_node는 (x, y) 형태
def bfs(N, start_node):
global city_map
# 상, 하, 좌, 우
dx = (1, -1, 0, 0)
dy = (0, 0, 1, -1)
queue = deque()
queue.append((start_node))
count = 0
while queue:
x, y = queue.popleft()
if x < 0 or y < 0 or x >= N or y >= N:
continue
if city_map[x][y] == 0:
continue
city_map[x][y] = 0
count += 1
for i in range(4):
next_x = x + dx[i]
next_y = y + dy[i]
queue.append((next_x, next_y))
return count
results = []
for i in range(N):
for j in range(N):
if city_map[i][j] == 1:
count = bfs(N, (i, j))
results.append(count)
results = sorted(results)
total = len(results)
print(total)
for count in results:
print(count)
앞으로는 공채가 아니라 추천에 의한 채용이 더 확대될 것.
(추천 후 코딩 테스트, 면접 보겠지만 가장 중요한 건 레퍼런스)
코딩, 하드스킬을 늘리는 것도 중요하지만 소프트스킬이 더 중요하다.
함께 일하고 싶은 매력적인 사람이 되어라.
현업에서는 라이브러리 직접 만들어 쓰는 경우 있다.
왜 이미 있는 걸 만드냐? 기성품은 성능 등에서 만족스럽지 못한 경우도 있다.
CS를 모르면 이런 걸 못한다.
남이 만든 걸 가져다만 쓸 수 있다.
이런 걸 할 수 있는 개발자는 많지 않다. 그런 개발자가 되어야 한다.
x86-64
x86은 intel 8086 마이크로프로세서와 그 후속 제품군 지칭 명칭
x86 = 아키텍처
뒤에 숫자 = 확장 비트
둘 다 명령어 집합이라는 것 기억하기
CISC
Complex Instruction Set Computer
설계 철학: 하나의 명령어로 다양한 작업을 수행
RISC
Reduced Instruction Set Computer
설계 철학: 간단하고 빠른 명령어들로 복잡한 작업을 수행
1. CISC
(이거 한 줄로 끝)
ADD MEM1, MEM2, MEM3
실행 과정:
1. MEM1의 값을 CPU 내부의 임시 레지스터로 불러옵니다.
2. MEM2의 값을 또 다른 임시 레지스터로 불러옵니다.
3. 두 임시 레지스터의 값을 더합니다.
4. 결과를 MEM3에 저장합니다.
2. RISC
LOAD R1, MEM1
LOAD R2, MEM2
ADD R3, R1, R2
STORE MEM3, R3
실행 과정:
1. MEM1의 값을 레지스터 R1로 불러옵니다.
2. MEM2의 값을 레지스터 R2로 불러옵니다.
3. 레지스터 R1와 R2의 값을 더하여 결과를 레지스터 R3에 저장합니다.
4. R3의 값을 MEM3에 저장합니다.
내부적으로 진행되는 단계는 비슷하다.
하지만 명령어 수준에서 어떻게 표현하고 처리하는지가 두 아키텍처의 차이
기계어 코드
익스트럭션 포맷: 하나의 명령어가 바이너리 필드들도 구성된 형식
인스트럭션으로 CPU의 내부 구성요소들을 조작하거나 동작시킴
CPU 내부 구성 요소
프로그램 카운터: 실행 할 다음 인스트럭션의 메모리 주소
정수 레지스터 파일: 연산을 위한 일시적인 데이터 저장 공간 64비트 값 저장, 정수 데이터 저장
조건코드 레지스터 파일: 플래그 래지스터, 상태 레지스터. 가장 최근에 실행한 산술 또는 논리 인스트럭션 상태 저장(오버플로우, 제로, 부호 등)
벡터 레지스터: 하나 이상의 정수 또는 부동소수점 값들을 각각 저장(한 번의 연산으로 여러 데이터 요소에 동일한 연산 적용)
처음으로 어셈블리어로 컴파일 해 보았다!
mstore.c
long mult2(long, long);
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
mstore.s
확장자 s는 소스파일이라는 뜻
s 확장자 소스파일은 기계어로 변환될 수 있다.
gcc -Og -S mstore.c
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 12, 0 sdk_version 13, 1
.globl _multstore ; -- Begin function multstore
.p2align 2
_multstore: ; @multstore
.cfi_startproc
; %bb.0:
stp x20, x19, [sp, #-32]! ; 16-byte Folded Spill
stp x29, x30, [sp, #16] ; 16-byte Folded Spill
add x29, sp, #16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
.cfi_offset w19, -24
.cfi_offset w20, -32
mov x19, x2
bl _mult2
str x0, [x19]
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
ldp x20, x19, [sp], #32 ; 16-byte Folded Reload
ret
.cfi_endproc
; -- End function
.subsections_via_symbols
mstore.o
gcc -Og -c mstore.c
CFFAEDFE 0C000001 00000000 01000000 04000000 68010000 00200000 00000000
19000000 E8000000 00000000 00000000 00000000 00000000 00000000 00000000
48000000 00000000 88010000 00000000 48000000 00000000 07000000 07000000
02000000 00000000 5F5F7465 78740000 00000000 00000000 5F5F5445 58540000
00000000 00000000 00000000 00000000 24000000 00000000 88010000 02000000
D0010000 01000000 00040080 00000000 00000000 00000000 5F5F636F 6D706163
745F756E 77696E64 5F5F4C44 00000000 00000000 00000000 28000000 00000000
20000000 00000000 B0010000 03000000 D8010000 01000000 00000002 00000000
00000000 00000000 32000000 18000000 01000000 00000C00 00010D00 00000000
02000000 18000000 E0010000 04000000 20020000 20000000 0B000000 50000000
00000000 02000000 02000000 01000000 03000000 01000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 F44FBEA9 FD7B01A9 FD430091 F30302AA 00000094 600200F9
FD7B41A9 F44FC2A8 C0035FD6 00000000 00000000 00000000 24000000 01000004
00000000 00000000 00000000 00000000 10000000 0300002D 00000000 01000006
19000000 0E010000 00000000 00000000 13000000 0E020000 28000000 00000000
01000000 0F010000 00000000 00000000 0C000000 01000000 00000000 00000000
005F6D75 6C747374 6F726500 5F6D756C 7432006C 746D7031 006C746D 70300000
목적파일(.o)을 소스파일(.s)로 리버스 엔지니어링(역공학)
odjdump - mstore.o
(오브젝트 파일 명령어)
mstore.o: file format mach-o arm64
Disassembly of section __TEXT,__text:
0000000000000000 <ltmp0>:
0: f4 4f be a9 stp x20, x19, [sp, #-32]!
4: fd 7b 01 a9 stp x29, x30, [sp, #16]
8: fd 43 00 91 add x29, sp, #16
c: f3 03 02 aa mov x19, x2
10: 00 00 00 94 bl 0x10 <ltmp0+0x10>
14: 60 02 00 f9 str x0, [x19]
18: fd 7b 41 a9 ldp x29, x30, [sp, #16]
1c: f4 4f c2 a8 ldp x20, x19, [sp], #32
20: c0 03 5f d6 ret
확장자 .O가 실행 파일이 아님! 이건 목적 파일이고 링킹 후에 실행 가능한 실행파일이 생성되는 것!
링커가 여러 목적 파일을 모아 하나의 실행 파일로 만들어 주는 것!
링킹
실행 가능한 코드를 생성하기 위해서 링커를 목적코드들에 대해 실행해야 한다.
gcc Og -o prog main.c mstore.c
hello.c를 컴파일 했는데 hello.out이 아니라 hello가 생성되어 GPT 물어 봄
gcc hello.c -o hello
이유는 -o 옵션이 출력되는 파일명을 지정하는 거!
-o를 생략하면 a.out이 생성됨.
hello.c
#include <stdio.h>
int main () {
printf("Hello, World!\n");
return 0;
}
**hello.o -> objdump -d hello
hello: file format mach-o arm64
Disassembly of section __TEXT,__text:
0000000100003f68 <_main>:
100003f68: ff 83 00 d1 sub sp, sp, #32
100003f6c: fd 7b 01 a9 stp x29, x30, [sp, #16]
100003f70: fd 43 00 91 add x29, sp, #16
100003f74: 08 00 80 52 mov w8, #0
100003f78: e8 0b 00 b9 str w8, [sp, #8]
100003f7c: bf c3 1f b8 stur wzr, [x29, #-4]
100003f80: 00 00 00 90 adrp x0, 0x100003000 <_main+0x18>
100003f84: 00 a0 3e 91 add x0, x0, #4008
100003f88: 05 00 00 94 bl 0x100003f9c <_printf+0x100003f9c>
100003f8c: e0 0b 40 b9 ldr w0, [sp, #8]
100003f90: fd 7b 41 a9 ldp x29, x30, [sp, #16]
100003f94: ff 83 00 91 add sp, sp, #32
100003f98: c0 03 5f d6 ret
Disassembly of section __TEXT,__stubs:
0000000100003f9c <__stubs>:
100003f9c: 10 00 00 b0 adrp x16, 0x100004000 <__stubs+0x4>
100003fa0: 10 02 40 f9 ldr x16, [x16]
100003fa4: 00 02 1f d6 br x16
위 코드에 대한 GPT 해설
sub sp, sp, #32: 스택 포인터(sp)를 감소시켜 스택 프레임을 생성합니다.
이는 로컬 변수를 저장할 공간을 확보하는 것입니다.
stp x29, x30, [sp, #16]: x29(프레임 포인터)와 x30(링크 레지스터)를 스택에 저장합니다.
add x29, sp, #16: 현재 스택 포인터의 값을 x29에 저장합니다.
mov w8, #0: 0 값을 w8 레지스터에 할당합니다.
str w8, [sp, #8]: w8의 값을 스택의 특정 오프셋에 저장합니다.
stur wzr, [x29, #-4]: 0 값을 x29의 특정 오프셋에 저장합니다.
adrp x0, 0x100003000: 페이지 주소를 x0에 로드합니다.
add x0, x0, #4008: x0에 주소 오프셋을 추가합니다.
bl 0x100003f9c: 주어진 주소로 분기하고 현재 주소를 링크 레지스터(x30)에 저장합니다.
즉, _printf 함수를 호출하는 것으로 보입니다.
ldr w0, [sp, #8]: 스택에서 값을 w0 레지스터에 로드합니다.
ldp x29, x30, [sp, #16]: 스택에서 x29와 x30 값을 로드합니다.
add sp, sp, #32: 스택 프레임을 해제합니다.
ret: 현재 함수를 종료하고 x30에 저장된 주소로 돌아갑니다.
16비트 데이터 타입 = 워드
(이유는 원래 인텔 프로세서는 16비트 구조를 사용하다가 추후에 32비트로 확장되었기 때문)
32비트 = 더블워드
64비트 = 쿼드워드
표준 int: 4바이트 더블워드
포인터: 8바이트 쿼드워드
long: 8바이트 쿼드워드
float(단일 정밀도): 4바이트 더블워드
double(이중 정밀도): 8바이트 쿼드워드
C가 있는데 어셈블리어로 코딩하는 경우가 있을까?
있다! 컴파일러가 훌륭하지만, C로 구현하지 못하는 기계어 수준의 코드들이 있다.
이때는 어셈블리어로 코딩 후 C와 연결할 수 있다.
지난 번에 공부함. 문제 풀어보자!
(요약하면 동적이라는 이름은 그냥 멋있어 보여서 붙인거고,
핵심은 문제를 작게 나누어 해를 저장(메모이제이션)하고 이를 모아서 전체 문제의 해를 구하는 철학)
재귀, 반복문 말고 DP로 풀어보자!
import sys
sys.setrecursionlimit(10**6)
n = int(sys.stdin.readline().strip())
def fib(n, mz=None):
if mz is None:
mz = [0] * (n + 1)
if mz[n] != 0:
return mz[n]
if n <= 2:
mz[n] = 1
return 1
mz[n] = fib(n - 1, mz) + fib(n - 2, mz)
return mz[n]
print(fib(n))
유튜브로 흐름만 먼저 보고 구현해서 성공!
마지막에 재귀 자체를 return해서 오류가 있었는데(mz에 저장을 안함)
mz에 저장 코드 넣고, 그 값을 리턴하게 바꾸니까 정상 동작!
피보나치 수열인 것 찾았음.
기본 로직은 짰는데% 15746
계속 못 풀어서 GPT 도움.
코드는 좀 깔끔하지 못하지만 해결은 함.
import sys
N = int(sys.stdin.readline().strip())
sys.setrecursionlimit(10**6)
def bi_loop(N):
if N == 1:
return N
mz = [1, 1]
temp = 0
for _ in range(N - 1):
a = mz[0]
b = mz[1]
temp = (a + b) % 15746
mz[0] = b
mz[1] = temp
return temp
print(bi_loop(N))
GPT 코드
def bi_loop(N):
if N == 1:
return 1
mz = [1, 1]
for _ in range(N - 1):
mz[0], mz[1] = mz[1], (mz[0] + mz[1]) % 15746
return mz[1]
N = int(input())
print(bi_loop(N))
오류 복기
요즘 자꾸 할당을==
로 잘못하고 있음. 주의할 것!
PID는 알고 있었는데 TID(쓰레드 아이디)를 알게 되었음.
TID를 LWP(Light Weight Process)라고도 함.
top
명령어를 통해 실행 중인 프로세스의 동적인 실시간 뷰를 볼 수 있음.
(table of processes)
그 순간에 최적이라고 생각되는 것을 선택해 나가는 방식
단, 근사 알고리즘이다. 100% 최적해를 보장하지는 않는다.
(어느 정도 적합한 수준의 해답)
2가지 조건이 있는 경우 그리디를 적용할 수 있다.
탐욕 선택 속성(greedy choice property)
: 매 선택이 지역적으로 최적이어야 한다.
: 현재의 선택이 이후 선택에 대한 정보나 가능성을 제거하면 안 된다.
(예를 들어, 주어진 예산 내에서 A책을 고르면 아직도 C책을 선택할 수 있는 여유가 있지만, B책을 먼저 고르면 예산 부족으로 C책을 선택할 수 없게 되는 상황에서는 그리디 알고리즘의 탐욕 선택 속성 조건을 충족하지 않는다.)
최적 부분 구조(optimal substructure)
: 부분해로 최적해를 구할 수 있다.
: 부분 문제의 해결 방법을 합치면 전체 문제의 최적해를 구할 수 있다.
예전에 비슷한 문제 프로그래머스에서 풀어본 적 있어서 쉽게 풀었음.
제일 큰 가치부터 하나씩 사용하는 방식으로 그리디 방식이라고 할 수 있음.
import sys
N, K = map(int, sys.stdin.readline().strip().split())
coins = []
for _ in range(N):
coins.append(int(sys.stdin.readline().strip()))
count = 0
for coin in sorted(coins, reverse=True):
count += K // coin
K = K % coin
if K == 0:
break
print(count)
'하'인데 코드 구현이 어려움.
풀이 흐름은 혼자 생각해 냄.
핵심 아이디어는 - 이후에 등장하는 +는 모두 -처럼 취급해도 됨.
입력값을 정리하는 게 어렵다.
import sys
s = sys.stdin.readline().strip().split("-")
result = 0
for num in s[0].split("+"):
result += int(num)
for i in s[1:]:
for num in i.split("+"):
result -= int(num)
print(result)
한번에 해결 불가능한 전체 문제를 작은 문제로 분할하여 분할된 부분부터 해결
대표적으로 병합 정렬(머지 소트)
분할 -> 정복 -> 결합
1. 분할: 현재의 문제와 동일하지만 크기가 더 작은 다수 부분 문제로 분할
2. 정복: 부분 문제를 재귀적으로 정복(부분 문제 크기가 충분히 작으면 직접적으로 해결)
3. 결합: 부분 문제의 해를 결합해 원래 문제의 해로 만듦