개발 프로젝트/Hive Helsinki [42 Helsinki]

[Hive Helsinki / Piscine] C04

iinana 2024. 3. 12. 23:21
728x90

Exercise 00: ft_strlen 

Create a function that counts and returns the number of characters in a string.
Allowed Function: None

 

  C04에서도 C02, C03와 비슷한, string을 기반으로 한 문제들을 대체로 볼 수 있다. 이 문제는 reproduce를 요구하지는 않았지만, <string.h> 헤더 내 존재하는 strlen() 함수와 비슷한 역할을 하는 함수를 만들기를 요구한다. strlen은 문제에서 설명하소 있는 대로 인자로 주어진 문자열의 길이를 구하는 것이다. 즉 null character 전까지의 길이를 리턴하는 함수를 만들면 된다. 

 따라서 길이를 파악하기 위한 len 변수를 하나 만들어 초기값을 0으로 주고 하나씩 더해 결국 리턴하는 코드를 만들었다. C02와 C03를 마쳤다면 쉽게 작성할 수 있는 코드이다. 

int	ft_strlen(char *str)
{
	int	len;

	len = 0;
	while (str[len])
		len++;
	return (len);
}

 

 

 

Exercise 01: ft_putstr 

Create a function that displays a string of characters on the standard output.
Allowed Function: write

 

  이 문제 역시 string에 대한 이해만 있으면 쉽게 풀 수 있다. 인자로 받은 문자열을 그대로 출력하기만 하면 된다. 

#include <unistd.h>

void	ft_putstr(char *str)
{
	while (*str)
		write(1, str++, 1);
}

 

 

 

Exercise 02: 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

 

  C04가 까다로워지기 시작하는 지점이 보통 여기라고 생각하는 것 같다. 이 문제는 인자로 받은 정수값을 그대로 출력하면 되는 문제이다. 말로만 보면 간단해 보인다. 사실 allowed function 제한만 없으면 이렇게 쉬운 문제가 없다. 그냥 printf를 써서 출력하면 되기 때문이다. 하지만 write가 char type만 출력할 수 있다는 점을 생각하면 복잡해진다. int type으로 받은 숫자를 char type으로 변환해서 사용해야 하기 때문이다. 그렇다고 그냥 형변환을 해버리면 아스키코드 값을 기반으로 변환되기 때문에 그래서는 안된다. 

 그래서 내가 취한 방식은 10의 자릿수만큼의 제곱을 만들어서 하나씩 나누면서 출력하는 방식이다. 만약 세자리수라면 pow 변수에 10의 세제곱을 넣어서 나누고 출력하고 pow에서 10을 나눠 다음 자리로 이동하고를 반복하는 것이다. 아래 표는 38792를 예로 로직을 이해하기 쉽게 표로 표현한 내용이다. 

누적 시행 횟수 nb pow 출력되는 숫자
1 38792 10000 3
2 8792 1000 8
3 792 100 7
4 92 10 9
5 2 1 2

 

 아래는 설명된 로직을 적용한 코드이다. int type의 최소값인 -2147483648을 if문으로 따로 처리한 이유는 해당 숫자를 양수로 바꿀 경우 overflow가 발생하기 때문이다. 따라서 숫자 그대로 출력한 후 함수를 종료하는 방식으로 예외처리하였다. 

 이번 코드에는 참고하면 좋을 테스트 케이스도 주석 내 main function에 넣어두었다. 내가 쉽게 발견하지 못했던 특이한 케이스를 위주로 넣었으니 시도해 보면 좋을 것 같다. 

#include <unistd.h>

void	ft_putnbr(int nb)
{
	int		pow;
	char	c;

	if (nb == -2147483648)
	{
		write(1, "-2147483648", 11);
		return ;
	}
	if (nb < 0)
	{
		write(1, "-", 1);
		nb = -nb;
	}
	pow = 1;
	while ((nb / pow) >= 10)
		pow *= 10;
	while (pow > 0)
	{
		c = (nb / pow) + '0';
		nb = nb % pow;
		write(1, &c, 1);
		pow /= 10;
	}
}

/*
int	main(void)
{
	write(1, "42: ", 4);
	ft_putnbr(42);
	write(1, "\n-42: ", 5);
	ft_putnbr(-42);
	write(1, "\n0: ", 5);
	ft_putnbr(0);
	write(1, "\n34293: ", 8);
	ft_putnbr(34293);
	write(1, "\n390100 ", 9);
	ft_putnbr(390100);
	write(1, "\n-2147483648: ", 13);
	ft_putnbr(-2147483648);
} */

 

 

 

Exercise 03: ft_atoi 

