2006년 9월 28일 목요일

비교에 대하여.

오랜만입니다. 이 핑계 저 핑계로 약속했던 연재가 늦어졌습니다. 이번엔 "비교"에 대해 이야기해 보죠. 수(數)의 비교에 대해선 우린 이미 잘 알고 있으니 문자열의 비교에 대해 이야기해 보겠습니다.

C#에서 문자열을 다루는 자료형인 string 클래스가 제공하는 문자열을 비교하는 방법에는 크게 다음 세가지가 있습니다. (더 있긴 하지만 이 세 가지를 이해하면 문자열의 비교에 관한 거의 모든 것을 할 수 있기 때문에 이 정도로 제한하겠습니다.)

1. Equals() 메쏘드
2. 비교 ( ==, != ) 연산자
3. Compare() 몌쏘드 (정적 메쏘드)

위 세가지 메쏘드가 비슷한 일을 하긴 합니다만, 각자 적절한 용도가 따로 있습니다.

Equals() 메쏘드도 두 문자열이 같은가/다른가를 비교해서 true/false를 반환합니다만, 옵션을 조금 줄수 있습니다. 예를 들어 대/소문자를 무시하고 비교할 것인지, 어떤 문화권의 문자열 비교를 수행할지 등을 옵션으로 넣어 사용할 수 있습니다.

string A = "ABC";
string B = "abc";

Console.WriteLine(A.Equals(B));  // false
Console.WriteLine(A.Equals(B, StringComparison.OrdinalIgnoreCase)); // true

다음은 비교 연산자를 이용한 비교입니다. C/C++ 프로그래밍 경험이 있던 분들은 뭔가 이상하다고 생각할 것입니다. "어떻게 비교 연산자가 문자열을 비교하는데 사용될 수 있지?"하고 말이죠. 여기에 무슨 마법이 있는 것은 아닙니다. 비교 연산자들을 오버로딩해서 문자열이 위치하는 메모리 주소가 아닌 "값"을 비교하도록 한 것뿐이니까요.

== 연산자는 문자열의 같은가/다른가를 단순 비교합니다. 같으면 true를 반환하고 다르면 false를 반환하지요. 예는 다음과 같습니다.

string a = "ABC";
strinb b = "CDE";

Console.WriteLine(a == b); // false

!= 연산자는 == 연산자의 반대 값을 반환한다는 것은 우리 모두 알고 있죠? 위 코드에서 a==b 대신 a!=b 를 입력하면 true가 출력됩니다.

Equals() 메쏘드와 비교 연산자를 이용한 문자열의 비교에 대해서는 이 정도로 마무리 하려 합니다. 제가 이야기 하고 싶은 부분은 사실 이 두 방법이 아니라 마지막 방법인 Compare() 메쏘드입니다. 그래도 마음이 동하는 분들은 코드를 직접 짜서 테스트를 하면서 더 연구해 보세요. :

Compare() 메쏘드는 다양한 비교 옵션을 제공하며, 비교 결과 또한 같다/다르다를 넘어서 작다/크다/같다로 세분화 됩니다. 그래서 반환형도 int 형이죠. string 클래스의 인스턴스보다 비교할 문자열이 크면 0보다 큰 값을, 같으면 0을, 그리고 작으면 0보다 작은 값을 반환합니다. 단, 이 메쏘드는 static으로 선언되었기 때문에 인스턴스 레벨에서 사용할 순 없고 클래스 레벨에서 호출해야 합니다. (인스턴스 레벨에서 제공하는 CompareTo() 메쏘드가 있습니다만, 이것과 Compare() 메쏘드는 다릅니다. CompareTo() 메쏘드는 작다/크다/같다의 결과를 반환하긴 하지만 기능 자체는 앞에서 소개한 두 메쏘드와 큰 차이가 없습니다.)

호오, 문자열 사이에 작다/크다의 개념이 있을 수 있나? 그럼 "abcde"가 "abc"보다 크다고 할 수 있는 걸까? 이것은 사실 문자열의 길이나 크기 따위의 문제가 아닙니다. 사전적 순서가 앞에 오느냐 또는 뒤에 오느냐에 따라 결정되는 문제죠. 사전적 순서로 따져서 앞에 오면 작다라고 판정되고 뒤에 오면 크다라고 판정되는 것입니다. 예를 들어 "abc"는 "abcde"보다 사전적으로 앞에 오므로 작다고 판정됩니다. 다음과 같이 말이죠.

