ft_split

ft_split

“ft_split에 대하여”

<목차>

1. MY CODES

2. 사용과 설명

ft_split – 매개변수로 문자열과 구분자를 받아서, 구분자를 기준으로 문자열을 쪼개 이중 배열을 만들고 반환한다. 이중 배열은 반드시 NULL 포인터로 끝나야 한다.

(1) MY CODES

#include "libft.h"

static int	is_sep(char s, char c)
{
	if (s == c || s == 0)
		return (1);
	return (0);
}

static int	count_size(char const *s, char c)
{
	int	s_size;

	s_size = 0;
	while (*s)
	{
		if (!is_sep(*s, c) && is_sep(*(s + 1), c))
			s_size++;
		s++;
	}
	return (s_size);
}

static void	ft_freeall(char **spl)
{
	size_t	j;

	j = 0;
	while (spl[j])
	{
		free(spl[j]);
		j++;
	}
	free(spl);
	return ;
}

static void	place_word(char **spl, char const *s, char c, int *flag)
{
	int		s_idx;
	int		i;

	s_idx = 0;
	while (*s)
	{
		if (is_sep(*s, c))
			s++;
		else
		{
			i = 0;
			while (*(s + i) && !is_sep(*(s + i), c))
				i++;
			spl[s_idx] = ft_substr(s, 0, i);
			if (spl[s_idx] == NULL)
			{
				*flag = 1;
				ft_freeall(spl);
				return ;
			}
			s_idx += 1;
			s += i;
		}
	}
}

char	**ft_split(char const *s, char c)
{
	char	**spl;
	int		s_size;
	int		flag;

	flag = 0;
	if (!s)
		return (NULL);
	s_size = count_size(s, c);
	spl = (char **) malloc(sizeof(char *) * (s_size + 1));
	if (spl == NULL)
		return (NULL);
	place_word(spl, s, c, &flag);
	if (flag == 1)
		return (NULL);
	spl[s_size] = NULL;
	return (spl);
}
  • libft에서 만들었던 문자열 처리 함수 중 가장 높은 난이도를 자랑하는 ft_split() 되시겠다.

(2) 사용과 설명

int main()
{
	char	*s = "abc1def1ghi";
	char	**spl = ft_split(s, '1');
	for (int i = 0; spl[i] != NULL; i++)
		printf("%s ", spl[i]);
	printf("\n");
	return 0;
}
output :
	abc def ghi
  • 문자열 “abc1def1ghi”과 구분자 ‘1’을 받아서 이중 배열 “abc def ghi” 을 반환받았다. 보통 공백을 기준으로 문자열을 쪼개지만, 공백이 아니라 다른 구분자로 쪼갤 수도 있다.

  • 함수는 다음과 같이 진행된다.

(1) 이중 배열의 길이를 구한다.

static int	is_sep(char s, char c)
{
	if (s == c || s == 0) // 구분자와 NUL일 때
		return (1);
	return (0);
}

static int	count_size(char const *s, char c)
{
	int	s_size;

	s_size = 0;
	while (*s)
	{
		if (!is_sep(*s, c) && is_sep(*(s + 1), c))
			s_size++; 
			// 해당 문자가 구분자가 아닌데 다음 문자가 구분자라면 이중 배열의 길이를 하나 늘려줌
		s++;
	}
	return (s_size); // 이후 s_size + 1 크기의 이중 배열 할당
}
  • 문자열을 순회하면서 인덱스와 그 다음 인덱스를 동시에 확인해서, 해당 문자가 구분자가 아닌데 다음 문자가 구분자라면 이중 배열의 길이를 하나 늘려준다. “abc1defghi”의 경우 …
        ↓       ↓       ↓    
    a b c 1 d e f 1 g h i \0
    
  • 이 지점에서 이중 배열의 길이가 늘어나게 된다.

  • 구분자를 만날 때마다 배열의 길이를 하나씩 늘리기 때문에 마지막의 NUL를 세주지 않으면 이중 배열 길이를 하나 덜 세게 되는데, 이것을 방지하려면 처음부터 배열 길이에 1을 더하고 시작하면 되겠다. 물론 그렇게 했을 때 발생하는 예외에 대해서 따로 처리를 해야 할 것이다.

