[Hive Helsinki / Piscine] C00
Exercise 00: ft_putchar
Write a function that displays the character passed as a parameter
Allowed Function: write
해당 문제는 기존에 <stdio.h> 헤더에 존재하는 putchar 함수와 동일한 기능을 하는 함수를 작성하는 것이 목표이다. 사실상 인자로 받은 character type의 변수를 write 함수를 이용하여 출력해주기만 하면 되는 간단한 문제이다.
하지만 여기서 중요하게 알아야 할 것이 바로 write 함수를 어떻게 사용하느냐 이다. 나는 이전에 c언어로 코딩하면서 주로 printf함수를 사용해 왔기 때문에 write 함수에는 익숙하지 않았다. 그래서 write 함수에 대해서 먼저 공부하였다. (write 함수는 window 환경에서는 사용이 불가능하다고 한다. 아무래도 42네트워크에서 흔히 사용할 수 없는 로우레벨의 함수를 찾으려고 한 듯 하다......)
기본적인 write function의 형태는 다음과 같다.
write(프린트 할 공간, 버퍼, 프린트할 바이트 수)
프린트할 공간의 의미는 해당 함수를 사용하여, 결과를 어디에 적을지 정하는 것이다. 터미널이 될 수도, 파일 안에 작성하는 것이 될 수도 있겠다. 하지만 내가 해야 할 것은 터미널에 출력하는 것이므로, 1이 그 역할을 한다는 정도만 확인하고 넘어갔다. (이후 파일 입출력을 사용해 본 결과, open함수로 파일을 열면 파일이 int 형태 변수에 저장되기 때문에 해당 인자를 파일 변수로 채우면 된다.) 버퍼는 제대로 설명하자면 복잡하지만, 결국 내가 전달해야 하는 것은 character 타입의 주소값이다. string의 첫 char의 주소값을 적고 바이트 수를 1보다 크게 작성하면 그 역시 작동할 것이다. 해당 문제에서는 char type 변수 하나만 출력하면 되기 때문에 바이트 수를 늘릴 필요가 없지만 뒤에 문제에서는 이걸 아느냐가 코드 수를 줄이는 데 굉장히 중요하게 작용했다.
이를 파악한 후 결과적으로 작성한 코드는 다음과 같다.
#include <unistd.h>
void ft_putchar(char c)
{
write(1, &c, 1);
}
<unistd.h> 헤더는 write 함수를 가지고 있는 헤더이다. 그 이후 내용은 write 함수가 어떻게 작동하는지 안다면 간단하게 파악할 수 있을 것으로 예상된다.
하지만 나는 ft_putchar함수가 결국 기존에 있는 putchar함수와 같은 역할을 하는 것이기 때문에 putchar함수와 완벽히 똑같이 기능하도록 만들어보고 싶었다. 그러기 위해서 putchar 함수에 대해 먼저 알아보았다.
기본적인 putchar function의 형태는 다음과 같다.
int putchar(int character)
putchar 함수는 int 형태로 출력할 character를 입력받아 터미널에 출력한 후, 해당 character를 다시 int 타입으로 반환한다. 여기서 왜 character를 받아서 출력하는데, 굳이 int 형태로 입력받고, 반환하는지에 대해 의문점이 생길 수 있다. 그 이유는 EOF를 처리학 위해서 이다. 그냥 코드 내에서 변수를 선언하고 출력할 때는 크게 문제가 없지만, 터미널 입력이나 파일을 읽는 등, 외부에서 입력을 받을 때에는 EOF를 처리하는 것이 중요하다. 보통 우리가 외부입력의 끝이 어딘지 코드를 작성할 때 알 수 없기 때문에, 일종의 flag로 EOF를 사용하게 되는 것이다. 그리고 EOF는 -1의 값을 가진다. -1은 char 타입으로는 처리할 수 없다. 그렇기 때문에 우리는 int 형태의 값을 입력받아 int형태의 값을 리턴하게 되는 것이다. 결국 putchar 함수가 -1을 리턴하면 우리는 입출력을 그만해야 한다는 것을 알 수 있다.
이를 바탕으로 작성한 함수는 다음과 같다. int 값을 입력받아 char 값으로 형변환을 한다. write 함수는 int 값을 출력할 수 없기 때문이다. 그리고 터미널 출력이므로, 첫 번째 인자로 1을 주고, 두 번째 인자로 형변환된 character가 담긴 c변수의 주소값을 넣어준다. 마지막으로 한 글자를 출력하는 것이므로 인자로 1을 주면 된다. 끝으로 인자로 받은 character을 리턴하는 것으로 함수가 마무리된다.
#include <unistd.h>
int ft_putchar(int character)
{
char c;
c = (char)character;
write(1, &c, 1);
return (character);
}
Exercise 01: ft_print_alphabet
Create a function that displays the alphabet in lowercase, on a single line, by ascending order, starting from the letter ’a’.
Allowed Function: write
해당 문제는 말 그대로 알파벳을 a부터 z까지 터미널에 출력하는 함수를 만드는 것이다. 사실 아래 방법 말고 그냥 write function안에 알파벳 26자를 넣어 한 번에 출력하는 쉬운 방법이 있긴 하다. (실상 코딩을 조금만 공부했어도 이 방법이 더 번거로울 테지만, 피신에는 비기너도 많기 때문에 실제로 시험에서는 그런 방법을 쓰는 사람도 많이 봤다.) 그래도 문제가 원하는 바가 그런 것 같지도 않고 내 입장에서는 알파벳 스물여섯 자를 순서 맞춰서 실수 없이 입력하는 일이 더 번거로울 것 같았기 때문에 그냥 정석대로 풀었다.
이 문제는 아스키코드에 대한 개념만 알면 쉽게 접근이 가능하다. 프로그램들은 주로 문자를 숫자, 즉 할당된 '아스키코드'로 처리하기 때문에 하나씩 증가시키며 출력하는 아래 코드가 가능해진다. null character, 'A', 'a'와 같은 대표적인 아스키코드는 나중에는 결국 외워버리게 되지만, 실수를 줄이기 위해서 안전하게 문자를 넣어서 처리하는 게 나을 수 있다.
#include <unistd.h>
void ft_print_alphabet(void)
{
char c;
c = 'a';
while (c <= 'z')
{
write(1, &c, 1);
c++;
}
}
Exercise 02: ft_print_reverse_alphabet
Create a function that displays the alphabet in lowercase, on a single line, by descending order, starting from the letter ’z’.
Allowed Function: write
해당 문제는 앞서 풀었던 문제에서 순서만 역순으로 바꾸면 된다. 즉, z부터 시작해서 하나씩 줄여가며 a까지 출력하면 되는 것이다. 코드는 아래와 같다.
#include <unistd.h>
void ft_print_reverse_alphabet(void)
{
char c;
c = 'z';
while (c >= 'a')
{
write(1, &c, 1);
c--;
}
}
Exercise 03: ft_print_numbers
Create a function that displays all digits, on a single line, by ascending order.
Allowed Function: write
해당 문제도 앞서 풀었던 문제들과 상당히 유사하다. 단순히 알파벳을 숫자로 바꾼 것뿐이다. 하지만 주의해야 할 점은 char 변수에 값을 할당할 때, 정수 1이 아닌 문자 '1'을 할당해야 한다는 점이다. 그리고 마찬가지로 while문 내 조건을 넣을 때 역시 정수 9가 아닌 문자 '9'를 넣어야 한다. 문자가 아닌 정수를 넣게 되면, 해당 아스키코드를 가진 다른 문자가 출력될 것이다. (그리고 해당 아스키코드를 가진 문자는 심지어 printable도 아니기 때문에 상당히 괴이한 결과를 볼 가능성이 높다.)
#include <unistd.h>
void ft_print_numbers(void)
{
char c;
c = '0';
while (c < 58)
{
write(1, &c, 1);
c++;
}
}
Exercise 04: ft_is_negative
Create a function that displays 'N' or 'P' depending on the integer’s sign entered as a parameter. If n is negative, display 'N'. If n is positive or null, display 'P'.
Allowed Function: write
해당 문제 역시 간단하다. 주어진 숫자가 음수면 'N'을, 양수이거나 0이면 'P'를 터미널에 출력하면 되는 문제이다. 여기서 대체로 비기너들은 'N'과 'P'를 위한 변수를 생성하여 문제를 해결한다. 그렇게 write를 사용하는 방법밖에 배우지 않았기 때문이다. 하지만 string의 개념을 제대로 이해하고 있는 사람이라면 보다 간단한 코드로 문제를 해결할 수 있다. 기본적으로 c언어에는 따로 string type이 제공되지 않는다. 따라서 char타입 array를 생성하고 끝에 null character를 포함하여 문자열의 끝을 표기해 줌으로써 string 형태를 이용 가능하다. 그런데 이 stirng을 그대로 사용하게 되면 이는 첫 번째 글자의 주소값을 가리키게 된다.
즉 아래 두 가지 표현이 같아지는 것이다.
// character 'N'을 쓰는 방법 1
char c;
c = 'N';
write(1, &c, 1);
// character 'N'을 쓰는 방법 2
write(1, "N", 1);
따라서 굳이 변수 선언을 하지 않고, 간단히 아래와 같이 코드를 짤 수 있다. 0도 P에 포함되어야 한다는 점에 주의하여 코드를 작성하자.
#include <unistd.h>
void ft_is_negative(int n)
{
if (n < 0)
write(1, "N", 1);
else
write(1, "P", 1);
}
Exercise 05: ft_print_comb
Create a function that displays all different combinations of three different digits in ascending order, listed by ascending order - yes, repetition is voluntary.
Allowed Function: write
해당 문제는 각각의 자릿수가 모두 다른 세 자릿수를 오름차순으로 출력하는 문제이다. 789가 있으면 987은 존재해서는 안 된다는 말에서, (1) 같은 숫자 조합이 다시 나오면 안된다 (2) 같은 숫자 조합이라면 더 작은 수를 택한다. 라는 점을 파악할 수 있고, 다시 여기서 각 자릿수도, 숫자 자체도 모두 오름차순이어야 한다는 점을 유추할 수 있다.
따라서 나는 크기가 3인 char type 배열을 하나 선언하여, 인덱스 0을 100의 자릿수, 1을 10의 자릿수, 2를 1의 자릿수로 설정하여 문제를 풀었다. 각각 자릿수의 숫자를 바꿔가면서 출력하기 위해서 삼중 while문을 사용했고, 789가 최댓값이므로, 각각 자릿수의 최댓값을 '7', '8', '9'로 설정했다. 그리고 한 자릿수의 다음 자릿수는 해당 자릿수 + 1로 설정하여 반복 없는 오름차순을 완성하였다. 세 번째 자릿수(일의 자릿수)까지 결정된 이후에는 해당 숫자를 출력하고, 마지막 숫자(789)가 아닌 경우에는 콤마(,)와 공백을 함께 출력했다.
void ft_print_comb(void)
{
char num[3];
num[0] = '0';
while (num[0] <= '7')
{
num[1] = num[0] + 1;
while (num[1] <= '8')
{
num[2] = num[1] + 1;
while (num[2] <= '9')
{
write(1, res, 3);
if (!((num[0] == '7') && (num[1] =='8') && (num[2] == '9')))
write(1, ", ", 2);
++num[2];
}
++num[1];
}
++num[0];
}
}
Exercise 06: ft_print_comb2
Create a function that displays the number entered as a parameter. The function has to be able to display all possible values within an int type variable.
Allowed Function: write
위 문제와 조건은 유사하나, 두 자릿수 두 개를 출력해야 하는 문제이다. 두 개의 두 자릿수는 다른 숫자여야 하며, 마찬가지로 오름차순이어야 한다. 또한, 이전 문제와는 다르게 한 숫자 내 자릿수까리는 같은 숫자를 가져도 된다.
해당 문제를 풀기 위해, int type의 a를 첫 번째 숫자로, b를 두 번째 숫자로 설정하고, 이중 while문으로 두 숫자를 증가시키며 하나씩 출력해 나갔다. 출력의 편의를 위해 크기가 5인 char type의 배열을 선언했고, 예시 출력과 똑같이 출력하기 위해 res[2], 즉 배열의 중간값을 공백으로 미리 설정해 두었다. 그리고 a, b가 하나씩 증가하며 res[0]과 res[1]에는 a의 10의 자릿수와 1의 자릿수가, res[3]과 res[4]에는 b의 10의 자릿수와 1의 자릿수가 각각 들어가서 배열 5자리를 한꺼번에 출력하는 방식이다.
int type인 a와 b를 char type 배열로 바꿔주는 방법은, 10의 자리 수를 추출하기 위해 나누기를 사용하고 (a와 b 그리고 10이 모두 int type 이므로 int type의 십의 자릿수만 추출된다. double type으로 지정하면 소수가 추출될 수 있다.) 1의 자리 수를 추출하기 위해 나머지 연산을 사용한다. 그리고 0의 아스키코드를 더해주면 numeric character로 변환할 수 있다.
#include <unistd.h>
void ft_print_comb2(void)
{
char res[5];
int a;
int b;
res[2] = ' ';
a = 0;
while (a < 99)
{
res[0] = (a / 10) + '0';
res[1] = (a % 10) + '0';
b = a + 1;
while (b < 100)
{
res[3] = (b / 10) + '0';
res[4] = (b % 10) + '0';
write(1, res, 5);
if (!((a == 98) && (b == 99)))
write(1, ", ", 2);
b++;
}
a++;
}
}
Exercise 07: ft_putnbr
Create a function that displays the number entered as a parameter. The function has to be able to display all possible values within an int type variable.
Allowed Function: write
해당 문제는 int type으로 받은 숫자를 터미널에 출력해야 하는 문제이다. printf를 쓸 수 있다면 printf("%d", nb);로 간단히 끝날 일이지만, 우리는 char type만 출력 가능한 write 함수를 써야 하므로, int type을 char type으로 바꿔줘야 한다. 문제는 char type이 숫자 0에서 9까지밖에 표현을 못한다는 점이다. 위 ft_print_comb2 문제에서 두 자릿수까지는 바꾸어줬지만, 여기서는 int range 내 더 큰 범위의 값과 음수까지 모두 고려해줘야 한다.
내가 아래 코드에서 택한 방법은 자릿수만큼 10을 제곱해 줘서 하나씩 나눠서 한자릿수씩 추출해 가며 출력해 주는 방식이다. 이때 int range의 최솟값인 -2147483648은 따로 예외처리를 해줘야 한다. 왜냐하면 음수를 처리할 때 '-' 기호를 먼저 써주고 수를 양수로 변환해서 다음 단계를 진행하는 방식을 택했는데, int range의 최댓값은 2147483647이라 최솟값을 양수로 바꾸면 overflow가 발생하기 때문이다. 해당 예외를 처리하기 위한 다양한 방법이 있지만 예외가 저거 하나라서 그냥 하드코딩 해주고 함수를 종료시켰다.
#include <unistd.h>
int powing(int nb)
{
int pow;
pow = 1;
while ((nb / pow) > 9)
pow *= 10;
return (pow);
}
int check_condition(int nb)
{
if (nb == -2147483648)
{
write(1, "-2147483648", 11);
return (1);
}
return (0);
}
void ft_putnbr(int nb)
{
int pow;
char res;
if (check_condition(nb) == 1)
return ;
if (nb < 0)
{
write(1, "-", 1);
nb = -nb;
}
pow = powing(nb);
while (pow > 0)
{
res = nb / pow + '0';
nb %= pow;
write(1, &res, 1);
pow /= 10;
}
}
이렇게 적어놓고 평가해 주면서 다양한 다른 접근방법도 봤는데, recursive를 이용하면 함수를 훨씬 짧게 작성해 줄 수 있다. 자릿수마다 함수를 call 해야 하기 때문에 시간효율 측면에서는 부정적 효과가 있겠지만, 해봐야 11자리가 최대이기 때문에 크게 신경 쓰지 않아도 될만한 정도이다. 게다가 코드가 획기적으로 짧아진다. 피신 내내 뚝심 있게 혹은 고집 있게 iterative로 사용하긴 했지만 recursive 버전 코드도 적어두면 좋을 것 같아서 남긴다. (해당 코드는 moulinette을 통과한 코드가 아님)
void ft_putnbr(int nb)
{
char c;
if (nb / 10 == 0)
{
c = nb + '0';
write(1, &c, 1);
return ;
}
if (nb == -2147483648)
{
write(1, "-2147483648", 11);
return ;
}
if (nb < 0)
{
write(1, "-", 1);
ft_putnbr(-nb);
}
c = nb / 10 + '0';
write(1, &c, 1);
ft_putnbr(nb % 10);
}
Exercise 08: ft_print_combn
Create a function that displays all different combinations of n numbers by ascending order.
Allowed Function: write
해당 문제는 exercise05 ft_print_comb와 유사하고, 조건 역시 동일하나, ex05에서 세 자릿수를 출력했던 것과는 달리 n을 인자로 받아 n자릿수를 출력해야 한다는 점에서 차이가 있다.
따라서 iterative를 사용했던 이전의 comb 문제와는 다르게 코드 간소화 등 여러 이유로 recursive를 사용했다. 크기 10의 res 배열을 선언해 준 후, cur 인자를 0부터 시작하여 end 인자(n - 1)까지 반복하며 각 자릿수를 채우고 자릿수가 다 채워지면 출력한 후 다음 수로 넘어가서 다시 반복해 주는 방식이다. 한 recursive 내에서는 각 자릿수에 대해, 이전 자릿수 + 1부터(첫 번째 자리의 경우 0부터) 9까지를 차례로 넣어가며 배열을 하나하나 완성한다.
조금 더 자세히 설명하면, reucr_comb 함수 내 첫 if문은 배열이 다 채워져 출력할 준비가 되었는지를 확인하고, 그렇다면 출력하고 해당 recursive 단계를 종료하기 위해 존재하며, 두 번째 if else 문은 첫 번째 자릿수는 이전자릿수 + 1부터 시작할 수 없으므로 0부터 시작하고 아니라면 이전자릿수 + 1부터 시작하기 위함이다. 그리고 while 문에서 각 자릿수에 한 숫자씩 넣은 후, 다음 자릿수를 위한 recursive 함수를 사용한다. 첫번째 if문에 걸려 한번 출력하고 나면 그 앞자리로 가서 다음 숫자를 넣는 과정을 진행하게 될 것이다.
#include <unistd.h>
void recur_comb(int cur, int end, char *res)
{
int i;
if (cur > end)
{
write(1, res, end + 1);
if (res[0] != (9 - end + '0'))
write(1, ", ", 2);
return ;
}
if (cur == 0)
i = '0';
else
i = res[cur - 1] + 1;
while (i <= '9')
{
res[cur] = i;
recur_comb(cur + 1, end, res);
i++;
}
return ;
}
void ft_print_combn(int n)
{
char res[10];
if ((n <= 0) || (n >= 10))
return ;
res[9] = 0;
recur_comb(0, n - 1, res);
}