💻STUDY/BOJ

[BOJ] 1157. 단어 공부 (C)

coldNoodlePigeon 2022. 1. 14.

※구현을 목표로 하였기 때문에 비효율적일 수도 있음 주의. 아직 코딩 초보입니다!!  


1157. 단어 공부 

알파벳 대소문자로 된 단어가 주어지면, 이 단어에서 가장 많이 사용된 알파벳이 무엇인지 알아내는 프로그램을 작성하시오. 단, 대문자와 소문자를 구분하지 않는다. 

 

첫째 줄에 이 단어에서 가장 많이 사용된 알파벳을 대문자로 출력한다. 단, 가장 많이 사용된 알파벳이 여러 개 존재하는 경우에는 ?를 출력한다.

 

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 1000001

int main() {
	char *s;
	s = (char*)malloc(sizeof(int)*MAXSIZE); //문자를 입력받고 저장

	char* a;
	a = (char*)malloc(sizeof(int)*MAXSIZE); //문자 저장 (대문자 변환해서) 
	
	int *b;
	b = (int*)malloc(sizeof(int)*MAXSIZE); //문자 개수 저장 
	b[0] = 1; //첫번째 초기화
	
	
	gets(s);

	int k,max,count,t=0; 

	for (int i = 0; s[i] != '\0'; i++) {
		k = (s[i]>='a' && s[i]<='z')? s[i]-32:s[i]; //문자가 소문자이면 대문자로 변경
		
		int is = 0; 
		for (int q = 0; a[q]!='\0'; q++) {
			if (a[q] == k) is++; 
		}
		if (is==0) {
			
			count = 0; //문자 개수 세는 변수 

			for (int j = i; s[j]; j++) { //같은 문자 있는지 검토 시작 
				if (k == ((s[j] >= 'a' && s[j] <= 'z') ? s[j] - 32 : s[j])) { //소문자면 대문자로 변경 후 같은지 검사
					count++; 
				}

			}
			a[t] = k; 
			b[t] = count; 
			t++; 
		}
	}
	
	int r=0; 
	max = b[0]; 
	for (int m = 0;b[m]; m++) {
		if (max < b[m]) {
			max = b[m]; 
			r = m; 
		}
	}
	count = 0; 
	for (int u = 0; b[u]; u++) {
		if (max == b[u]) count++; 
	}


	if (count==1) {
		printf("%c", a[r]); 
	}
	else {
		printf("?"); 
	}
	
}

먼저 C언어로 구현해보았다. 이번에는 내가 잘못 알고 있었던 오개념들도 많았고 코드를 구현하면서 놓쳤던 부분들도 새롭게 배울 수 있어서 좋았다. 비효율적 코드이긴 하지만... 구현을 목표로 하였기 때문에 더 효율적인 방법은 차차 생각해보기로 했다. 일단 구현을 했다는 점에서 나에게 칭찬을 해보자. 아이 잘했다. 굿 잡. 

 

 


내가 생각한 코드 구현 방향 

 

내가 생각했던 코드의 작동방향은 이러하다.

 

먼저 문자열을 저장할 s에 gets함수를 이용하여 입력을 받는다. 

s 첫번째 인덱스부터 차례대로 한 문자씩 대문자로 바꾼 후, 문자열을 세도록 한다. 

(단, 대문자로 변환한 문자들을 저장하는 배열(a) 에 해당 문자가 존재하는지를 판단을 먼저 하고, 존재한다면 해당 인덱스부터 문자열 끝까지 차례대로 count를 세도록 해야한다. 만약 해당 문자가 존재하는지를 판단하지 않고 그냥 코드를 수행한다면.. 예를 들어 abaab 가 입력되었다고 한다면, a 배열에는 A B A A B 가 저장되고, b 배열( count 저장함) 에는 3 2 2 1 이 저장된다. 원래 정상적으로 코드가 돌아간다면 a 배열에는 A B 가 저장되고 b 배열에는 3 2 가 저장되어야 한다.)  

