문제로 풀어보는 알고리즘 149쪽 문제 3.c 풀이(알고리즘 코딩 이벤트 1주차 2번째 문제)

구독하던 인사이트 출판사 블로그에서 이벤트를 하는 것을 발견하고, 간만에 알고리즘 문제에 도전~

문제는 블로그에서 확인할 수 있듯이, 주어진 집합을 하나이상의 집합으로 나누는 모든 방법을 출력하는 것이다.

뭔가, 효율적으로 이를 구하는 방법이 존재할 것 같아서 계속 고민해 봤으나, 원초적인 알고리즘을 만드는 일을 수학자가 할 일이라고 생각해서, 그냥 딱 떠오르는 방법으로 코딩 해 버렸다.

1. 입력 받은 N으로 리스트를 집합을 구한다. makelist/1
2. 1에서 만든 집합에서, 가능한 모든 부분집합을 구한다. subsets/1
3. 2에서 구한 부분집합의 조합 중, 각 원소가 한 개씩만 존재하는 조합만 고른다. combinations/1

평소 사용하던 언어로 하는 것은 시시할 것 같아, 도무지 사용할 일이 없을 것 같았던 Erlang으로 시도. 머리가 더 아팠다.

출력예

1> c(insight).
{ok,insight}
2> insight:combinations(3).
[[0],[1],[2]]
[[0,1],[2]]
[[0,2],[1]]
[[0],[1,2]]
[[0,1,2]]
ok
3> insight:combinations(4).
[[0],[1],[2],[3]]
[[0,1],[2],[3]]
[[0,2],[1],[3]]
[[0],[1,2],[3]]
[[0,1,2],[3]]
[[0,3],[1],[2]]
[[0],[1,3],[2]]
[[0,1,3],[2]]
[[0],[1],[2,3]]
[[0,1],[2,3]]
[[0,2,3],[1]]
[[0,2],[1,3]]
[[0,3],[1,2]]
[[0],[1,2,3]]
[[0,1,2,3]]
ok
4>

중요: Erlang은 인사이트의 책으로 처음 배웠다.

비트필드의 크기(Size of Bit-fields)

union U_Data
{
    unsigned int _key;
    struct S_Data
    {
        unsigned short _code;
        unsigned short _no;
    } _evtkey;
};

개발자는 두 값(code, no)를 조합해서 고유한 키값을 손쉽게 만들기 위해 U_Data를 아래와 같이 사용하고 있다.

U_Data data;
data._evtkey._code = 10;
data._evtkey._no = 4;
unsigned int another_key = data._key; // 고유한 키값을 손쉽게(?) 얻었다!

그런데, _code의 크기가 100,000을 넘어가게 생겼다. 그래서 타입을 int로 바꿔야 했다. 이미 _key는 unsigned int 타입으로 여러 함수의 인자로 사용되고 있기 때문에, U_Data의 현 크기(4바이트)를 바꾸기는 쉽지 않다.

다행히 _no의 범위는 기껏해야 수십을 넘어가지 않았으므로, 비트필드를 사용하여, _code에 24비트를, _no에 8비트를 사용하기로 하였다.

union U_Data
{
    unsigned int _key;
    struct S_Data
    {
        int _code:24; // 타입을 바꾸고, 24비트를 할당
        unsigned short _no:8; // 8비트를 할당
    } _evtkey;
};

다른 코드는 손대지 않고, 손쉽게 _code의 크기를 늘렸다. 근데, 제대로 동작하지 않는다. 앞선 예제와 반대로, _key를 이용해서 _code와 _no를 구하려고 하니, _no에는 쓰레기 값이 있다.