string A = "abc";
string B = "abcde";

Console.WriteLine(String.Compare(A, B)); // -1 출력, 즉 "abc"는 "abcde"보다 작다.

이해 되시죠? 이러한 문자열의 사전적 순서에 근거한 비교를 사전적 비교(Lexical Comparison) 이라고 합니다.

이렇게 해서 Equals() 메쏘드, 비교 연산자, Compare() 메쏘드 세가지 방법을 이용한 문자열의 비교에 대해 알아봤습니다. 이 정도를 알면 C# 프로그래머가 알아야할 문자열의 비교는 안다고 할 수 있습니다.

하지만 여기에서 조금만 더 같이 생각해 보죠. 유럽 언어들은 대부분 로마자(Roman Alphabet)에 기반안 언어 체계를 갖고 있습니다. 얼핏보면 "어, 다 알파벳이네?"하는 생각이 들 정도로 비슷하지요. 하지만 각 나라마다 차이가 다 있습니다.

예를 들어 ch가 영어에서는 두 글자지만, 체코에서 ch는 한 글자로 사용됩니다. 헝가리어에서는 CS, DZ, DZS … 등 두 문자 이상이 합쳐져 한 문자를 이루기도 합니다. 이런 경우 문제가 생기기도 합니다.

예를 들어 헝가리에서 SI 프로젝트를 수주 받아 개발을 진행하고 있다고해 봅시다. 시스템 설계를 하다가 코드 테이블을 만들어야 할 필요가 생겼고, 그래서 두 글자의 약자로 이루어지는 코드 시스템을 만들고 이를 데이터베이스에 넣었습니다. 가령 Computer Science는 CS, Communication Technology는 CT 와 같이 말이죠.

그래서 만들어진 코드는 다음과 같습니다.

AT, BT, CS, CT, IE

GUI에서는 이 값을 읽어 정렬한 후 화면에 사용자에게 표시하는데, 한가지 이상한 점이 발견됐습니다.

AT, BT, CT, CS, IE 로 정렬이 되는 겁니다. 왜 CT와 CS가 바뀌는 걸까요? 우리는 영어를 기준으로 코드를 만들었지만 CS는 헝가리어에서 C 다음에 오는 단일 문자이기 때문입니다. 그래서 CT가 CS보다 작다고 판단돼서 CS보다 앞에 오는 것이죠.

이 문제를 위해 .NET 프레임워크는 String.Compare() 메쏘드에 CultureInfo 클래스를 매개 변수로 받도록 설계했습니다. CultureInfo 클래스는 생성자의 매개 변수로 문화 이름(Culture Name)을 문자열을 받습니다. 이 문화 이름은 RFC 1766 에 따라 명명 되며, <언어-국가>의 형식으로 되어 있습니다. 언어-국가의 형식으로 이름이 구성되는 것은 한 언어를 여러 나라에서 사용하는 경우가 많기 때문입니다. 스페인어는 스페인 뿐 아니라 남미의 아르헨티나, 파라과이, 페루, 멕시코 등에서 사용하고 있지요. 독일어도 독일 뿐 아니라 스위스, 오스트리아에서 사용합니다. 영어 또한 마찬가지고요. 국가별 영어의 문화 이름 예는 다음과 같습니다.

en-US : 영어-미국
en-GT : 영어-영국(Great Briton)
en-PH : 영어-필리핀

우리가 앞에서 예로 들었던 헝가리-헝가리어의 CultureInfo 이름은 <hu-Hu>입니다. 아래의 코드를 실행해 보면 String.Compare() 메쏘드가 반환하는 값이 다름을 알 수 있습니다.(한번 해보세요!)

string a = "CS";
string b = "CT";
Console.WriteLine(String.Compare(a, b, false, new CultureInfo("en-US"))); // 미국-영어
Console.WriteLine(String.Compare(a, b, false, new CultureInfo("hu-HU"))); // 헝가리-헝가리어

참고로, CultureInfo 클래스는 문자열의 비교 뿐 아니라, 날짜, 주소, 통화, 숫자의 Formatting에도 사용됩니다. 잘 알아 두세요.

오늘의 이야기는 여기서 끝입니다. 조금은 도움이 되었기를 바랍니다.
다음에는 Endian에 대해 이야기 해보겠습니다. 다음이 언제가 될진 모르겠지만요 ^^;

댓글 1개:

  1. 잘 보았습니다.. 자주 써먹을수 있는 아이템이네요 ^^;;;

    답글삭제