변환한 문자들을 저장한 a 배열에 해당 문자가 존재하지 않는 것을 확인하면, 해당 문자부터 문자열 끝까지 해당 문자와 같은 문자 (이때, 문자 비교를 위해 비교하는 대상의 문자가 소문자라면, 대문자로 바꿔줘야한다.) 가 존재하면 count를 증가시키고, 문자열 끝까지 검토한 후 해당 문자와, 해당 문자의 count 수를 각각 a 배열과 b 배열에 저장한다. (같은 인덱스 위치에!) a 배열과 b 배열에 차례대로 넣기 위해 t 라는 인덱스 변수를 두어 해당 과정을 수행할때마다 1씩 증가하여 인덱스를 이동시킨다. 

다음으로, b 배열에서 가장 큰 수를 찾고 (max), max 값의 중복이 있는지를 검사한다. 

중복이 없다면 해당 문자를 a 배열에서 가져와서 출력하고, 아니라면 ?를 출력하도록 한다. 

 

 


코드 구현

 

대략적인 작동 방향을 정리해보았으니 이번에는 짠 코드들을 세부적으로 살펴보겠다. (보면 뭔가 쓸데없는 변수들이 많은데..코드를 짜면서 테스트 해본다고 뭔갈 많이 만들어놨다. 안쓰이는 변수는 생략하고 보길 바란다.) 

 

먼저 입력받은 문자열 s 를 차례차례 k 라는 int형 변수에 대문자로 변환하여 저장합니다. 대문자로 변환하여 저장하는 a 배열에 해당 문자 k 가 저장되어 있는지를 확인합니다. 여기서 is 변수는 만약 a 배열에 해당 문자가 이미 존재한다면, is 가 0이 아닌 수가 되어 문자 개수를 세는 파트를 실행하지 않도록 방지해주는 역할을 하는 변수입니다. 만약, abab 가 입력되었고, 현재 큰 for 반복문이 3번째 (인덱스로는 2) a 를 k에 A로 저장하여 해당 작업들을 수행한다고 할때, 이미 a배열에 A와 B가 존재하므로 is 변수가 2가 되므로 , is 변수가 0이 아니기 때문에 문자 개수를 세는 코드를 실행하지 않도록 합니다. 

 

is 변수가 0이라면 (a 변수에 처음으로 저장되는 문자라면) count라는 변수를 0으로 처음 설정해주고(if 조건문 안에 해야한다! 바깥에다가 하면 값이 누적된다! 주의!) for 반복문을 통해 해당 문자가 있는 지점부터 문자열 끝까지 같은 문자가 있는지 검토를 시작한다. 

(왜 해당 문자가 있는 시점부터 검토를 하나요? 사실 상관없다. 0부터 검토를 해주어도 코드가 잘못돌아간다거나 반례가 생기지는 않는다. )

같은 문자가 있다면, count를 증가시킨다. count를 끝까지 증가시킨 후, a 배열과 b 배열에 차례대로 같은 인덱스 값으로 접근하여 해당 문자(대문자로 변환된 문자)와 문자의 개수를 저장한다. 이때, 배열에 차례대로 a b / 1 2 이런식으로 저장되게 하기 위해 따로 t라는 인덱스 접근용 변수를 만들어 같은 문자가 있는지를 검토하는 반복문이 끝날때마다 1씩 증가시켜서 배열의 인덱스를 이동시켰다. 

 

가장 문자 개수가 많은 값을 b 배열에서 찾는 반복문이다. max라는 변수를 만들어서 인덱스 차례차례 max 값보다 큰 값이 있으면 해당 값을 max 에 저장하도록 하였다. (r은 해당 문자의 위치를 나타내는 인덱스를 저장하는 역할을 하는 변수이다.) 

 

여기서 문제 조건을 잘 보면, 만약에 aabb 처럼 가장 많은 문자의 수가 2 2 , 똑같으면 ?를 출력하도록 하기 위해서 max가 중복인지를 확인하는 반복문도 작성하였다. count =0 이라는 변수를 만들어주었다. (아까 문자 개수를 세는 변수 아니었나요? -> 맞다. 0을 넣어서 초기화시켜주긴 했는데 원래대로라면 다른 변수를 사용했어야 했다.) b 배열을 처음부터 차례차례 검사하여 max 랑 같은 값을 가진 문자 개수 값을 세도록 count를 1씩 증가시켰다. 

