문자열 뒤집기 문제 그(it) 얘기

세그멘테이션 폴트에서 가져온 문자열 뒤집기 문제입니다.

문제 1.
본인에게 가장 편한 프로그래밍 언어로 문자열의 순서를 뒤집는 함수를 만드시오.


이 문제에 대한 해법은 몇 가지가 나올 수 있는데, 우선 문제의 정의에 따라 간단히 구현해 본다면...
/* 답: A안 */

void reverse(const char* text, char* out_buff, size_t max_length)
{
    const int src_length = strlen(text);
    int length = src_length < max_length ? src_length : max_length;

    for (int i = length-1; i >= 0; --i)
        *out_buff++ = text[i];

    *out_buff = NULL;
}
가장 심플하고 확실한 방법이긴 하지만, 사전에 메모리 공간을 할당받아야 된다는 것과 잘못된 메모리 접근으로 인한 오류의 가능성도 내포하고 있습니다.

물론, 메모리를 새로 할당받지 않고 그 자리에서 순서를 뒤집을 수 있는 방법도 있습니다.
/* 답: B안 */

template<typename T>
inline void swap(T& __a, T& __b)
{
    T __tmp = __a;
    __a = __b;
    __b = __tmp;
}

char* reverse(char* text)
{
    const int length = strlen(text);

    for (int i = length-1; i > 0; --i)
        for (int j = 0; j < i; ++j)
            swap(text[j], text[j+1]);

    return text;   
}
이 방법은 if문이 빠졌다는 것을 제외하면 버블 정렬과 모양새가 많이 비슷합니다. 그리고 여기에다는 쓰지 않았지만, 2번 문제에 대한 답도 이런 형태입니다.

하지만... 1번 문제를 풀면서 처음부터 이런 알고리즘으로 구현할 생각이었다면 최악의 선택이라고 감히 말할 수 있습니다. 말할 것도 없이, 이 알고리즘은 쓸데없이 많은 루프를 돌기 때문입니다.

자, 그럼 조금 다듬어 보기로 합시다.
/* 답: C안 */

template<typename T>
inline void swap(T& __a, T& __b)
{
    T __tmp = __a;
    __a = __b;
    __b = __tmp;
}

char* reverse(char* text)
{
    const int maxCnt = strlen(text) / 2;

    for (int i = 0; i < maxCnt; ++i)
        swap(text[i], text[length - i - 1]);

    return text;
}
문자열 뒤집기라는 것은 sort에서와는 달리 비교를 통해서 자리바꿈 하는게 아니라 처음부터 옮겨야할 자리가 정해져 있습니다. 게다가 서로 대칭되는 위치로 자리 바꿈만 하면 되는 겁니다.

예를 들어, "123456789"라는 문자열을 뒤집는다고 하면...

1 ↔ 9
2 ↔ 8
3 ↔ 7
4 ↔ 6
5는 제자리

결국 문자열 길이의 절반 만큼만 루프를 돌면 됩니다.

문제에서는 문자열 뒤집기를 다루고 있지만, 문자열도 결국은 배열. 좀 더 일반화시키면 어떤 종류의 배열도 순서를 뒤집어 볼 수 있습니다.
/* C안의 일반화: 배열 뒤집기 */

template<typename T>
inline void swap(T& __a, T& __b)
{
    T __tmp = __a;
    __a = __b;
    __b = __tmp;
}

template<typename T>
T* reverseArr(T* src, size_t length)
{
    const int maxCnt = length / 2;
   
    for (int i = 0; i < maxCnt; ++i)
        swap(src[i], src[length - i - 1]);
   
    return src;
}

트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://Sizuha.egloos.com/tb/4156481 [도움말]