Write a function that converts the initial portion of the string pointed by str to its int representation.
The string can start with an arbitray amount of white space (as determined by isspace(3)).
The string can be followed by an arbitrary amount of + and - signs, - sign will change the sign of he int returned based on the number of - is odd or even.
Finally the string can be followed by any numbers of the base 10.
Your function should read the string until the string stop following the rules and return the number found until now.
You should not take care of overflow or underflow. result can be undefined in that case.
Allowed Function: None

 

  이 문제는 기존에 존재하는 atoi 함수를 reproduce하는 문제이다. 하지만 완전히 똑같이 reproduce하는 것은 아니고, 문제에서 제시하는 특정한 조건들이 있다. 우선 atoi 함수는 기본적으로 숫자로 구성된 문자열을 받아서 int type으로 변환해 주는 함수이다. 우리가 ft_putnbr() 함수를 만들 때, int type을 받아서 문자 타입으로 변환하여 출력했던 것과 반대방향이라고 생각하면 편하다. 하지만 그것만 수행하는 것은 아니고, 주어지는 문자열 형태에 적용 가능한 형식이 주어진다. 첫 번째로, 숫자 앞에는 여러 개의 white space가 올 수 있다. 여기서 정확히 white space가 무엇인지는 "man isspace"로 확인할 수 있는데, '\t', '\n', '\v', '\f', '\r', ' '의 6가지 공백이 white space에 해당된다. 또한 숫자 앞에 여러 개의 '+' 혹은 '-' 부호가 올 수 있는데, 여기서 '-' 부호가 홀수개이면 음수가 되고, 짝수개이면 양수가 된다. 여기서 주의할 점은 부호 사이에 혹은 숫자 사이에 공백이 나올 수 없다. 또 숫자 사이에 부호가 나올 수 없다. 즉 [ 공백(생략 가능) ->  부호(생략 가능) -> 숫자 ] 순서로 문자열이 제시되어야 우리가 원하는 정삭적인 결괏값을 기대할 수 있는 것이다. 만약 rule에 위배되는 내용이 나오면 거기서 변환을 멈추게 되고, 거기까지의 숫자가 결괏값이 된다. 

 몇가지 예를 들면 아래와 같다. 

"\t---+--+1234ab567"  =>  1234 (a에서 숫자가 아닌 값이 나와 규칙에 위배되므로 변환을 멈춤)
"--+- +1325632167"  => 0 (부호 사이 공백에서 규칙이 위배됐으므로 변환을 멈추고 변환된 숫자가 없으므로 0이 결괏값)

 

이러한 복잡한 규칙들을 바탕으로 작성한 코드는 다음과 같다. 우선 is_white_space() 함수를 통해 주어진 문자가 whitespace인지 아닌지 판단하는 것을 돕는다. if문을 두 개로 나눈 이유는 42 norm의 한 줄 당 글자수 제한 때문이므로 원래는 하나로 합쳐도 무관하다. 

 본격적으로 ft_atoi() 함수를 보면, str 문자열이 존재하고, 현재 문자가 white space일 동안  str 문자열 내에서 체크하는 순서를 다음으로 넘겨준다. white space이면 규칙을 위배하지는 않지만 쓸모 있는 정보는 아니므로 다음으로 넘기는 것이다. 다음으로는 sign 변수로 '-' 부호가 몇 개 나오는지를 기록하며, 부호 파트를 다시 넘겨준다. 부호 파트 역시 알고 싶은 정보는 '-'부호가 짝수개인지 홀수개인지에 대한 정보뿐이므로 나머지는 기록해 줄 필요 없이 넘기면 된다. 다음으로는 숫자 파트일 동안 res 변수에 숫자를 기록해 가며 넘겨준다. 이전까지의 res 변숫값에 10을 곱해주고 거기에 현재 받은 문자를 숫자로 바꿔 더하는 방식을 택한다. 플로우를 좀 더 쉽게 이해하기 위해 숫자 "1234"를 예로 표를 만들어보았다. 

시행 횟수 현재 res 변수의 값 현재 읽은 문자 계산 과정
1 0 '1' 0 * 10 + ('1' - '0') = 1
2 1 '2' 1 * 10 + ('2' - '0') = 12
3 12 '3' 12 * 10 + ('3' - '0') = 123
4 123 '4' 123 * 10 + ('4' - '0') = 1234

 

 마지막으로 이렇게 바꾼 res 변수에 sign이 홀수라면 -1을 곱해주고 아니라면 그냥 리턴하는 방식으로 부호를 조정하여 리턴한다. 만약 처음부터 규칙에 맞지 않거나 숫자가 나오지 않는 등의 경우는 while문 자체가 실행되지 않으므로 결괏값이 0으로 정상적으로 리턴된다. 

