Github 기초 사용법 - 1




Git 이란 ?

 깃(Git)은 컴퓨터 파일의 변경사항을 추적하고 여러 명의 사용자들 간에 해당 파일들의 작업을 조율하기 위한 분산 버전 관리 시스템이다(wiki). 즉, 한마디로 개발자들에겐 소스코드의 버전관리를 위한 시스템이다!
 우리(개발자)는 개발을 할 때, 여러 어려움을 겪는다. 기능을 구현하다가 실패했을 때, 이전 상태로 돌아와야한다. 또는 설정파일을 잘못 수정했다가 백업을 해두지 않으면 큰 문제가 발생하기도 한다. 또는 여러명이 같이 협업을 할 때에도 코드의 일관성을 유지하는데 큰 어려움을 겪는다. 이러한 문제를 Git을 통해 해결할 수 있다.
 이번 포스트에서는 Git을 Terminal Interface를 통해 사용하는 방법에 대해 다룬다(우리는 개발자니까). 그리고 Git 설치는 생략한다.
Git 을 만든 '리누스 토발즈' 는 Git을 만들기 전에 BitKeeper라는 상용 버전관리 시스템을 사용하였으나, 어떤 계기로 인해 직접 Git을 만들기 시작했고, 소문에 의하면 2주만에 완성했다고 한다.   


목 차

   1. git init & status & add
   2. Commit
   3. Branch
   4. Merge
   5. 병합(merge) 충돌 관리
   



1. git init & status & add

  먼저 작업공간을 정한 후, Git Bash를 열고 git init을 생성하면 다음과 같이 초기화된다.




  git init을 완료하면 (master)가 추가됨을 확인할 수 있고, 내 작업공간에서 버전 관리를 위환 환경이 구성됨을 의미한다. 하지만 폴더 내에서는 아무 변화가 없어보인다.


 폴더에 파일 하나를 생성해보자. Hello, World를 출력하는 C코드를 작성해본다.

#include<stdio.h>

int main(){
    printf("Hello, World!\n");
    return 0;
}


  그리고 Git Bash 로 돌아가 git status를 입력하면 추적되지 않은 파일이 하나 생성되었음을 알려주고, 친절히도 변경사항을 저장하라는 git add 명령어를 제안한다. git add 명령어를 통해 변경사항을 저장하고 다시 status 명령어를 통해 확인해보자.


 git status : 현재 작업공간에서의 변경사항이 있는지 현재 상태를 알려줌

 git add [파일이름] : 변경된 파일을 (인덱스에) 추가함


 git add * : 현재 작업공간의 모든 파일의 변경사항을 추가




 git add 로 변경사항을 추가하였다. 하지만, 내용을 확정하려면 다른 명령어가 필요하다.




2. Commit

git commit : 변경 내용을 확정

 git add를 통해 변경사항을 추가하였지만, 언제든 코드는 수정될 수 있다. 따라서 변경사항을 확정하기 위해 commit 명령어가 필요하다. git commit을 해보자. git commit을 실행하면, vim 에디터로 넘어가면서 commit의 메세지를 작성하라고 알려준다. 메시지 작성하기 위해선(vim 사용법에 대해 자세히 다루진 않는다).  i 를 누르면 입력모드가 되며, 'message' 를 작성한 후, ESC 를 누르고,  " :(콜론)wq " 를 입력하면(w - write, q - quit) 저장할 수 있다. 메시지는 사용자가 임의의 메시지를 작성하면 된다. commit이 완료되면 마지막 이미지와 같이 추가된 하나의 파일이 저장됨을 확인할 수 있다.  


git commit -m "message"    :  -m 옵션을 통해 메세지를 바로 입력할 수 있다.


 


 




3. Branch

 Branch는 가지를 치는 것과 같다. 처음 git을 초기화하면 master 브랜치가 생성된다(터미널에 표현되는 것처럼). 브랜치는 새로운 기능을 구현하기 위해 현재 코드 상태에서 새로운 가지로 뻗어나가 기능을 구현한 후, 완료되면 기존의 가지(master 또는 다른 branch)에 병합할 수 있다. 




  브랜치를 생성해보자. git branch python을 통해 python이라는 이름의 브랜치를 생성한다. 그리고 git checkout으로 브랜치를 변경할 수 있다.


git branch [브랜치 명] : 브랜치 생성

git branch -a  또는 git branch --list)   :   브랜치 ( -a : 모든 ) 목록 확인

git checkout [브랜치 명] : [브랜치 명] 브랜치로 작업환경 변경





 브랜치를 변경하여도 아직 작업공간에서는 변화가 없다. python 브랜치에서 Hello, World를 출력하는 python 코드를 작성하고 commit까지 해보자.



 그리고 git checkout master 브랜치로 다시 이동해보면, 방금 작성했던 python 파일이 사라진다. 즉, python 브랜치에서 코드를 작성하고 변경한 내역이기 때문에 master 브랜치와는 독립적으로 동작함을 확인할 수 있다.

 






  