덧글

  • 아즈마 2009/06/03 13:36 # 답글

    이제 Hello, World! 하고 앉아있는 저로서는, A안 겨우겨우 알아보는게 겨우...(orz)
  • asdfgh 2009/06/03 14:17 # 삭제 답글

    프로토타입이
    char* reverse(char* text) 또는
    char* reverse(char* text, size_t length)일 경우
    후출하는 쪽에서
    char *p = "hello world";를 넘길 경우
    세그먼트 폴트 에러가 발생합니다.
    호출하는 쪽은 이유도 모르고 당하는거죠.
    (이런 어이없는 실수때문에 날밤을 새게 되죠. 이 경우, 제3자가 봤을때 호출한쪽의 실수가 아니라 호출당한쪽, reverse가 잘못한게 되죠)

    차라리
    char *p = "hello world";
    char q[256];
    이 훨씬 낫죠.
    q에 대해 꼭 메모리 할당이라는 개념을 적용할 필요는 없겠죠~




  • 시즈하 2009/06/03 14:34 #

    제대로 쓴다면, 저라면
    void reverse(char* in_out_text, size_t length)
    같이 선언하겠습니다. reverse에 들어가는 문자열 인수가 상수가 되서는 안된다는 점을 강조하는 거죠.

    그리고 char *p = "hello world"; 처럼 쓰는건 좋은 표현이 아니죠.
    "hello world"가 본래 상수이기 때문에 const char *p = "hello world"; 같이 써줘야 컴파일 단계에서 상수성을 체크해 줄 수 있습니다.
    (좀 더 정확한 선언은 const char *p const = "hello world";가 되겠습니다)


    A안 같은 경우는 상수를 날려도 문제 없습니다.

    그리고 char buff[256]; 같이 버퍼를 잡는 것도 안전하다고는 할 수 억습니다.
    255글자를 넘는 문자열이 넘어오지 않는다는 보장이 없으니까요.

    B,C안의 경우는 퍼퍼를 따로 잡지 않고 소스를 직접 수정하는 방식이니, 문자열 길이만 제대로 넘겨주는 그 문제는 회피할 수 있습니다.
  • asdfgh 2009/06/03 15:00 # 삭제 답글

    둘의 차이는 잘 알고 계시리라 생각합니다.

    /* 세그먼트 폴트를 일으키는 코드(#1) */
    char *p = "hello world";
    void reverse(p, strlen(p));

    /* 세그먼트 폴트를 일으키지 않는 코드(#2) */
    char p[] = "hello world";
    void reverse(p, strlen(p));

    문제는 #1처럼 쓰는 경우가 아주 일상적이고 당연하다는 점이죠.

    ** 그리고 오해가 있으신 것 같은데, size_t length가 필요한 이유는 받는 쪽의
    크기를 알 수 없기 때문에 사용하는 것이지,
    시즈하님의 경우처럼 받는쪽의 버퍼를 reverse함수쪽에서 알고 있는 경우는
    size_t length가 필요가 없죠.


    아래의 경우에만 length가 필요하죠.
    void reverse(const char* text, char* out_buff, size_t out_buf_length)

    length를 두는 이유는... 그 크기까지만 "안전하게" 변환을 하겠다는 의미입니다.
    윈도의 ini파일 읽어들이는 함수들의 프로토타입을 참고하시면 될 겁니다.
    (문자열이 더 길어도 버퍼에서 잘라버리도록 api가 설계되어 있죠)
  • 시즈하 2009/06/03 15:14 #

    length가 들어가 있는 것은, 사실 일반화된 reverseArr()를 먼저 만들어 놓고 그걸 char* 버전으로 고쳐서 올렸거든요. 문제 풀이용 예제로 빨리 만들다 보니 손이 가는 데로 만들었습니다.

    문제의 정의 자체는 문자열 전체를 뒤집는 거였으니, 문자열 처리에만 국한한다면 말씀하신대로 문자열 길이는 알아서 계산해주면 되겠죠.

    reverseArr()도 템플릿을 약간 수정하면 length를 뺄 수 있죠.
  • 숑카 2009/06/06 13:43 # 삭제 답글

    public static void main(String[] args) throws IOException {

    BufferedReader in new =BufferedReader(new InputStreamReader(Systen.in));

    StringBuffer str = new StringBuffer();

    str.append(in.readerLine());

    System.out.println(str.reverse());
    }

    라고 하면 안될까요?....-_-;;
  • Lyn 2009/06/06 20:05 # 삭제 답글

    숑카 // 승리의 쩜넷?
  • Lyn 2009/06/06 20:05 # 삭제 답글

    아 자세히보니 커피네 ㅡ,.ㅡ
덧글 입력 영역