(2) 할당된 이중 배열을 원본 문자열로 채워준다

static void	place_word(char **spl, char const *s, char c, int *flag)
{
	int		s_idx;
	int		i;

	s_idx = 0;
	while (*s)
	{
		if (is_sep(*s, c)) // 구분자라면 아무 처리도 없이 다음 문자열을 확인한다.
			s++;
		else
		{
			i = 0;
			while (*(s + i) && !is_sep(*(s + i), c))
				i++; // 구분자라면, 포인터 s를 고정시키고 구분자가 나올 때까지 i를 늘린다.
			spl[s_idx] = ft_substr(s, 0, i); 
			// s와 i를 사용하면 부분 문자열을 깔끔하게 할당할 수 있다.
			if (spl[s_idx] == NULL)
			{
				*flag = 1; 
				// 메모리 할당에 실패하여 함수가 종료되었다는 것을 알리기 위한 flag이다.
				ft_freeall(spl); // 할당에 실패했다면, 이중 배열 내의 모든 요소를 모두 해제한다.
				return ;
			}
			s_idx += 1; // 이중 배열의 인덱스를 하나 늘린다.
			s += i; // 포인터의 위치를 다음 문자열로 옮긴다.
		}
	}
}
  • 이제 만들어둔 이중 배열에 문자를 채워넣는다. 위 코드에서는 문자열을 순회하면서 구분자가 아닌 부분의 인덱스를 구한 후, 부분 문자열을 만들어 이중 배열에 하나씩 넣어주는 방식을 사용했다. 부분 문자열을 만들 때는 ft_substr을 사용했다.

(3) 마무리 및 메모리 해제

static void	ft_freeall(char **spl)
{
	size_t	j;

	j = 0;
	while (spl[j])
	{
		free(spl[j]);
		j++;
	}
	free(spl);
	return ;
}

char	**ft_split(char const *s, char c)
{
	char	**spl;
	int		s_size;
	int		flag;

	flag = 0;
	if (!s)
		return (NULL);
	s_size = count_size(s, c);
	spl = (char **) malloc(sizeof(char *) * (s_size + 1));
	if (spl == NULL)
		return (NULL);
	place_word(spl, s, c, &flag);
	if (flag == 1) 
	// place_word가 메모리 할당에 실패하여 종료되었다면, flag가 1이고, 이 경우
	// 곧바로 함수를 종료시킨다.
		return (NULL);
	spl[s_size] = NULL;
	return (spl);
}
  • place_word에서 메모리 할당에 실패하여 종료되었을 경우, flag가 1이 된다. 이렇게 플래그를 세우든, 아니면 place_word() 함수의 반환값을 int로 받아서 구분하든 간에 메모리 할당에 실패해서 반환되었는지, 정상 종료되었는지에 대한 구분이 반드시 필요하다.

    개인적으로는 반환값을 받아서 구분하는게 예쁘다고 생각한다.

  • 메모리 할당에 실패하여 이중 배열 및 그 안의 요소들을 전부 해제했는데, 그 배열들의 인덱스에 접근하게 되면 오류가 발생하게 된다. 위 코드에서 ` spl[s_size] = NULL;` 부분이 해당한다. 그래서 flag가 1이라면 곧바로 NULL을 반환하면서 함수를 종료시켰다.
  • ft_freeall(char **spl) 함수는 이중 배열을 받아서 그 안의 일차원 배열과 이중 배열 자체를 해제시키는 함수인데, 이 함수는 앞으로 과제를 진행할 때 두고두고 사용하게 되므로 숙지하는 편이 좋을 것 같다.