4. Merge

 git merge  [브랜치 명] : '현재' 브랜치에서 [브랜치 명]의 변경사항을 병합


 git merge를 통해 다른 브랜치와 병합할 수 있다. 주의할 점은 '현재' 브랜치에서 변경한다는 점이다. 즉, master 브랜치에서 git merge python을 하는 것(python 브랜치를 master 브랜치에 병합)과, python 브랜치에서 git merge master (master 브랜치를 python 브랜치에 병합) 하는 것은 다르다


 master 브랜치에서 python branch를 병합해보자.





병합을 하고 나면 python 브랜치에만 있던 python 코드가 master 브랜치에 병합되어 합쳐짐을 확인할 수 있다. 여기까지 git의 기초 사용법에 대해 다루었다. 다음은 각 브랜치를 병합 시에 쉽게 발생할 수 있는 충돌을 관리하는 법에 대해 다룬다.





5. Merge 충돌 관리

 여러명이 같은 기능을 수정하거나 하면 충돌이 쉽게 발생한다. 즉, 두 명이서 같은 파일에서 작업하면 기존의 내용이 서로 다를 수 있으며, git은 어떤 코드를 반영해야할지 모르게 된다. 충돌이 나는 상황을 살펴보자.


 먼저 python 브랜치에서 python 코드에 주석을 작성하고, commit한 후, master 브랜치에서도 똑같이 주석을 다른내용으로 작성하고 commit을 완료해보자.




 그리고, master 브랜치에서 python 브랜치를 병합해보자. 



 충돌이 발생했다고 표시되며, 브랜치 표시가 (master|MERGING) 으로 변경되었다. master|MERGING 상태는 충돌을 해결하기 위한 임의의 브랜치라고 볼 수 있다. 사용자는 충돌이 발생한 코드를 확인해 변경 후 commit을 하면 충돌을 해결할 수 있다. 

참고로, git diff 명령어를 통해 어디서 충돌이 발생하였는지, 어떤 변경이 있었는지 추적할 수 있다.


<<<<<<HEAD : 현재 브랜치의 표시

 [HEAD]의 코드

=========== : 현재 브랜치와 병합 브랜치의 구분선

 [병합 할 브랜치]의 코드

>>>>>>[브랜치 명] : 


 git diff : 병합 전 어떻게 바뀌었는지 확인가능


 두 주석 모두 변경사항으로 반영하기로 하고, commit을 해보자. 그러면 (master|MERGING)에서 다시 (master) 브랜치로 돌아옴을 확인 할 수 있다.

 



 여기까지 git의 기본 사용법에 대해 다루었다. 사실 아직 git의 가장 인기있는 호스팅 사이트인 github을 사용하지 않고, 로컬 저장소 환경에서만 git의 사용법을 다루었다...(작성하다보니 너무 친절해져서...) 앞으로는 Github의 원격 저장소 및 issue, project 등 여러 기능까지도 다룰 계획이다.




문제 정의


축구 팀 A와 B가 90분에 걸쳐 경기를 한다. 적어도 한 팀이 골을 소수로 득점할 확률을 구하라.

경기를 5분 간격으로 나누고, 처음 간격은 처음 5분이다.

A팀과 B팀이 이 간격에서 득점할 확률이 주어질 때, 경기가 끝난 후 적어도 한 팀이 골을 소수로 득점할 확률을 구하시오.

입력 예) 50 50




문제 풀이


이 문제는 DP로 풀 수 있습니다.

DP[p][a][b] : p번째 간격이 지났을 때, A팀이 a골, B팀이 b골을 넣었을 확률


즉, pa를 A가 넣을 확률, pb를 B가 넣을 확률이라고 할 때,

DP[p][a][b] = DP[p-1][a][b] * (1.0 - pa) * (1.0 - pb) +

DP[p-1][a-1][b] * pa * (1.0 - pb) +

DP[p-1][a][b-1] * (1.0-pa) * pb +

DP[p-1][a-1][b-1] * pa * pb

로 구할 수 있습니다.





소스코드