이유는, 1바이트 패킹 시(#pragma pack(1)), VC++에서, U_Data의 크기를 4바이트가 아닌, 6바이트로 다루기 때문이며, 비트필드가 어떻게 메모리에 할당되는지에 대해서는 표준문서가 명시하고 있지 않다.

Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined.

– ISO/IEC 14882:2011 9.6.1

VC++에서는 비트필드가 명시된 타입에 순차적으로 값을 구겨 넣으며, 타입이 변경되면 새로운 타입에 새롭게 할당을 시작한다. 즉, S_Data의 첫번째 데이터 타입이 int이므로 4바이트에 24비트를 할당하고, 그 다음 2바이트인 unsigned short타입에 8비트를 할당해서, 총 6바이트가 사용된다. int타입인 _code에 8비트가 남았다고 해서 _no를 int에 할당하는 것이 아니란 이야기다(gcc는 4바이트를 할당한다). 명시적으로 새로운 할당을 시작하려면 비트의 크기에 0을 넣으면 된단다(http://msdn.microsoft.com/en-us/library/ewwyfdbe.aspx).

이 문제는 _code와 _no의 타입을 int로 맞춰주는 것으로 해결된다.

문제를 해결하고, 기억을 되살려보니, 정확히 2005년 10월 4일에 이와 똑같은 문제를 겪고 남겨둔 기록이 있었다.


struct T1
{
    int m_a : 5;
    int m_b : 6;
    int m_c : 5;
};
struct T2
{
    short m_a : 5;
    short m_b : 6;
    short m_c : 5;
};
struct T3
{
    char m_a : 5;
    char m_b : 6;
    char m_c : 5;
};

1로 패킹했을 경우(#pramgma pack(1)), 각 구조체의 크기는 각 각 얼마일까?

만약, sizeof()의 결과로 2를 기대했다면, T1과 T3는 잘못된 결과를 야기한다.

T1의 경우, sizeof()의 결과는 4이다. 총 비트의 합은 16이지만, 첫 번째 멤버변수의 크기가 4이기 때문에 컴파일러는 모든 비트를 첫 번째 변수의 크기인 4바이트에 구겨 넣는다. 나머지 16비트는 사용되지 않는다.

T2의 경우는 2이다. 16비트는 short int형에 딱 맞게 들어간다. 따라서 낭비하는 비트는 없다.

T3의 경우는 3이다. 컴파일러는 char의 크기인 1바이트에 5비트를 우선 구겨 넣는다. 그 다음 6비트를 확보해야 하는데, 첫 번째 1바이트에는 3비트밖에 남은 공간이 없으므로 또 하나의 char형 크기인 1바이트에 6비트를 확보한다. 마찬가지로 그 다음 5비트는 적절한 공간이 없으므로 새로운 char형 크기인 1바이트를 또 할당한다. 그래서 총 3바이트 크기가 되며, 8비트를 사용하지 않은 채 남겨두게 된다.

대부분의 컴파일러는 이와 같은 방식으로 동작한다. 그러나 표준문서에 비트필드의 크기에 대한 언급은 없기 때문에 엄밀히 따지면 이는 컴파일러의 구현에 따라서 틀려질 수 있다. 참고로, 비트필드에 접근하는 속도는 표준타입의 멤버변수에 접근하는 시간보다 느리기 때문에, 속도를 위해서라면 바람직한 방법은 아니다.

2011년, 이직.

마지막으로 포스팅했던 날짜가 2010년 12월 7일이네요. 그때라면, 발전하지 못하고 있는 것 같은 스스로를 자책하면서 이직을 준비하던때 입니다. 나름 만족하며 다니던 회사였는데, 막상 옮기려니 이런저런 생각이 들어군요. 왜 제가 회사를 갈아타려했는지 이야기해보겠습니다.

우선, 월급이 밀린다거나, 회사의 정치적인 문제라거나, 혹은 회사가 망해버렸거나, 그것도 아니면 잘렸거나…, 이런건 이유에 해당되지 않습니다. 전 한번도 월급을 늦게 받아본 적이 없었고, 회사가 망했던 경우도 없었고, 사내정치나 대인관계로 고민해본적이 없습니다. 그런 점에선 분명히 운이 좋았다고 할 수 있습니다. 여기에 조금 덧붙이자면, 제 여건에 맞춰서 나름 회사를 잘 선택했다고 볼 수도 있고요.

2010년 12월 7일 당시의 회사는 두 번째 회사였습니다. 첫 번째 회사는 2년 동안 다녔고, 당시의 퇴직 사유는 절반 이상이 ‘세계여행’때문이었죠. 나머지 중 상당수는 출근길에 본 눈부신 햇살 때문이었고요. 좀 더 정확히 이야기하면, 그때의 햇살이, 오랜 시간 꿈꿔왔던 ‘세계여행’에 대한 욕구를 자극했던 거죠. 그리고 얼마 안 있어 회사를 그만 두었고, 세 달을 쉬고 떠나려 했는데, 채 세 달을 채우진 못했습니다. 세 달 동안 논다는 것도 어렵더라고요. 제 생각엔, 한 한 두 달이면 충분하지 않을까 싶습니다. 그런 의미에서 회사에서 장기 근속자에게 한 달의 특별 휴가를 주는 건 직원을 붙잡아둘 수 있는 아주 좋은 수단이라는 생각이 듭니다. 어쨌든 그리고 정말 여행을 떠났습니다. 2년 간 일을 했으니 어느 정도는 돈이 있었고, 따라서 굳이 돈에 스트레스 받으며 돌아다니지 않아도 됐죠. 또, 1년을 예상했으니 시간 때문에 서두를 필요도 없었고요. 근데, 그도 6개월 여 만에 그만 두었습니다. 여기에 대해서는 어떤 식으로 든 따로 이야기할 기회가 있으리라 믿고, 끝내죠.

한국에 돌아와서 일주일 동안은 매일 면접을 봤는데, 그 중 한 회사에 입사하기로 했지만, 이후에 다른 회사에서 연락이 오는 바람에, 먼저 회사에 양해를 구하고 저의 두 번째 회사에 입사하게 되었죠. 그렇게 두 번째 회사에서 거의 4년을 일했습니다.

환경, (대부분의)사람들, 보수. 이 모두에 대해서 큰 불만이 없었습니다. 적어도 처음 2년은 상당히 만족스럽게 느꼈고, 그래서 타 회사의 제의도 고민 없이 거절해 버렸죠. 돌이켜 생각해보면, 팀장이 바뀐 이후부터 아쉬움이 조금씩 늘어나지 않았나 싶습니다. 우선, 팀 내의 사소한 일들이 공정하게 처리되지 않는다고 느끼게 되었고요, 팀원들의 업무 성과에 대해서 신경 쓰지 못하는 모습이 눈에 띄었죠. 또 하나 결정적인 건, 그런 아쉬움이나 불만들을 제대로 들어주지 않는다는 것이었습니다. 즉, 팀원은 팀원들끼리 불만을 이야기하고, 팀장은 팀장 나름의 불만을 이야기하는 형국이었던 겁니다. 이런 상태로 시간이 흐르니, 팀원이 어떤 문제를 발견했을 때도, 굳이 고치려 하지 않게 되더군요. 혼자 해결 할 수 있는 일이면 당연히 그렇게 하겠지만, 아시다시피 회사 업무라는 게 부서간, 또 사람간에, 얽히고 설킨 관계인 지라, 어디 그게 쉽습니까. 이렇게 되니, 팀이 스스로 발전하는 기회를 놓쳐버리게 된 것입니다. 이 말은요…, 팀원이 팀의 구성원으로서 느낄 수 있는 성취감을 없애버렸다는 말이기도 합니다. 회사는 그냥 회사가 되어버린 거고, 팀은 그냥 팀이 되어버린 겁니다. 조직을 위해서 하고자 하는 동기부여가 제대로 되지 않았던 거죠.
일이 재밌으면, 사람을 붙잡을 수 있습니다. 수많은 오픈소스 프로젝트가 이를 증명하고 있습니다. 동시에, 소리 없이 사라지는 상당수의 오픈소스 프로젝트가, 일에 대한 열정이 얼마나 쉽게 사라지는지, 또 동기부여가 부족한 일이 얼마나 쉽게 중단되는 지를 증명합니다.

돈을 많이 주면, 사람을 붙잡을 수 있습니다. 다른 의견도 있겠지만, 대부분의 직장인에게 연봉을 두 배로 준다고 하면, 군말 없이 자리에서 일어날 겁니다. 그러나, 이 많다는 기준이 천차만별인데다, 계속 변하는 지라, 돈으로만 사람을 붙잡으려면, 상당한 손해를 감수해야 할 수도 있겠죠.

이 모든 것을 뛰어넘을 수 있는 게 ‘사람’이라고 생각합니다. 단순히 좋은 술친구를 의미하는 것 말고, 서로의 고민을 이야기하고 해결해 줄 수 있는 사람 말입니다. 사람이 좋으면 일이 재미없을 리 없을 테죠. 그렇다면, 돈이 다소 모자라도 좀 더 견딜 수 있을 겁니다.

비슷한 처지의 사람들과는 이것이 쉽습니다. 친구 간에 고민을 털어놓는 것 처럼요. 최소한 같이 고민해 줄 수는 있죠. 그러나 이를 해결해 줄 수 있는 열쇠를 가진 사람에게는 그것이 어렵습니다. 그래서, 책임 있는 자리에 앉아있는 사람의 역할이 중요합니다. 안타깝게도, 자리가 높아질 수록, 이런 생각을 점점 잊더군요. 그래서 이런 사람은 찾기 힘듭니다. 전, 지금까지 한 번 봤습니다. 위에 언급했던 첫 번째 팀장이요. 팀원을 위해 상사나 다른 부서와 싸워줄 수 있는 탐장은 많지 않습니다. 근데, 그 팀장은 싸우더군요. 싸운다기 보다는, 할 말을 하는 거죠. 누가 봐도 옳은 생각을요.

이쯤 해서 정리하는 게 좋겠습니다. 2011년이 얼마 남지 않았거든요. 제가 회사를 옮기려고 했던 이유는, 모두가 옳다고 생각하는 의견이 제대로 반영되지 못하는 것 때문이었습니다. 그렇게 5-6개월 동안 몇 군데에 이력서를 넣었고, 지금 일하는 곳으로 옮기게 되었습니다.

어쩌면, 새로운 회사에서는 제 고민이 해결됐는지 궁금한 분도 있을지 모르겠네요. 그것에 대해서는, 나중에 기회가 되면 다시 쓰도록 하겠습니다.

2011년을 포스트 하나 없이 그냥 넘길 수 없어서, 원래 쓰려 던 주제는 이게 아니었는데, 괜스레 개인적인 이야기만 늘어놓아 버렸습니다. 새해에도, 프로그래밍을 좋아하시는 모든 분들이 계속 프로그래밍 할 수 있었으면 좋겠습니다.

C++ 문자열 나누기

Ruby에도 C#에도 문자열에 대한 split함수가 있다. 그러나 우리의 C++에는 그런 게 없다. 이런 하잖은 기능은 직접 구현해야 한다. 이게 C++이 멋진 이유 아니겠나.

서핑 중, 문자열을 자르는 아주 멋진 코드조각을 stackoverflow에서 발견했다.

vector<string> tokens;

copy(istream_iterator<string>(iss),
    istream_iterator<string>(),
     back_inserter<vector<string> >(tokens));

구차하게 for문을 작성하지 않아도 되는 이런 우아한 방법이 있다니. 쇼크! 내가 얼마나 STL에 무신경했는지 느꼈다.
한눈에 들어오지 않아서 Visual C++ 2005의 STL소스를 참조해서 살펴봤다.

알다시피, copy()함수는 첫 번째 인자로 들어오는 입력 이터레이터를 두 번째 인자로 들어오는 입력 이터레이터까지 순회하면서 그 값을 세 번째 인자인 출력 이터레이터의 위치에 쓴다. back_inserter클래스는 컨테이터에 값을 추가하기 위한 일종의 헬퍼이다.

여기서 내가 가장 헛갈렸던 것은, ‘첫 번째 인자와 두 번째 인자의 값을 비교함에 있어서 istream_iterator는 그 값을 어떻게 가져오며, 비교대상인 istream_iterator클래스의 기본 생성자(두 번째 인자)는 무슨 의미인가’였다.

Visual C++의 STL소스를 뒤져보면, istream_iterator클래스의 기본생성자는 문자열의 char(istream_iterator클래스 기본 템플릿 파라미터)을 가리키는 포인터 변수를 0으로 설정한다. 즉, 종료문자열을 의미한다.

istream_iterator()
: _Myistr(0)
{	
    // construct singular iterator
}

그렇다면, copy()함수는 입력 이터레이터의 값을 어떻게 가져올까? 이는 istream_iterator클래서의 _GetValue()함수를 사용하며, _GetValue()함수는 basic_istream클래서의 >>연산자를 사용한다. 결국, copy()함수는 istringstream클래스에 정의된 >>연산자를 사용하게 된다.

자못 복잡해 보일 수도 있지만, 코드는 아름답다.

여기서 한 걸음 더 나아가서 delimiter를 공백문자가 아닌 다른 걸로 바꾸고 싶었다. istringstream에서는 그러한 능력을 제공하지 않는다. 역시 C++답다. split()함수를 구현하지 않은 마당에, delimiter를 설정하는 것 따위, C++에게는 사치다. 근데, getline()함수에서는 delimiter를 설정할 수 있다. 결국, 다음과 같이 쓸 수 있다.

for (string token; getline(iss, token, 'i'); tokens.push_back(token));

어쨌든, 한 줄은 한 줄. – -;

교훈 : STL소스는 복잡하다.

실행중인 인터넷 익스플로어 제어하기

이거, 간단치 않았던 작업이다. 열려있는 인터넷 익스플로어(이하 IE)를 제어해야 했다. 하다 보니, 이런걸 요청 받기도 하고 또, 겨우겨우 구현하기도 한다. 최단로(路)인지는 모르겠지만, 어쨌든 길은 길이다.

IE::_FindWindow(const TCHAR* tszSubWindowText)는 IE의 윈도우타이틀을 검색해서 핸들을 구한다. IE::Back()함수와 IE::Navigate()함수는 모두 윈도우 핸들을 찾기 위한 윈도우 타이틀을 인자로 요구하며, 각각, IE의 뒤로 가기와 URL이동을 구현한다.

이 코드는 oleacc.lib가 필요하며, Window XP에서 테스트 되었다.

근데…, 이걸 어디에 쓰냐고?

쓸 데가 있더이다~

킨들3 사용기

동기는 가격이 너무 싸다는 것이었다. 단돈 139달러. 배송비(20불)와 관세(약 32,000원)를 포함해도 20만원이 조금 넘는다. 와이파이까지 지원되는데, 이정도 가격이면 정말 만족스럽다. 20만원이면, 겨우(?) IT관련 서적 몇 권이다. 게다가, 무려 한글도 지원한다. 배송은 출발 4일만에 도착. 그것도 토요일과 일요일이 낀 상태. 아마존에서 책을 받는 속도와는 비교 불가. 다만, 당시에는 품절이라 대기시간이 있었다.

생김새는 깔끔하다. 그냥 군더더기 없는 수준. 키보드의 질감은 약간 거칠어서, 만지면, 키보드구나 알 수 있다. 페이지를 넘기는 버튼이 좌우에 두 개씩 있는데, 오른쪽으로 넘기는 버튼이 더 크다. 처음에는 페이지버튼이 좌우 대칭이 더 좋지 않았을까 생각했지만, 책을 읽다가 뒤로 가는 경우가 적은 만큼, 앞으로 넘기는 버튼이 더 커야 하는 게 맞는 것 같다. 240g의 무게는 어떤 자세로 사용해도 부담 없을 정도로, 충분히 가볍다.

화면은 아주 편안하게 다가온다. 해상도가 아주 뛰어나진 않지만(600×800), 그것을 무시할 수 있을 만큼 부드럽게 보인다. 일반 책을 읽는 느낌과 상당히 흡사해서, 모니터화면으로 문서를 읽는 것과는 확실한 차이를 느낄 수 있다. 전자잉크의 특성상, 페이지전환시 잔상이 생기지만, 사용에 문제를 줄만한 수준은 아니다. 다만, 화면의 부분적인 갱신이 일어날 경우(예를 들어 사전을 찾아보는 경우 등), 갱신전의 화면이 깨끗하게 지워지지 않았다. 페이지를 전부 갱신하는 경우는 이런 현상이 생기지 않는다. 북큐브 B-815와 비교해보니, 콘트라스트와 전환속도 모두 두드러질 정도로 차이가 났다(긍정적 의미).

한글을 포함해서 일본어와 중국어도 지원한다. 그러나, 그것뿐이다. 뒷면의 한국 전파인증 마크와 한글 지원은, 배송국가에 한국을 포함시키기 위한 것일 뿐이다. 아마존의 변환 서비스를 이용하면 좀 더 괜찮은 한글폰트를 볼 수 있다지만, 그게 아니라면, 평생 사용해본 적도, 사용할 일도 없는 한글폰트를 맞닥뜨릴 것이다. 이에 비해, 일본어와 중국에는 읽을만한 수준이다.

을 구입하는 과정은 정말 간단하며, 많은 킨들 책들이 무료로 제공되기도 한다. 적어도 아마존에서 책을 사려한다면, 킨들에디션은 매력적인 선택이다. 내가 산 9.99불짜리 책을 새 책으로 사려면, 최소한 9불이상 줘야 한다. 비슷하다고? 아마, YES24에서 구입하는 것이라면, 선뜻 구입하지 않았을 것이다. 내가 북미에 산다고 해도 비슷할 것이다. 킨들로 10불이 넘을 배송비와 보름가량의 배송시간을 아낄 수 있으므로, 분명히 이는 괜찮은 선택이었다. 그러나 일부 책들은, 가격이 비싸다. 아마존에서 개인셀러가 파는 책들의 가격은, 아마존이 파는 가격보다 일반적으로 더 저렴하다. 새 책이라면 그 차이가 큰 경우는 많지 않지만, 그렇지 않다면 이야기가 달라진다. 예를 들어, 위시리스트에 담긴 ‘A Lion Called Christian’은 킨들에디션이 9.06불이다. 페이퍼백이 10.07불이니 그럭저럭 봐줄 만 하지만, 개인셀러가 파는 중고 하드커버는 단돈 0.01불이다. 여기에 해외배송을 하면 대략 14불정도의 금액이 나오는데, 이런 경우는 매우 망설여 진다. 중고 책이기 때문에 어쩔 수 없다면, 최소한 개인셀러가 파는 새 책보다는 저렴해야 한다. 아마존에서 개인셀러가 파는 중고 책들은 상태가 완벽한 경우가 많다. 적어도 내 경험에 Like New라고 설명된 중고 책은 새 책이나 다름 없었고, 이것이 더 싸다면, 난 중고로 산다.

앞으로도 킨들을 계속 사용할 것이다. 단, 내가 줄을 그으면서 읽는 책이 아닌 경우에 한해서다. 나에게 있어서 소설이나 에세이가 바로 그런 책들이다. 학습서를 읽을 때면, 난 언제나 책에 줄을 긋는다. 물론, 킨들에서도 줄을 그을 수 있고, 또 주석을 달 수도 있다. 책갈피를 꽂아둘 수도 있지만, 그것이 내가 원하는 것을 종이 책처럼 빨리 찾을 수 있게 하지는 못한다. 템플릿 파라미터를 갖는 C++ 템플릿을 찾아보기 위해서 수백 번 다음페이지 버튼을 누를 수는 없지 않은가(목차에서 링크를 제대로 지원하지 않으면 정말 이래야 할 수도 있다). 그게 아니라면, 킨들은 정말 쓸만하다. 물론, 책을 읽지 않는 사람이 킨들을 산다고 책을 읽을 가능성은 그다지 많지는 않겠지만 말이다.

Reading Excel using Ruby

There’re some libraries dealing with Excel, but they didn’t work in some conditions. So I’ve just written the code for this.

ExcelData uses win32ole module. It means this work only on Windows and Excel needs to be installed.
There’s only a public class method Load returns row array. Each row consists of hash with column name and value string pair.
Load method requires minimum three parameters. Excel filename(excelFilename), sheet name(worksheetName) and first header column name(firstHeaderColumnName). ExcelData finds first row with first header column name and read cell from there. If empty cell is found(both row and column), it stops reading the file.

require 'win32ole'

class ExcelData
public
  def	ExcelData.Load(excelFilename, worksheetName, firstHeaderColumnName, keySearcher = nil)
    xls       = nil
		ws        = nil
    excelData = []

    begin
      xls         = WIN32OLE.new('Excel.Application')
      xls.visible = false
      ws          = xls.Workbooks.Open(excelFilename).Worksheets(worksheetName)

      excelData   = _CollectData(ws, firstHeaderColumnName, keySearcher)
    rescue
      puts "Failed to open excel file : #{excelFilename}"
    ensure
      xls.quit if xls != nil
    end

    return excelData
  end

private
  def	ExcelData._FindFirstDataRowNum(worksheet, firstHeaderColumnName)
    rowNum  = 1

    while (true)
      row = worksheet.Range("a#{rowNum}")
      break if (row['Value'] != nil && row['Value'] == firstHeaderColumnName)
      rowNum  += 1
    end

    return rowNum + 1
  end
	
  def	ExcelData._FindLastDataRowNum(worksheet, firstDataRowNum)
    rowNum  = firstDataRowNum

    while (true)
      row = worksheet.Range("a#{rowNum}")
      break if (row['Value'] == nil || row['Value'] == "")
      rowNum  += 1
    end

    return rowNum - 1
  end

  def	ExcelData._FindLastDataColChar(worksheet, headerDataRowNum)
    colChar	= 'a'

    while (true)
      row = worksheet.Range("#{colChar}#{headerDataRowNum}")
      break if (colChar == 'z' || row['Value'] == nil || row['Value'] == "")
      colChar.succ!
    end

    return colChar
  end

  def ExcelData._CollectData(worksheet, firstHeaderColumnName, keySearcher)

    firstDataRowNum = _FindFirstDataRowNum(worksheet, firstHeaderColumnName)		
    lastDataRowNum  = _FindLastDataRowNum(worksheet, firstDataRowNum)
    lastDataColChar = _FindLastDataColChar(worksheet, firstDataRowNum - 1)

    puts "Collecting excel data : Total #{lastDataRowNum - firstDataRowNum + 1} rows ..."

    # Column Names
    colNames	= []
    row = worksheet.Range("a#{firstDataRowNum - 1}:#{lastDataColChar}#{firstDataRowNum - 1}")
    row.each do |cell| colNames << cell['Value'] end

    # Collect excel data
    excelData	= []
    for rowNum in firstDataRowNum..lastDataRowNum
      colIndex  = 0
      rowHash   = {}

      row = worksheet.Range("a#{rowNum}:#{lastDataColChar}#{rowNum}")

      row.each do |cell|
        if (!cell['Value'].to_s.empty? && cell['Value'].to_s =~ /^[0-9.]+/)
          rowHash.store(colNames[colIndex], cell['Value'].to_i.to_s)
        elsif
          rowHash.store(colNames[colIndex], cell['Value'].to_s)
        end

        colIndex	+= 1
      end

      excelData << rowHash

      keySearcher.store(rowHash[colNames[0]].to_s, excelData.length - 1) if keySearcher != nil

      print "." if excelData.length % 10 == 0
    end

    puts 

    return excelData
  end
end

You can simply use this code like bellow.

xls = ExcelData.Load('myfile.xls', 'sheet1', 'ITEMID')