[출처 : http://blog.naver.com/nlp1/150079485722]


 한글

 구분  시작  끝
 한글(자음, 모음)  1100  11FF
 호환용 한글(자음, 모음)  3131  318F
 한글 음절(가~힣)  AC00  D7A3

 

한자

 구분  시작  끝
 한중일 부수 보충  2E80  2EFF
 한중일 통합 한자 확장 - A  3400  4DBF
 한중일 통합 한자  4E00  9FBF
 한중일 호환용 한자  F900  FAFF
 한중일 통합 한자 확장  20000  2A6DF
 한중일 호환용 한자 보충  2F800  2FA1F

 

일어

 구분  시작  끝
 하라가나  3040  309F
 가타카나  30A0  30FF
 가타카나 음성 확장  31F0  31FF
[출처 : http://bbs.python.or.kr/viewtopic.php?p=66561&sid=d7caa9e552c40cae5c64bd0169a5d73a]

>>> a = "\uc5ec\ub7ec" 
>>> a.decode('unicode-escape') 
u'\uc5ec\ub7ec' 
>>> print a.decode('unicode-escape') 
여러 



>>> import re 
>>> re.sub(r'\\u(\w+)', lambda m: unichr(int(m.group(1), 16)), a) 
u'\uc5ec\ub7ec' 
>>> print _ 
여러  
[출처 : http://lumitech.tistory.com/entry/Python-String-Unicode-translate-function]


문자(열) 교체 함수 str.translate(), unicode.translate() 사용법

우선 일반 문자열(unicode object 아닌 문자열)부터.

str.translate(tbl,del)두개의 인자를 받는다. tbl은 0~255의 ASCII문자를 각각 대체할 문자로 만든 매핑테이블이다. 말이 테이블이지 길이 256짜리 문자열이다. 즉, ASCII코드 32인 문자(스페이스)는 매핑테이블의 32번째 글자로 바꿔주는 것이다. 따라서 tbl은 꼭 길이 256인 문자열(str object)여야 한다.

string.translate(s, tbl, del) == s.translate(tbl, del) 이다.

 

바로 예제 ㄱㄱㅅ

 

  1. #원본 문자열. \n는 개행, \t는 탭문자, \b는 앞글자를 지운다.
    tstr = """가나다\n\t\b123 4\b5\b6\babcd efgh (789) -_-+ 라마바 사2사2 文字"""

    # 원본 문자열을 프린트
    print "original:", tstr

    # 간단한 translate. 각 바이트의 첫 비트로 translate
    tbl = ""
    for cc in range(256):
       if cc < 128: tbl += "0"
       else: tbl += "1"
    print "tbl =", tbl
    print "initial bit:", tstr.translate(tbl)

 

컨트롤 캐릭터를 지우는데 활용해 보기.

 

  1. # ascii code값이 32미만(컨트롤캐릭터)이면 32(space)로 바꾸는 매핑테이블. \n \r \t \b 이런거 다 공백으로 바뀐다.
    TRMAP_ASCII_CTRL2SPACE = "".join([chr(max(32,cc)) for cc in range(256)])
    print "ctrl to space:", tstr.translate(TRMAP_ASCII_CTRL2SPACE)

    # 컨트롤 캐릭터를 공백으로 바꾸고 홀수는 지우는 변환.
    print "ctrl to space deleting '13579':", tstr.translate(TRMAP_ASCII_CTRL2SPACE, "13579")

 

컨트롤 캐릭터를 공백으로 바꾸지 않고 del인자를 써서 삭제할 수도 있다.

  1. # 아무것도 바꾸지 않는 매핑 테이블
    TRMAP_NOCHANGE = "".join([chr(cc) for cc in range(256)])

    # 컨트롤 캐릭터만 삭제
    ASCII_CTRLCHARS = "".join([chr(cc) for cc in range(32)])
    print "delete ctrl chars:", tstr.translate(TRMAP_NOCHANGE, ASCII_CTRLCHARS)

 

character code가 127이하인 문자를 삭제하는 del인자를 써서 한글, 한자, 일어 등 멀티바이트문자만 남길 수도 있다. 간단하므로 예제 코드 생략.

 

다음은 unicode.translate()

이건 사용법은 비슷하지만 두가지 차이점이 있다. 첫째, 매핑 테이블은 진짜-_-테이블이라는것. 둘째, del 인자를 주지 않는다는 것.

매핑테이블은 dict로 정의한다. 그래서 메모리를 크게 잡아먹을 수도 있다. 그래도 함수 수행자체는 뎁따 빠르므로 활용가치가 높다.

매핑 테이블을 만드는 유용한 함수를 하나 같이 소개한다.

__builtins__.zip(seq1, seq2, ... ) 함수는 리스트를 반환하는데, seq1, seq2, ... 에서 순서대로 하나씩 꺼내서 tuple로 묶은 리스트를 반환한다.

즉, zip([1,2,3], ('a', 'b', 'c')) 는 [(1,'a'), (2,'b'), (3,'c')] 를 리턴한다.

dict object는 zip의 결과를 초기화 인수로 받을 수 있도록 만들어져 있으므로, 이걸 활용하면 unicode translate 맵핑테이블을 쉽게 작성할 수 있다.

 

한글이 나타나는 unicode range가 정해져 있으므로, 한글을 공백으로 바꿔버리는 예제를 만들어 보자.

  1. # 위에서 사용한 테스트 문자열을 unicode로 바꾼다.
    utstr = unicode(tstr, "utf8")

    # unicode에서 한글이 나타나는 영역이다.
    __unicode_range_hangul = range(0xac00, 0xd7a4)
    __unicode_range_hangul_jamo1 = range(0x1100, 0x11fa)
    __unicode_range_hangul_jamo2 = range(0x3131, 0x318f)
    __unicode_range_korean = __unicode_range_hangul + __unicode_range_hangul_jamo1 + __unicode_range_hangul_jamo2

    __build_mapping_range2char = lambda r,uc: zip(r, uc * len(r)) 

    # 한글을 모두 공백으로 바꿔버리는 매핑테이블
    trmap_korean = dict(__build_mapping_range2char(__unicode_range_korean, u" "))

    print "utstr:", utstr
    print "utstr without Korean chars:", utstr.translate(trmap_korean)

 

삭제를 하려면 매핑 테이블에서 빈 문자열( u"" )을 주면 된다. __build_mapping_range2char() 람다함수를 조금 고치면 된다. 예제 생략.

[출처 : http://mygony.com/archives/1110]


utf-8 한글을 자르기 위한 함수입니다. javascript 처럼 멀티바이트 글자에 대해 1문자로 취급할 수도 있도록 했습니다. 원래대로라면 utf-8 으로 된 한글은 3byte 이지만, 익숙한 계산법을 따르기 위해서 이 함수에서는 2byte인 것처럼 취급합니다. 즉, 다음과 같습니다.

원본
한글과 English를 혼용해서 씁니다.
checkmb=true, len=10
한글과 Eng (한글=2*3 + 공백=1*1 + 영문=1*1 => 10)

checkmb=false, len=10
한글과 Englis (모두 합쳐 10자)

 $a = '한글hangul도 포함include되었습니다.';

 
/**
* cut string in utf-8
* @author gony (http://mygony.com)
* @param $str     source string
* @param $len     cut length
* @param $checkmb if this argument is true, the function treats multibyte character as two bytes. default value is false.
* @param $tail    abbreviation symbol
* @return string  processed string
*/
function strcut_utf8($str, $len, $checkmb=false, $tail='...') {
    preg_match_all('/[\xEA-\xED][\x80-\xFF]{2}|./', $str, $match);
 
    $m    = $match[0];
    $slen = strlen($str);  // length of source string
    $tlen = strlen($tail); // length of tail string
    $mlen = count($m); // length of matched characters
 
    if ($slen <= $len) return $str;
    if (!$checkmb && $mlen <= $len) return $str;
 
    $ret   = array();
    $count = 0;
 
    for ($i=0; $i < $len; $i++) {
        $count += ($checkmb && strlen($m[$i]) > 1)?2:1;
 
        if ($count + $tlen > $len) break;
        $ret[] = $m[$i];
    }
 
    return join('', $ret).$tail;
}


파라미터 설명 
String $str : 원본 문자열
Integer $len : 문자열을 자를 길이
Boolean $checkmb : 이 값을 true로 하면 한글을 영문2자와 같이 취급한다. 기본값은 false
String $tail : 생략후 붙일 줄임 기호

반환값
{String} 처리된 문자열

주의! 이 함수는 UTF-8 문자열을 다룹니다. 다른 charset 에는 적용되지 않습니다.

mb_ 계열 함수가 없는 환경을 위해서 만들어봤습니다.

거의 없을 줄 알았는데 아직도 그런 환경이 있기는 있더라구요. ^^;; 
[출처 : http://www.ezslookingaround.com/blog/tech/?no=636]


 1. 파일 매칭과 정규식을 혼동하지 말자 


    우선 주의해야 할 것이 있어 먼저 말해 둡니다. 
    Shell이 파일 패턴 매칭에 쓰는 와일드카드(wildcard)와 정규식을 혼동해서는 안됩니다. 
    또한 와일드 카드와 정규식은 공통적으로 다음과 같은 문자들을 특별하게 취급합니다: 
    `*'와 `?', `()', `[]', `|'. 


    Shell과 find, cpio와 같은 프로그램들은 정규식이 아닌 파일 패턴 매칭을 위해 와일드카드 
    문자를 쓴다는 것을 먼저 기억해 두어야 합니다. 

    또한 이러한 특수문자들은 shell에 의해 확장되어 버리기 때문에 미리 따옴표로 둘러싸야 합니다. 
    아래의 명령을 입력하면: 

    $ grep [A-Z]*.c chap[12]

    Shell은 다음과 같이 확장할 것입니다: 

    grep Array.c Bug.c Comp.c chap1 chap2

    가장 간단한 방법은 의심스러운 부분은 모두 작은 따옴표인 `''으로 둘러싸는 것입니다. 


2. Using M-etacharacters in Regular Expression 



    정규식을 쓸 때에는 다음 세가지를 고려해야 합니다: 

        1) Anchor는 텍스트 한 줄에서 패턴이 위치하는 곳을 지정하며, 
        2) Character들은 그 위치에서 하나 이상의 문자에 매치(match)되며, 
        3) Modifier는 앞에 나온 문자 집합의 반복 횟수를 지정합니다. 



    Caret (^) 문자는 줄의 첫 부분이라는 것을 나타내는 anchor이며, 
    hash mark (#)는 단순히 문자 자체를 의미합니다. 
    Asterisk (*)는 modifier이며, 이 의미는 앞에 나온 문자 집합이 0개 이상 있다는 뜻입니다. 
    1개 이상이 아닌 0개 이상인 것에 주의하기 바랍니다. 



    정규식은 크게 단순 정규식1과 확장 정규식2으로 나눌 수 있습니다. 
    awk, egrep과 같은 몇몇의 프로그램은 확장 정규식을 지원하며, 
    나머지 대부분의 프로그램은 단순 정규식 표현만 지원합니다. 
    이 프로그램들에는 vi, sed, grep, csplit, dbx, more, ed, expr, lex등이 있습니다. 


3. Anchors 



    패턴의 위치를 지정하는 anchor에는 ^와 $가 있습니다. 
    전자는 start anchor라고 하며, 후자는 end anchor라고 합니다. 
    각각은 텍스트 한 줄에서 첫 부분, 끝 부분을 의미합니다. 
    따라서 정규식 "^A''는 문자 'A'이되, 문장의 처음에 나오는 'A'를 의미합니다. 
    마찬가지로 "$A''는 문자 'A'이되, 문장의 마지막에 나오는 'A'만을 의미합니다. 


    두 anchor 모두 제 위치에 있을 때에만 그 역할을 발휘합니다. 
    예를 들어 정규식 "1^''나 "$1''에서의 ^, $는 그 역할을 하지 못하고, 
    단순히 일반 문자로 해석됩니다. 만약 실제로 문장의 첫 부분에 오는 ^이나, 
    문장의 마지막에 오는 $를 원한다면 이들 문자 앞에 \를 붙이면 됩니다. 


        표1: Regular Expression Anchor Character Examples  



        패턴        Matches
        -----------------------------------------------
        ^A           문장의 처음에 나오는 'A'
        A$            문장의 마지막에 나오는 'A'
        A^           아무곳에나 올수 있는 'A^'
        $A            아무곳에나 올수 있는 '$A'
        ^\^       문장의 처음에 나오는 '^'
        ^^           '^\^'과 동일
        \$$         문장의 마지막에 나오는 '$'


    이 문자들은 단지 정규식이 아닌 비슷한 의미로도 많이 쓰입니다. 
    예를 들어 vi의 명령 모드에서 `$'는 문장의 끝으로 커서를 이동하는 데에 쓰이며, 
    `^'는 문장의 처음으로 이동시키는 데에 쓰입니다. 


    또한 C shell에서 `!^'는 바로 전 줄의 첫번째 인자를 지정하는데에 쓰이고, 
    `!$'는 마지막 인자를 지정하는데에 쓰입니다. 

    다른 프로그램에서도 비슷한 명령들을 볼 수 있습니다. 
    ed와 sed에서 `$'는 파일의 마지막 줄을 의미하며, 
    `cat -v -e' 명령은 각 줄의 끝을 `$'로 나타내게 해 줍니다. 



4. Matching a Character with a Character Set 



    가장 간단한 문자 집합은 한 문자로 나타내는 것입니다. 
    예를 들어 정규식 `the'는 `t', `h', `e'의 세 문자로 이루어진 것이며, 문자열 `the'에만 match됩니다. 
    그러나 이 정규식은 `the' 뿐만 아니라 `other'에도 match될 수 있습니다. 
    이를 방지하려면 정규식의 앞에 (또는 뒷 부분, 또는 앞뒤 모두에) 공백을 주어 
    ` the'로 쓰면 `other'와 같은 단어에 match되는 것을 막을 수 있습니다. 



    앞에서 배운 anchor와 조합하면 여러가지로 응용할 수 있습니다. 
    예를 들어 자신에게 온 편지 중 보낸이의 목록을 알고 싶다면 다음과 같이 할 수 있습니다. 
    e-mail 형식상 보낸이는 ``From: xxx''의 형태로 기록됩니다. 
    따라서 정규식 `From'을 grep 명령에 쓰면 보낸이의 목록을 뽑을 수 있습니다. 
    하지만 단어 ``From''은 매우 빈번하게 쓰이는 것이라 필요없는 줄까지 출력할 경우가 예상됩니다. 
    이 때, 앞에서 배운 anchor인 `^'를 써서 다음과 같이 하면 - 100% 보장할 순 없지만 - 보낸이의 목록만을 얻을 수 있습니다: 


    $ grep '^From: ' /usr/spool/mail/$USER



    앞으로 설명할 몇몇 문자들은 정규식에서 특별한 뜻을 가집니다. 
    이러한 문자들을 글자 그대로의 의미로 쓰고 싶다면 앞에다 `\'를 붙이면 됩니다. 



5. Match any Character with `.' (Dot) 



    Dot (.) 문자는 정규식에서 한 글자에 해당합니다. 글자의 종류는 상관없습니다. 
    따라서 정규식 `...'은 세 글자로 구성된 단어에 해당합니다. 
    또한 한 글자로만 된 줄은 `^.$'로 나타낼 수 있습니다. 



6. Specifying a Range of Characters with [...] 



    특정 범위에 해당하는 글자를 나타내고 싶다면 [] 사이에 넣으면 됩니다. 
    예를 들어 `A', `B', `C' 세 글자 중 한 글자에 해당하는 정규식은 `[ABC]'입니다. 
    또한 한 숫자로만 이루어진 줄은 `^[0123456789]'로 쓸 수 있습니다. 
    Hyphen (`-') 문자를 써서 범위로 지정할 수 있습니다. 


    예를 들어 위에서 한 숫자만으로 이루어진 줄은 간단히 `^[0-9]'로 나타낼 수 있습니다. 
    알파벳, 숫자, 밑줄 문자 중 한 글자에 해당하는 정규식은 `[A-Za-z0-9_]입니다. 

    좀 더 복잡한 예를 들어 보면: 



    줄의 첫 문자가 `T'이고, 
    다음으로 소문자 한 글자가 나오고, 
    다음으로 알파벳 소문자 모음(vowel)이 나오고, 
    세 글자로 이루어진 (네 번째 문자가 `') 
    정규식은 `^T[a-z][aeiou] '입니다. 



7. Exceptions in a Character Set 



    []을 쓰면 주어진 범위에 해당하는 문자를 찾을 수 있습니다. 
    반대로 `^'을 함께 쓰면 주어진 범위에 해당하지 않은 문자를 찾습니다. 
    예를 들어 `[^aeiou]는 소문자 모음이 아닌 문자에 해당하는 정규식입니다. 
    이 때 `^' 문자는 반드시 `[' 다음에 와야 합니다. 


    다른 anchor들과 마찬가지로 제 자리에 오지 않은 문자들은 특별한 뜻을 가지지 않습니다. 
    예를 들어 `[' 문자 바로 뒤에 나오는 `]' 문자나 `[' 뒤에 바로 나오는 `-'은 일반 문자로 취급됩니다. 
    표2를 참고하기 바랍니다. 


        표 2: Regular Expression Character Set Examples 

        Regular Expression      Matches
        ------------------------------------------------------
        [0-9]                            숫자중에 한 글자
        [^0-9]                          숫자가 아닌 한 글자
        [-0-9]                           숫자나 '-' 한글자
        [^-0-9]                        숫자와 '-'가 아닌 한글자



8. Repeating Characteer Sets with * 



    정규식의 마지막 부분인 `modifier'는 바로 앞의 패턴의 반복 횟수를 지정합니다. 
    `*' 문자는 바로 앞 패턴이 0번 이상 나온다는 것을 의미합니다. 
    따라서 `0*'는 문자 `0'이 0개 이상 나올 수 있다는 것을 의미합니다. 
    따라서 간단한 10진 수치는 `[0-9]*'으로 표현할 수 있을 것입니다. 
    조심할 것은 1개 이상이 아닌 0개 이상이라는 것입니다. 


    예를 들어 문장의 첫 부분에서 `#'로 시작하는 단어를 찾기 위해 `^#*'를 쓴다면 
    `#'로 시작하는 줄은 당연히 포함되고, - 0번 반복일 경우 - `#'로 시작하지 않는 줄로 포함됩니다. 
    따라서 앞에서 다룬 10진 수치의 경우 좀 더 정확한 표현은 `[0-9][0-9]*'이 됩니다. 


9. Matching a Specific Number of Sets with \{ and \}



    `*'만으로는 최대 횟수를 지정할 수 없습니다. 
    몇몇 프로그램들에선 다음과 같은 횟수를 지정하는 정규식을 쓸 수 있습니다. 
    이는 두 개의 쌍으로 이루어진 특수 표현을 사용합니다: `\{'와 `\}'. 

    예를 들어 소문자 4 개에서 8개로 이루어진 문자열은 [a-z]\{4,8\}에 해당합니다. 
    횟수를 지정할 때 쓰이는 수치는 0에서 255 사이의 수치면 됩니다. 


    만약 두 번째 수치가 생략되면 *와 비슷하게 상위 경계에 적용받지 않습니다. 
    따라서 특수 문자 *는 \{0,\}와 같은 뜻입니다 .와 마찬가지로 이는 앞 패턴의 횟수를 지정하기 위해 쓰입니다. 
    따라서 이 표현이 문장의 처음에 나온다면 modifier의 기능을 잃고 일반 글자로 쓰이게 됩니다. 



        표 3: Regular Expression Pattern Repetition Examples 

        Regular Expression      Matches
        ------------------------------------------------------
        *                                  *로 시작되는 줄
        ^A*                              아무줄이나 해당
        ^AA*                           하나 이상의 A로 시작되는 줄
        ^AA*B                         하나 이상의 A로 시작되고 B로 끝나는 줄
        ^A\{4,8\}B              4~8개의 A자가 오고 B로 끝나는 줄


10. Matching words with \< with \>



    단어를 찾는 것은 때때로 복잡한 일이 될 수 있습니다. 
    예를 들어 `the'를 찾기 위해 the를 쓰더라도 `other'에 매치되는 경우가 있습니다. 
    이를 방지하기 위해 앞뒤에 공백을 넣는다 하더라도 `the'라는 단어가 줄의 맨 앞 부분이나
    뒷부분에 있다면 이 방법으로는 찾을 수 없습니다. 


    ed, ex, vi와 같은 프로그램은 이런 경우 단어(word) 단위로 검색할 수 있게 \<와 \>를 지원합니다. 
    즉 정규식 \<the\>는 `the'에는 매치되지만 `other'에는 매치되지 않습니다. 



11. Remembering Patterns with \(, \), and \1



    반복되는 단어를 찾을 때, 쓸 수 있는 유융한 기능입니다. 
    예를 들어 같은 글자가 반복되는, 두 글자로 이루어진 패턴을 찾을 때, [a-z][a-z]는 거의 쓸모가 없습니다. 
    \(와 \)는 이 사이의 패턴을 기억해 줍니다. 실제 이 패턴은 \1로 쓸 수 있습니다. 
    예를 들어 같은 글자가 반복되는, 두 글자로 이루어진 패턴은 \([a-z]\)\1로 쓸 수 있습니다.


    한 정규식에 여러 쌍의 \(와 \)가 있다면 \1, \2, \3등으로 쓸 수 있습니다. 최대 9까지 기억할 수 있습니다. 
    `Palindrome'이라는 것이 있습니다. 간단히 말해 앞으로 쓰나 뒤로 쓰나 같은 단어를 말합니다. 
    예를 들면 `radar'가 있습니다. 5글자로 된 palindrome을 찾으려면 정규식 \([a-z]\)\([a-z]\)[a-z]\2\1을 씁니다. 

    이는 특히 sed를 써서 문자열을 치환할 때에도 유융하게 쓰입니다. 


12. Potential Problem 



    확장 정규식을 다루기 전에 먼저 알고 넘어갈 것이 있습니다. 
    \<, \> 문자는 vi에서 처음 사용된 것입니다. 따라서 다른 프로그램에서는 지원하지 않을 수도 있습니다. 
    또한 \{, \}도 새로 등장한 것입니다. 오래된 프로그램에서는 지원하지 않을 가능성이 높습니다. 
    초보자가 흥미를 잃는 가장 큰 이유는 바로 이런 것 때문입니다. 


    즉 정규식은 그 종류가 다양하고 또, 100% 지원하는 프로그램이 드물기 때문입니다. 
    그러나 최근에 만들어진 대부분의 프로그램들은 대부분의 정규식을 지원하기 때문에 
    사용자의 시스템이 특별히 오래된 것이 아니면 무난하게 쓸 수 있을 것입니다. 
    또 하나의 문제는 조금 골치가 아픈 것입니다. 



    예를 들어 A.*B는 `AAB'에도 해당하지만 `AAAABBBBABCCCCBBBAAAB'에도 해당합니다. 
    얼핏 보면 문제가 없을 것 같지만 심각합니다. 예를 들어 위의 긴 문자열은 자체가 A.*B에 매치될 수도 있지만 
    작게 쪼개면 `AAAAB'도 정규식에 해당하고 `AB'도 해당하고, 마지막 `AAAB'도 해당하는 등, 여러 가지 경우가 나옵니다. 
    grep과 같은 프로그램은 단순히 주어진 정규식에 해당하는 줄을 출력해 주는 역할을 하므로 별 문제가 되지 않지만 
    sed와 같은 프로그램을 써서 정규식에 해당하는 표현을 다른 것으로 바꾼다고 할 때 문제가 발생합니다. 
    따라서 정규식을 써서 무언가를 바꾸거나 제거할 때에는 주의해야 합니다. 
    참고로 이런 경우에 정규식은 항상 가장 긴 문자열에 매치되려고 합니다. 


13. Regular Expression Examples 



    이 장에서는 정규식을 써서 만든 간단한 예제를 모았습니다. 



    미국 주(State) 표시: (NM) 
    [A-Z][A-Z]

    미국 주, 시(city) 표시 (Portland, OR) 
    ^.*, [A-Z][A-Z]

    미국식 날짜 표시 (JAN 05, 1993) (January 5, 1993) 
    [A-Z][A-Za-z]\{2,8\} [0-9]\{1,2\}, [0-9]\{4\}

    미국 주민번호 (123-45-6789) 
    [0-9]\{3\}-[0-9]\{2\}-[0-9]\{4\}

    전화번호 (547-5800) 
    [0-9]\{3\}-[0-9]\{4\}

    달러 ($1) ($ 1000000.00) 
    \$ *[0-9]+(\.[0-9][0-9])?

    HTML 태그 (<H2>) (<UL COMPACT>) 
    <[^>]*>

    빈 줄 
    ^$

    줄 전체 
    ^.*$

    하나 이상의 공백 
      *
----------------------------------------------

솔직히 어디서 퍼왔는지 출처를 알수 없네요..

작성해주신분께 감사드리며...
[출처 : http://divestudy.tistory.com/8]


사실 한글 인코딩에는 관심이 없었다. 그냥 대충 처리해도 잘 보고 잘 쓸수 있기때문에??

이번에 초성검색에 대한 문제를 해결하는데 문제가 생겼다.
한글이 어떻게 저장 되있냐에 따라 초성 검색 방식이 달라지기 때문이다.
어줍잖은 지식으로 조합형으로 되있겠지라고 했지만 이게 왠걸?!
조합형으로 저장을 안하네 ㅎㄷㄷ 시스템마다 다름 윈도우는 기본적으로 euc-kr(완성형)
어쨌든 우분투에서 만들어야 하니 우분투 인코딩셋에대해서 알아본 결과
UTF-8 이란것을 알수 있었다 (locale 명령어로 알수 있음)
UTF-8 이라.. 어서 많이 들어 본 단어 인데;; 브라우져에서도 보고 파일 전송때도 보고..
알고 보니 UNICODE를 표현하는 방법 중에 하나라고 한다.(UTF-8 뿐만아니라 -16, -32 등도 있음)
어쨌든!! 이런건 인터넷에 널려 있으니 참고 하면 되고 변환 방법을 알아보자!!

유니코드는 전세계 모든언어를 표기하기위해 만들어졌지만 한글만 변환하므로 이에 관련된것만 보겠다.
"가"는 UNICODE로 AC 00 (16 진수 표기 , UAC00 라고도 표기한다 앞의 U는 유니코드를 나타낼때) 이고
U-00000800 - U-0000FFFF 범위에 들어가므로 비트 마스크(?)는 1110xxxx 10xxxxxx 10xxxxxx 이다.

즉, UTF-8로 표기시 1110xxxx 10xxxxxx 10xxxxxx 형태가 되며 x는 AC00를 순서대로 넣어주면 된다.
AC00 => 1010 1100 0000 0000 이며 차례 대로 넣어주면

11101010 10110000 10000000 로 변환된다. 이것을 16진수로 바꿔 주면 EA B0 80 이 된다.

다음으로 알아볼것은 UNICODE를 초성, 중성, 종성으로 분리하는 방법이다.

"갈"을 분리 해보자! "갈"은 유니코드로 UAC08이다.

1) 분리할 문자를 AC00으로 뺀다 ( 0xAC08 - 0xAC00 = 0x8)
2) 1의 결과를 21로 나누고 28로 나눈 몫은 초성이다. (8/21/28 = 0)
3) 1의 결과를 21*28로 나눈 나머지와 28로 나눈 몫은 중성이다 ( (8 % (21*28)) / 28 = 0)
4) 1의 결과를 28로 나눈 나머지는 종성이다 ( 8 % 28  = 8 )

초성은  0  중성은  0  종성은 8을 테이블에서 찾으면 된다.

초성 테이블

10  11  12  13  14  15  16  17  18 
 ㄱ  ㄲ   ㄴ  ㄷ  ㄸ  ㄹ  ㅁ  ㅂ  ㅃ  ㅅ  ㅆ  ㅇ  ㅈ   ㅉ   ㅊ   ㅋ   ㅌ   ㅍ   ㅎ 

중성 테이블
 0  1  2  3  4  5  6  7  8 10  11  12  13  14  15  16  17  18 
 ㅏ  ㅐ  ㅑ  ㅒ  ㅓ  ㅔ  ㅕ  ㅖ  ㅗ  ㅠ  ㅘ ㅛ   ㅙ  ㅚ  ㅜ  ㅝ  ㅞ  ㅟ  ㅡ
 19 20 
 ㅢ  ㅣ  

종성 테이블
 0  1  2  3  4  5  6  7  8 10  11  12  13  14   15  16  17  18
   ㄱ  ㄴ  ㄵ  ㄶ  ㄷ   ㄹ   ㄺ   ㄻ   ㄼ  ㄽ  ㄾ  ㄿ  ㅀ   ㅁ  ㅂ  ㅄ 
19  20  21  22  23  24  25  26  27
ㅅ  ㅆ  ㅇ   ㅈ  ㅊ  ㅋ   ㅌ  ㅍ  ㅎ 


"ㄱ" + "ㅏ" + "ㄹ" 인걸 알수 있다11 이젠 초성 검색 ㄱㄱ씽!!!
[출처 : http://ryu2811.tistory.com/23]


__attribute__ ((packed)) : 구조체 내의 최대 사이즈 데이터 타입으로 alignment 하지 않도록한다.

************************************************************
#include <stdio.h>

typedef struct _test_t_
{
    unsigned char uc1;
    unsigned char uc2;
    unsigned long long ui1;
} test_t;

typedef struct _testp_t_
{
    unsigned char uc1;
    unsigned
    char uc2;
    unsigned long long ui1;
} __attribute__ ((packed)) testp_t;

int main (void)
{
    test_t t1;
    testp_t t2;

    printf ("test_t : %d\n", sizeof (t1));
    printf ("testp_t : %d\n", sizeof (t2));

    return 0;
}
************************************************************
[ryu2811@qwertyryu-home ~/work/src/test] ./kkk
test_t : 16
testp_t : 10

[출처 : http://studyfoss.egloos.com/5409933]


gcc: 4.5.0
arch: x86_64


정렬 제한이란 데이터가 메모리에 저장될 때 해당 메모리의 주소에 대한 제약 사항을 말하는 것이다.
구체적으로는 데이터가 저장될 위치가 특정한 단위로 정렬(배치?) 되어 있어야 한다는 것을 뜻한다.

예를 들어 CPU 아키텍처에서 메모리 접근 시에 32비트 단위로 정렬된 주소에만 접근할 수 있다고 하자.


위의 데이터 A는 주소값이 4의 배수 형태이므로 정렬되어 있지만, B의 경우는 그렇지 않다.
B의 경우 해당 데이터에 접근 시 실행 환경에 따라 다음과 같은 여러 가지 결과를 일으킬 수 있다.
  • 비정상적인 메모리 접근을 인식하여 OS가 프로그램을 종료시킨다.
  • CPU 혹은 컴파일러가 B가 속한 두 메모리 영역에 접근한 뒤 적절한 비트 연산을 거쳐 원하는 값을 만들어준다.
  • 주소를 적당히 4의 배수로 변경하여 메모리에 접근한다.
  • 별 문제없이 실행된다.

어떤 경우이든 정렬되지 않은 메모리에 대한 접근은 성능에 도움이 되지 않는다.
최악(?)의 경우 B가 걸쳐있는 두 메모리 워드가 각자 다른 캐시 라인에 속할 수도 있음을 생각해 보자.
따라서 컴파일러는 컴파일 시에 이러한 정렬 제한을 고려하여 데이터를 메모리 상에 배치한다.
(물론 최종적으로는 링커가 이러한 작업을 수행한다.)

C 언어의 기본 데이터 타입은 모두 자신의 크기와 동일한 정렬 제한을 가진다.
gcc에서 이러한 정보는 sizeof와 __alignof__를 통해서 얻을 수 있다.

/* align-basic.c */

#include <stdio.h>

char c;
short s;
int i;
long l;
float f;
double d;

int main(void)
{
#define print_align(type, var)                        \
  printf(#type "\t%zd\t%zd\t%p\n", sizeof(type), __alignof__(type), &var)

  printf("type\tsize\talign\taddress\n");
  print_align(char, c);
  print_align(short, s);
  print_align(int, i);
  print_align(long, l);
  print_align(float, f);
  print_align(double, d);

  return 0;
}

위의 예제 프로그램을 빌드한 후 실행해보면 다음과 같은 결과를 얻을 수 있다.

$ gcc align-basic.c
$ ./a.out
type     size    align    address
char     1       1        0x40209a
short    2       2        0x402098
int      4       4        0x402094
long     8       8        0x402088
float    4       4        0x402090
double   8       8        0x402080

데이터 타입의 크기와 정렬 제한은 모두 동일한 크기를 가지며
실제 변수가 생성된 주소도 이러한 정렬 제한에 맞추어 졌음을 볼 수 있다.
좀 더 자세히 살펴보면 동일한 크기/정렬 제한을 가지는 데이터끼리 묶어서 배치하였으며
공간을 효율적으로 사용하기 위해 큰 크기/정렬 제한을 가지는 데이터부터 배치한 것도 볼 수 있다.

정렬 제한은 포인터 변환 시에도 반드시 고려해야 할 부분인데
cast 연산을 통해 강제로 변환한 주소값이 변환된 포인터가 가리키는 데이터 타입의
정렬 제한을 어길 수 있기 때문이다. 예를 들어 위의 예제에서
변수 i의 주소를 long * 타입으로 변환한다면 long 타입이 요구하는 8 바이트의 정렬 제한을
만족시키지 못하므로 해당 포인터를 통해 메모리에 접근 시 위에서 언급한 결과 중의 하나를 얻게 될 것이다.

이제 본격적으로 구조체에 대해서 살펴보기로 하자.
구조체는 여러 데이터 타입을 모아둔 집합체의 개념이므로 구조체를 이루는 각 멤버에 대해서도
위에서 언급한 정렬 제한을 모두 만족시켜야 한다. 또한 구조체는 위의 예제에서와 같이
메모리를 효율적으로 사용하기 위해 구조체 멤버의 순서를 임의로 조정할 수 없으며
반드시 구조체 선언 시에 명시된 순서대로 멤버를 메모리 상에 배치해야 한다.
따라서 이를 조정하기 위해 멤버 사이에 사용하지 않는 패딩 공간을 할당할 수 있다!!

구체적인 예를 통해 살펴보기로 하자.

/* align-struct.c */

#include <stdio.h>

struct sc {
  char c;
} sc;

struct sl {
  long l;
} sl;

struct scl {
  char c;
  long l;
} scl;

struct slc {
  long l;
  char c;
} slc;

int main(void)
{
#define print_align(type, var)                        \
  printf(#type "\t%zd\t%zd\t%p\n", sizeof(type), __alignof__(type), &var)

  printf("type\t\tsize\talign\taddress\n");
  print_align(struct sc, sc);
  print_align(struct sl, sl);
  print_align(struct scl, scl);
  print_align(struct slc, slc);

  return 0;
}

실행 결과를 살펴보기 전에 먼저 결과가 어떻게 나오게될 지 생각해 보자.
멤버로 오직 하나의 데이터 만을 가지고 있는 sc와 sl의 경우는
단순히 해당 멤버의 크기와 정렬 제한을 그대로 갖게될 것이다.
하지만 scl과 slc와 같이 서로 다른 데이터 타입으로 이루어진 경우라면 어떨까?

위의 예제를 실행해 보면 다음과 같은 결과를 얻을 수 있다.

$ gcc align-struct.c
$ ./a.out
type          size    align    address
struct sc     1       1        0x4020a8
struct sl     8       8        0x4020a0
struct scl    16      8        0x402080
struct slc    16      8        0x402090

앞서 말한대로 sc와 sl의 경우에는 특별한 사항이 없다.

scl의 경우에는 우선 정렬 제한이 8이라고 나오는데 이는 멤버 중의 가장 큰 정렬 제한을
가지는 long 타입의 정렬 제한을 그대로 물려받은 것이다.
또한 크기는 9가 아닌 16으로 나오는데 이는 8 바이트 단위로 정렬된 위치에 첫 멤버인
char 타입의 데이터가 저장되고 그 이후에 long 타입의 데이터가 다시 8바이트 단위로
정렬된 위치에 저장되기 때문이다. 즉 1~7번 바이트는 사용되지 않고 단순히 long 타입의
멤버의 정렬 제한을 보장하기 위한 용도로 채워진 패딩 바이트인 것이다.

구조체 내의 각 멤버의 위치는 offsetof 매크로를 통해 조사할 수 있는데
위의 경우 offsetof(struct scl, l)의 값은 8이 될 것이다.

slc의 경우에는 마찬가지로 정렬 제한이 8이고 크기가 16으로 나오는데
정렬 제한이야 그렇다 하겠지만 크기는 왜 9가 아닌 16으로 나오는 것일까?
만약 크기가 9이더라도 long 타입과 char 타입의 정렬 제한을 이미 모두 만족시키고 있는데
왜 굳이 구조체 뒤쪽에 불필요한 패딩 바이트를 추가하여 크기를 크게 만들었을까?

그 해답은 바로 배열 때문이다.
알고 있듯이 배열은 구조체와 같은 집합체이지만 동일한 데이터 타입으로 이루어진 것이며
중요한 사항은 배열을 이루는 각 원소들은 메모리 상에서 연속된 영역에 존재해야 하며
각 원소들 자체에 대해서도 정렬 제한을 만족시켜야 한다는 점이다.
만일 slc의 크기가 9가 된다면 배열의 다른 요소들은 정렬 제한을 만족하지 못할 것이므로
slc 내에 패딩을 포함시켜 slc의 크기 자체를 정렬 제한의 배수가 되도록 맞춘 것이다.

이렇게 구조체 내의 패딩 바이트가 추가되는 규칙을 알고 있다면
구조체 선언 시 각 멤버들의 위치를 잘 선택하여 효율적인 메모리 배치를 이루도록 할 수 있을 것이다.

하지만 상황에 따라 이러한 구조체의 크기/정렬 제한을 조정해야 하는 경우가 있을 수 있는데
(일반적으로는 그리 추천할 만 한 방법은 아닐 것이다)
이 때는 gcc에서 확장 기능으로 제공하는 aligned 혹은 packed 속성을 이용하면 된다.

역시 예제를 통해 살펴보기로 하자.

/* align-adjust.c */

#include <stdio.h>

struct asc {
  char c __attribute__((aligned(8)));
} sc;

struct __attribute__((aligned)) asl {
  long l;
} sl;

struct __attribute__((packed)) pscl {
  char c;
  long l;
} scl;

#pragma pack(push, 2)
struct pslc {
  long l;
  char c;
} slc;
#pragma pack(pop)

int main(void)
{
#define print_align(type, var)                        \
  printf(#type "\t%zd\t%zd\t%p\n", sizeof(type), __alignof__(type), &var)

  printf("type\t\tsize\talign\taddress\n");
  print_align(struct asc, sc);
  print_align(struct asl, sl);
  print_align(struct pscl, scl);
  print_align(struct pslc, slc);

  return 0;
}

앞서 말한대로 구조체의 정렬 제한은 멤버 중 가장 큰 값을 물려받는다.
따라서 구조체의 정렬 제한을 변경하는 대신 멤버의 정렬 제한을 변경한 경우에도
구조체에 영향을 미치게 된다. aligned 속성에는 2의 제곱수에 해당하는 숫자 만을
인자로 사용할 수 있으며 기본 정렬 제한보다 큰 값을 적용하는 경우에만 의미가 있다.
인자를 생략한 경우 실행 환경에 가장 적합한 정렬 제한을 가지도록 컴파일러가 결정한다.
반대로 packed 속성의 경우에는 정렬 제한을 가장 작은 수인 1로 줄이게 되며
1이 아닌 값을 적용하기 위해서는 #pragma를 이용할 수 있다.

위의 예제를 실행하면 다음과 같은 결과를 얻을 수 있다.

$ gcc align-adjust.c
$ ./a.out
type          size    align    address
struct asc    8       8        0x4020a8
struct asl    16      16       0x402080
struct pscl   9       1        0x40209a
struct pslc   10      2        0x402090

구조체에 비트 필드가 추가되는 경우 보다 복잡/미묘한 상황이 발생할 수 있는데
이에 대해서는 "더 이상의 자세한 설명은 생략한다".. ;;


=== 참고 문헌 ===

+ Recent posts