ft_split

“ft_split에 대하여”
<목차>목차>
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)
함수는 이중 배열을 받아서 그 안의 일차원 배열과 이중 배열 자체를 해제시키는 함수인데, 이 함수는 앞으로 과제를 진행할 때 두고두고 사용하게 되므로 숙지하는 편이 좋을 것 같다.