#include< stdio.h >
double dp[20][20][20];
int prime[7] = { 2,3,5,7,11,13,17 };
int isprime(int x) {
	for (int i = 0; i < 7; i++)
		if (x == prime[i])
			return 1;
	return 0;
}
int main() {
	int a, b;
	scanf("%d%d", &a, &b);
	
	double A = a / 100.0, B = b / 100.0;
	dp[1][0][0] = (1. - A) * (1. - B);
	dp[1][1][0] = A * (1. - B);
	dp[1][0][1] = (1. - A) * B;
	dp[1][1][1] = A*B;
	
	for (int p = 2; p <= 18; p++) {
		for (a = 0; a <= 18; a++) {
			for (b = 0; b <= 18; b++) {
				dp[p][a][b] = dp[p - 1][a][b] * (1. - A)*(1. - B);
				if (a != 0)
					dp[p][a][b] += dp[p - 1][a - 1][b] * A * (1. - B);
				if (b != 0)
					dp[p][a][b] += dp[p - 1][a][b - 1] * (1. - A) * B;
				if (a != 0 && b != 0)
					dp[p][a][b] += dp[p - 1][a - 1][b - 1] * A * B;
			}
		}
	}	
	double ans = .0;
	for(a=0;a<=18;a++){
		for (b = 0; b <= 18;b++) {
			if (isprime(a) || isprime(b))
				ans += dp[18][a][b];
		}		
	}
	printf("%.10lf\n", ans);
	return 0;
}


'알고리즘 문제풀이' 카테고리의 다른 글

BOJ 1194 달이 차오른다, 가자.  (0) 2020.08.11
BOJ 6087 레이저통신  (0) 2020.08.09
BOJ 11505 구간 곱 구하기  (0) 2020.08.06
1351 무한 수열  (0) 2018.05.28
1670 정상 회담 2  (0) 2018.05.17

문제 정의


무한 수열 A를 푸는 문제 입니다.

 - A[0] = 1;

 - A[i] = A[i/P] + A[i/Q] ( i>=1, i/P 또는 i/Q가 정수가 아닐 때는 가우스 기호를 이용한다. ( 가우스 기호, [3.4] = 3)

 - N, P, Q가 주어질 때, A[N]을 구하여라. ( ,  )




문제 풀이 - 1


이 문제는 전형적인 DP 문제입니다. 

피보나치 문제에서 DP[n] = DP[n-1] + DP[n-2] 의 점화식으로 n번째 값을 구하기 위해 이전에 계산한 n-1, n-2번째 값을 이용합니다.


하지만, 이 문제의 큰 문제는 N과, P,Q의 범위입니다. N의 범위가 커 변수에 모두 저장할 수 없습니다.

첫 번째로 생각한 방법은 STL의 map을 이용해 저장하는 방법입니다. 실제 10^12크기의 변수는 메모리 제한에 걸리니

실제 사용되는 양은 그렇게 많지 않아 map에 저장해 사용하면 가능할 것이라 생각했습니다.




소스코드 - 1


#include < stdio.h >
#include < map >
using namespace std;
typedef unsigned long long ull;
ull N, P, Q;
map < ull, ull > m;
ull dp(ull x) {
	if (x == 0)
		return 1;
	if (m[x] != 0)
		return m[x];
	return m[x] = dp((ull)x / P) + dp((ull)x / Q);
}
int main() {	
	scanf("%lld%lld%lld", &N, &P, &Q);
	printf("%lld\n", dp(N));
	return 0;
}






문제 풀이 - 2


 map을 이용하면 편리하게 문제를 금방풀 수 있었지만, 허무해서 다른 방법은 없을까 생각해봤습니다.

한 가지 떠오른 방법은 N으로부터 P와 Q가 나눠지기 때문에 나눠진 수를 이용하면 저장할 수 있겠다 싶었습니다.

즉, N에서 P를 1번 나눈 값은 N/P이고 P는 1번 나누었고, Q는 0번 나누었습니다. 

N에서 같은 p와 q번 P,Q를 나눈 값은 언제나 동일한 점을 알았고, 이를 이용해 다음처럼 구현할 수 있었습니다.




소스코드 - 2


dp의 크기는 P와 Q의 최소값인 2로 나눌때의 최대값, 즉 10^12 = 2^10^4 으로 약 40 이상으로 잡으면 됩니다.


 
#include < stdio.h >
typedef unsigned long long ull;
ull dp[50][50], N, P, Q;
ull solve(ull x, int p, int q) {
	if (x == 0)
		return 1;
	if (dp[p][q] != 0)
		return dp[p][q];
	return dp[p][q] = solve((ull)x / P, p + 1, q) + solve((ull)x / Q, p, q + 1);
}

int main() {
	scanf("%lld%lld%lld", &N, &P, &Q);
	printf("%lld\n", solve(N, 0, 0));
	return 0;
}

'알고리즘 문제풀이' 카테고리의 다른 글

BOJ 1194 달이 차오른다, 가자.  (0) 2020.08.11
BOJ 6087 레이저통신  (0) 2020.08.09
BOJ 11505 구간 곱 구하기  (0) 2020.08.06
1344 축구  (0) 2018.06.01
1670 정상 회담 2  (0) 2018.05.17

+ Recent posts