만약, aaab 처럼 max가 중복되지 않는다면 b 배열에는 3 1 이 있다. 즉, count는 1이다. 

aabb처럼 max가 중복된다면, 즉 b 배열에 2 2 가 저장되어 있다면 count는 2이다. 

 

따라서 if 조건문을 통해 count가 1일 경우에만 해당 인덱스(r)에 있는 문자 (a 배열에 접근) 를 출력하도록 하고, 그게 아니라면 중복이 존재한다는 의미이므로 ?를 출력하도록 한다. 


코드를 구현하면서 가졌던 오개념, 헷갈렸던 점 정리. 

 

  • 메모리 동적 할당 함수인 malloc()은 <stdlib.h> 라이브러리에 저장되어 있다. 사용할 경우 반드시 라이브러리를 앞에 포함시켜주자.
  • 일반적인 배열은 크기가 고정되어 있다. 따라서 배열 크기를 설정할때 주의하여야 한다. 이때, 정해져 있는 배열의 크기보다 큰 배열을 설정해주어야할 때에는 동적 할당을 사용해주어야 한다. 이는 메모리를 운영체제에서 할당받아서 사용하고, 사용하고 나면 반납하는 용도이다. 이 메모리의 공간을 힙(heap)이라고 하며 이 공간은 운영체제가 사용되지 않는 메모리 공간을 모아놓은 곳이다.(에러참고: https://docs.microsoft.com/ko-kr/cpp/code-quality/c6262?f1url=%3FappId%3DDev16IDEF1%26l%3DKO-KR%26k%3Dk(C6262)%26rd%3Dtrue&view=msvc-170)
    char s[1000001]; //올바르지 못한 방법. 정해진 배열의 크기를 초과해버린다.
    
    char *s;
    s=(char*)malloc((sizeof(int)*1000001); //올바른 방법.
  • 포인터에 할당된 메모리를 배열처럼 사용하기 위한 방법. ( https://dojang.io/mod/page/view.php?id=317 ) (코딩도장 38.1) 해당 링크의 방법을 참고하였다. 처음에 sizeof(int)를 곱하지 않아서 헤맸었다. sizeof(int)를 곱해야만 배열처럼 사용할 수 있는 점을 주의하자! 
  • 현재 코드에는 생략해두었는데, free() 함수는 할당된 메모리 블록을 운영체제에게 반환한다. free(포인터명) 을 통해 프로그래머가 직접 할당된 메모리를 해제해주어야 한다. 만약에 할당 해제를 안하고 그냥 프로그램을 종료시키면.. 동적할당된 배열이 메모리를 차지하기 때문에 컴퓨터 성능에 문제가 생긴다. 이번에는 컴파일에 문제가 없어서 생략해버렸지만, 반드시 free함수를 통해 할당된 메모리 블록을 반환하도록 하자. 
  • 문자 하나만 출력할때에는 %s가 아니라 %c로 출력해야한다. 

내가 학기 중에 c언어를 배우면서 그냥 넘겨짚고 넘어간 부분을 다시 복습할 수 있어서 좋았다.

c언어는 정말..메모리 관리를 효율적으로 할 수 있는 언어라 속도도 빠르고 좋지만 일일히 프로그래머가 다 코드로 지정해놔야해서..귀찮은 점이 많은 것 같다. (확실히 백준 채점만 해봐도 파이썬보다 c언어 속도가 훨씬 빠르다.)

 

이번 문제는 구현을 목표로 해서 많이 비효율적인 코드이지만, 조금 더 고민해보고 효율적인 코드를 짤 수 있을지 생각해봐야겠다. 파이썬으로 구현하는건 내일 시도해보는 걸로. 

 

궁금한 점이 있으시다면 댓글로.. 

댓글