int	is_white_space(char c)
{
	if ((c == '\t') || (c == '\n') || (c == '\v'))
		return (1);
	if ((c == '\f') || (c == '\r') || (c == ' '))
		return (1);
	return (0);
}

int	ft_atoi(char *str)
{
	int		res;
	int		sign;

	res = 0;
	while (*str && is_white_space(*str))
		str++;
	sign = 0;
	while (*str && ((*str == '+') || (*str == '-')))
	{
		if (*str == '-')
			sign++;
		str++;
	}
	while (*str && (*str >= '0') && (*str <= '9'))
	{
		res = res * 10 + (*str - '0');
		str++;
	}
	if (sign % 2)
		return (res * (-1));
	else
		return (res);
}

 

 

 

Exercise 04: ft_putnbr_base 

Create a function that displays a number in a base system in the terminal.
This number is given in the shape of an int, and the radix in the shape of a string of characters.
The base-system contains all useable symbols to display that number :
◦ 0123456789 is the commonly used base system to represent decimal numbers
◦ 01 is a binary base system ;
◦ 0123456789ABCDEF an hexadecimal base system ;
◦ poneyvif is an octal base system.
The function must handle negative numbers.
If there’s an invalid argument, nothing should be displayed. Examples of invalid arguments :
◦ base is empty or size of 1;
◦ base contains the same character twice ;
◦ base contains + or - ;
Allowed Function: write

 

  이 문제는 이때까지 다뤘던 모든 문제를 종합하여 활용하는 것을 요구하는 문제이다. 조건에서 볼 수 있듯 절대 간단한 문제는 아니지만 앞 문제들을 제대로 이해하고 있다면 못 풀 것은 없는 문제이다. 다만 사소한 실수를 하지 않도록 주의해야 한다. 전체적인 문제를 설명하면, 우선 우리가 만들고자 하는 함수는 하나의 int type 인자와 문자열 인자, 총 두 개의 인자를 받는다. 이것을 통해 int type 인자을 출력해야 하는 것인데, 앞선 ft_putnbr()에서처럼 그대로 출력하는 것이 아니라, 주어진 문자열 인자에서 제시하는 base를 기반으로 변환하여 출력하는 것이다. 예시로 나와있듯 만약 base 문자열이 "0123456789"라면 10진수로 변환하여 출력하는 것이다. 이 base는 숫자가 아니라도 무엇이든 될 수 있는데,  예시에서 제시된 것처럼 "poneyvif"가 주어진다면 8진수를 기반으로 하지만 숫자 "01234567"로 표현하는 것이 아니라 이를 주어진 알파벳으로 대체하여 표현해야 하는 것이다. 다만 base 문자열에 몇가지 제한 사항이 있는데, base 문자열의 길이는 2 이상이어야 하고, 같은 문자를 두 번 이상 포함해서는 안되며, 문자 '+' 혹은 '-'를 포함해서는 안된다. 이 경우에 해당하는 base를 받은 경우에는 아무것도 출력되면 안된다. 

 이를 바탕으로 코드를 작성해보았다. 우선 check_conditions() 함수를 통해 base 문자열이 올바른 형식을 가지고 있는지 검사한다. 만약 검사하여 invalid하다고 판단되면 0을 리턴하고 아니라면 base의 길이를 리턴한다. base의 길이를 리턴하는 이유는 base 길이가 2 이상이어야 한다는 조건을 체크해야 하고 또 몇 진법을 사용하여 숫자를 변환해야 할지를 결정하기도 해야 하기 때문이다. 

 우리가 만들어야 할 함수인 ft_putnbr_base() 함수의 경우, helper function인 check_condtions()를 이용하는 것으로 시작한다. 그 이후 음수인지를 판단하여 음수라면 '-' 부호를 먼저 출력해준다. 여기서 약간 고민이 있었는데, 보통 이진수의 경우 음수일 때 앞에 '-' 부호를 붙여주는 것이 아니라 보수로 표현하기 때문에 어떤 방식을 취해야 할지 고민했다. 하지만 이 문제가 2진수 만을 다루는 문제가 아니고 보다 보편적인 규칙은 '-'를 붙이는 것이므로, '-'를 붙이는 것으로 처리했다. 그 이후에는 우리가 C03에서 16진수로 바꿔줄 때와 동일한 방식으로 각 진법에 맞는 수로 변환하여 출력해 줬다. 다만 다른 점은 base에서 인덱스에 맞는 수를 찾아서 출력해 줬다는 점이다. 

#include <unistd.h>

int	check_conditions(char *base)
{
	int		size;
	char	c;
	int		i;

	size = 0;
	while (base[size])
	{
		c = base[size];
		if ((c == '+') || (c == '-') || (c == ' '))
			return (0);
		i = size + 1;
		while (base[i])
		{
			if (c == base[i])
				return (0);
			i++;
		}
		size++;
	}
	return (size);
}

void	ft_putnbr_base(int nbr, char *base)
{
	int				bsize;
	long int		pow;
	unsigned int	n;

	bsize = check_conditions(base);
	if (bsize <= 1)
		return ;
	if (nbr < 0)
	{
		write(1, "-", 1);
		nbr = -nbr;
	}
	n = (unsigned int)nbr;
	pow = 1;
	while (n / pow >= bsize)
		pow *= bsize;
	while (pow > 0)
	{
		write(1, &base[n / pow], 1);
		n = n % pow;
		pow /= bsize;
	}
}

 

 

 

Exercise 05: ft_atoi_base 

Write a function that converts the initial portion of the string pointed by str to int representation.
str is in a specific base given as a second parameter.
excepted the base rule, the function should work exactly like ft_atoi.
If there’s an invalid argument, the function should return 0. Examples of invalid arguments :
◦ base is empty or size of 1;
◦ base contains the same character twice ;
◦ base contains + or - or whitespaces;
Allowed Function: write

 

  이 문제는 exercise03과 exercise04를 섞어놓은 것 같은 문제이다. exercise04를 반대로 바꾸는 것으로 생각할수도 있는데,  exercise04가 int 값을 문자로 바꿔 출력했다면, 이번에는 string으로 받은 base 기반 값을 10진수 int type의 숫자로 바꾸어야 한다. 이때 바꾸는 규칙은 atoi와 동일하고 base에 관한 규칙은 putnbr_base와 동일하다.

 조건에 대해서는 따로 다시 설명할 것은 없어 바로 코드 설명으로 넘어간다. 전체적인 로직은 우선 받은 string을 10진수우선 is_white_space() 함수는 atoi에서 사용했던 것과 같다. 또, check_conditions() 함수 역시 white space를 적용했다는 점 외에는 putnbr_base와 동일하다. (앞선 문제에서는 white space에 관한 언급이 없었지만 여기서는 있어서 추가했다.)

 find_num() 함수는 인자로 받은 문자를 base 문자열 내에서 찾아내서 그 인덱스 값을 리턴한다. 인덱스 값은 10진수 기반 값이 되므로, 해당 숫자를 이용하여 최종 결괏값을 만들어낼 수 있기 때문이다. 하지만 만약 찾는 문자가 없다면, 그것은 rule을 위반한 문자가 된다는 것이므로, 값 변환을 위한 while문 종료를 위해 -1을 리턴한다.

 마지막으로 우리가 만들어야 하는 목표 함수인 ft_atoi_base() 함수의 경우 base를 체크하는 것으로 시작하여, 앞서 만들었던 ft_atoi와 똑같이 white space와 부호 파트에서 필요한 정보만 취하고 해당 부분을 넘긴다. 마지막 while문에서는 base 내에 현재 위치의 문자가 존재하는 동안에 res 변수에 최종 결괏값을 더해나가는 과정을 반복한다. res 변수에 최종 결괏값을 더해나가는 과정은 ft_atoi와 유사하다. 마지막으로 부호를 판단하여 해당 값을 리턴해주면 함수가 마무리된다. 

int	is_white_space(char c)
{
	if ((c == '\t') || (c == '\n') || (c == '\v'))
		return (1);
	if ((c == '\f') || (c == '\r') || (c == ' '))
		return (1);
	return (0);
}

int	check_conditions(char *base)
{
	int		size;
	char	c;
	int		i;

	size = 0;
	while (base[size])
	{
		c = base[size];
		if (is_white_space(c))
			return (0);
		if ((c == '+') || (c == '-'))
			return (0);
		i = 0;
		while (base[i] && (i < size))
		{
			if (c == base[i])
				return (0);
			i++;
		}
		size++;
	}
	return (size);
}

int	find_num(char c, char *base)
{
	int		i;

	i = 0;
	while (base[i])
	{
		if (c == base[i])
			return (i);
		i++;
	}
	return (-1);
}

int	ft_atoi_base(char *str, char *base)
{
	int				bsize;
	int				res;
	int				sign;

	res = 0;
	bsize = check_conditions(base);
	if (bsize <= 1)
		return (0);
	while (*str && is_white_space(*str))
		str++;
	sign = 0;
	while (*str && ((*str == '+') || (*str == '-')))
	{
		if (*str == '-')
			sign++;
		str++;
	}
	while (find_num(*str, base) != -1)
	{
		res = res * bsize + find_num(*str, base);
		str++;
	}
	if (sign % 2)
		return (res * (-1));
	return (res);
}
728x90