In this blog, there are many things that I did and got or get to know, while I am developing applications. I write the articles to remember in mind and I hope they will be helpful for the visitors who read them.
C/C++로 데이터마이닝 툴 만들다가 적(跡)이 바뀌는 바람에 Java로 작업을 하고 있다.
12~3년 전에 잠깐 써보고 다시 하는 건데 아쉬운 부분도 있지만, 신경 쓸 부분이 많이 줄어 꽤 편하게 작업하고 있다.
하지만 어떤 클래스가 무슨 기능을 하고 어떤 메소드를 이용해야 하는 지 잘 모르니 많이 뒤져 보면서 필요한 것들을 찾고 있다.
그러던 중, Shunting-Yard Algorithm을
우연히 알게 되었다. 예전에 C/C++로 수식파서 만든다고 있는 머리 없는 머리 굴려가며 고생한 적이 있었는데 그때 이걸
알았다면 많이 쉽게 하지 않았을까 싶다.
수식파싱할 때 어려운 게 연산의 순서를 계산하는 것인데 이 알고리즘을 이용하면 쉽게 할 수 있기 때문이다.
데이터를 가공 / 처리할 때 수식을 통하여 새로운 의미의 변수를 만드는 작업을 굉장히 많이 한다.
이런 파생변수를 만들 때 필요한 것이 수식파싱 후 값을 계산하는 모듈이다.
공부도 할 겸 Java로도 이 모듈을 만들어 놓으면 여기 저기 쓸 데가 있지 않을까 싶다.
1에 2를 더하는 수식을 우리는 보통 "1 + 2"로 표시한다.
이와 같이, 보통 우리가 쓰는 수식은 infix notation 방식을 취하고 있다.
무슨 말인고 하니 연산자가 가운데(in) 있다는 얘기다.
Shunting-Yard Algorithm은 infix notation 형태의 수식을 Reverse Polish Notation (RPN) 형태로 바꾸는 데 사용할 수 있는 알고리즘이다. RPN은 연산자를 뒤에 위치시키는 표현법으로 "1 + 2"를 "1 2 +"와 같이 표시한다.
사람이 보기에는 "1 더하기 2"나 "1과 2를 더해"나 별 차이 없어 보이지만 컴퓨터가 보기엔 천지차이다. RPN 형태로 되어 있으면 루프 돌면서 쭉 계산해 나가면 되는데 infix 형태로는 그렇게 하기가 힘들다. 그래서 RPN 형태로 변경해야 하고 Shunting-Yard와 같은 알고리즘을 이용하는 것이다.
Shunting-Yard Algorithm은 이름에서 알 수 있듯이 차량기지(Shunting yard)에서 기차를들 정렬하기 위하여 사용하는 방식과 비슷하게 로직이 수행된다. 자세한 내용은 Wikipedia 참고.
Pseudocode
이야기를 풀어 나가기 위하여 Shunting-Yard Algorithm의 처리 과정을 정리해 보았다.
for each token in expression
{
if token == 'number'
token --> output queue
if token == 'function'
token --> stack
if token == 'function argument separator' (e.g. a comma)
{
while( stack not empty and top of stack != '(' )
pop stack onto output queue
if top of stack != '('
mismatched parentheses error.
else
pop '(' off stack, ignore it.
}
if token == 'operator'
{
while( stack not empty and stack top token == 'operator' )
{
if token precedence < stack top token or (token == left-associative and precedence <= stack top token)
pop stack onto output queue
else
break
}
token --> stack
}
if token == '('
token --> stack
if token == ')'
{
while( stack not empty and stack top token != '(' )
pop stack onto output queue
if stack is empty
mismatched parentheses error.
pop '(' off stack, ignore it.
if top of stack == 'function'
pop stack onto output queue
}
}
while( stack not empty )
pop stack onto ouput queue
만들어 보자
알고리즘만 간단히 구현할 거라면 필요 없겠지만, 확장성 있는 수식파서를 만들기 위해
Psedocode를 보면서 우리가 알 수 있는 것들을 정리해 보자.
우선, 수식을 한땀 한땀 이태리 장인처럼 따서 Token을 추출할 무언가가 필요하다.
추출된 Token에 따라 처리할 내용이 달라지는 것을 보아 Token은 특정 형태를 가지고 있다.
형태는 수식에 사용되는 연산자(Operator), 숫자(Operand), 함수(Function)와 괄호, 쉼표 등이 있다.
연산자는 left-associative, right-associative의 형태를 가진다.
알고리즘을 구현하기 위하여 Stack 한개와 Queue 한개가 필요하며,
수행완료 후 결과는 Queue가 남는다.
수식을 계산을 위한 것이므로 이 Queue에는 계산 순서에 맞게 Token이 위치할 것이다.
Queue와 Stack에는 Token이 들어 간다. 다양한 Token을 넣기 위한 인터페이스가 필요하다.
수행 중 괄호가 맞지 않는 경우 등 사용자 오류를 처리하기 윈한 Exception 클래스가 필요하다.
정리된 내용을 수용하려면 다음과 같은 것들을 만들어야 할 것 같다.
만들 애 이름
기능/역할
비고
ExprParser
수식파서 메인 클래스
-
ExprTokenizer
수식을 잘 잘라서 Token을 뽑아 줄 클래스
1번
ExprTokenType
Token의 형태를 정의한 Enumeration. 연산자, 괄호, 함수, 피연산자 등을 정의.
2번, 3번
OperatorAssoc
연산자의 Associative 방법을 정의한 Enumeration
4번
ExprToken
ExprTokenizer에서 Token을 이 객체로 추출
2번, 8번
OperatorToken
연산자를 처리하기 위한 토큰
3번
FunctionToken
함수를 위한 토큰
3번
ValueToken
피연산자를 대변하기 위한 토큰
3번
EtcToken
연산자, 함수, 피연산자 이외 것을 대변하기 위한 토큰. 예를 들어 괄호 같은 거~
3번
ParserException
수식파싱 및 값 계산 중 발생할 수 있는 오류 처리 클래스
9번
5번에서 필요한 Stack과 Queue는 알고리즘 구현할 때 java.util 패키지에 있는 것을 이용하면 된다.
6번은 파싱하는 메소드에서 생성한 Queue를 반환하거나 멤버 변수로 저장하면 되고,
7번은 알고리즘의 결과이다.
조금만 더 생각해 보자.
수식 내에서 Token을 구분짓는 건 Token의 형태를 문자로 표현한 것들이다.
무슨 말인고 하니, 연산자, 숫자, 괄호, 함수 등을 표현한 수식 내 문자들이 Token을 뽑는 기준이라는 것이다.
오호라~ Token 뽑는 방법은 수식내 문자를 하나 하나 살펴보며 앞서 말한 문자와 일치하는 지 비교해 보면 될 듯 하다.
그렇다면 어떤 문자들이 있을까?
연산자를 먼저 살펴보면, 사칙연산(+, -, *, /)을 기본으로 승수연산(^), 나머지 연산(Modulus, %) 등이 있다.
물론, 비교 연산자(=, >=, <=, !=), 논리 연산자(and, or, not)도 포함되면 좋을 것이다.
숫자와 괄호는 특별히 말할 것이 없고, 함수는 정의하기에 따라 무궁무진 하기 때문에 연산자처럼
몇 개 정의해서 끝날 문제가 아니다.
뭔가 함수객체 들만 몰아 넣어 관리할 Gallery스러운 것이 필요하다.
조금 더 나가서 연산자도 함수처럼 관리하면 편할 것 같다.
실제 계산하는 메소드도 같이 포함시켜 놓고~
그래서 다음과 같은 것들을 추가로 만들면 조금 더 멋진 파서가 되지 않을까 싶다.
만들 애 이름
기능/역할
ExprTokenGallery
연산자, 함수 형태의 Token을 자신을 표현할 문자와 함께 Key & Value 형태로 관리하는 클래스.
Calculatable
연산자나 함수의 실제 계산을 위한 인터페이스.
Token을 정의한 클래스에 멤버로 넣어 쉽게 값을 계산할 수 있게 함.
다음 편에 계속
휴... 사실 이 글을 쓰기 시작한 지 한달이 넘었다. 살은 더 붙여야 하지만 위에서 말한대로 구현도 다 해놨다.
그런데 왜 이리 안써지는지... 일 때문에 바쁜 것도 있었지만 이렇게 안써질 때가 가끔(사실 아주 많이) 있다.
이럴 땐 잠시 시간을 가져야 한다. 여기서 잠시 멈추고 다음에 소스와 함께 다시 해야겠다.
1. MySQL 서버 characterset 설정.
2. 사용하고 있는 툴의 문자 인코딩 지원 종류.
3. 쿼리를 저장한 파일 혹은 데이터 파일의 문자 인코딩 방식.
한글을 사용하려면 문자 인코딩 방식을 euc-kr나 Unicode를 지원해야 한다.
요즘 쿼리 툴은 대부분 Unicode 중 utf-8을 지원하기 때문에 2번은 크게 문제가 되지 않는다. 물론 확인은 해 봐야 한다.
3번도 무료로 사용할 수 있는 Sublime Text 등을 이용하면 쉽게 확인할 수 있다.
문제는 1번이다.
MySQL을 설치하고 utf-8을 기본으로 제공해 주면 좋은데 그렇지가 않다. 다음은 설치 후 MySQL의 상태를 확인한 화면이다.
mysql> status
--------------
mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1
Connection id: 20
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.0.95 Source distribution
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: latin1
Db characterset: latin1
Client characterset: latin1
Conn. characterset: latin1
UNIX socket: /var/lib/mysql/mysql.sock
Uptime: 6 days 5 hours 2 min 54 sec
Threads: 1 Questions: 41 Slow queries: 0 Opens: 13 Flush tables: 1 Open tables: 6 Queries per second avg: 0.000
--------------
mysql>
mysql> status
--------------
mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1
Connection id: 2
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.0.95 Source distribution
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: utf8
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/lib/mysql/mysql.sock
Uptime: 22 sec
Threads: 1 Questions: 5 Slow queries: 0 Opens: 12 Flush tables: 1 Open tables: 6 Queries per second avg: 0.227
--------------
mysql>
Hadoop Homepage에 있는 문서 "File System Shell Guide"를
참고하여 작성하였음!
들어가기
HDFS(Hadoop Distributed File System)는 Linux의 i-node나 Windows의 FAT과 같은 파일을 저장하고 관리, 활용하기 위한 시스템이다.
다른 파일 시스템과 크게 다른 점은 여러 컴퓨터에 파일내용을 나누어 저장한다는 것이다.
HDD에서의 i-node나 FAT의 개념을 컴퓨터로 확장해 보면 개념적으로는 HDFS와 거의 같아 보인다.
사용하기 위한 명령도 Linux 명령과 매우 흡사하다.
"hadoop dfs"가 항상 앞에 붙고 뒤에 명령어(command)가 온다. [command] 앞에 "-" 오타가 아니다.
명령어는 항상 "-"로 시작하기 때문에 명시적으로 표시하였다.
끝에는 명령을 수행하는데 필요한 인수들이 위치한다.
HDFS의 모든 명령은 인수(args)로 파일 혹은 파일경로를 가지며,
이들은 URI 형태로 정의된다.
URI
URI(Uniform Resource Identifier)는 말그대로 동일한 리소스를 유일하게 정의하고 식별하기 위한 규약으로 다음과 같은 형태를 가진다.
scheme://authority/path
크게 scheme, authority, path 세 부분으로 구성되며,
HDFS 명령의 인수로 사용 시, 각 부분은 HDFS와 Local File System을 나타내기 위하여 다음과 같이 정의된다.
URI
HDFS
Local FS
scheme
문자열 "hdfs"
문자열 "file"
authority
hostname:post
(지정안함)
path
Linux 파일경로와 동일하게 지정
Exmaple
hdfs://alpha.centos:8020/data/README.txt
file:///opt/hadoop-1.0.4/README.txt
HDFS의 authority에서 기본 포트를 이용하고 있다면 port는 지정할 필요가 없다.
scheme와 authority은 생략가능하며, 생략했을 경우 명령을 수행하는 머신의 설정을 참고한다.
HDFS 셀 명령
이젠 HDFS의 셀 명령에 대하여 본격적으로 알아 보자.
명령어
설명
cat
hadoop dfs -cat URI [URI ...]
지정한 파일(들)을 화면에 표시한다.
chgrp
hadoop dfs -chgrp [-R] GROUP URI [URI ...]
지정한 파일의 group을 변경한다. 단, 파일의 소유자이거나 슈퍼유저만 변경 가능하다.
"-R" 옵션을 붙이면 하위 디렉토리들도 재귀적으로 그룹이 변경된다.
chmod
hadoop dfs -chmod [-R] MODE URI [URI ...]
지정한 파일의 권한(Permission)을 변경한다. 파일의 소유자이거나 슈퍼유저만 변경 가능할 수 있으며,
"-R" 옵션을 붙이면 하위 디렉토리들도 재귀적으로 권한이 변경된다. MODE는 Linux 명령의 chmod와 똑 같이 지정하면 된다.
chown
hadoop dfs -chown [-R] [OWNER][:[GROUP]] URI [URI ...]
슈퍼유저만 사용할 수 있는 파일의 소유자를 변경하는 명령이다.
"-R" 옵션을 붙이면 하위 디렉토리들도 재귀적으로 소유자가 변경된다.
copyFromLocal
hadoop dfs -copyFromLocal <localsrc> URI
지정한 로컬 파일을 HDFS로 복사한다.
copyToLocal
hadoop dfs -copyToLocal [-ignorecrc] [-crc] URI <localdst>
지정한 HDFS 내 파일을 로컬 파일로 복사한다. "-ignorecrc" 옵션을 지정하면 CRC 체크를 실패하더라도 복사해 온다.
CRC도 복사해 오고 싶으면 "-crc" 옵션을 사용한다.
count
hadoop dfs -count [-q] <path> [path ...]
지정한 디렉토리(들) 내에 있는 디렉토리 개수, 파일 개수, 파일 크기, 디렉토리 명을 순서대로 표시한다.
"-q" 옵션은 할당량(quota)에 대한 정보를 추가로 표시한다. QUOTA, REMAINING_QUATA, SPACE_QUOTA, REMAINING_SPACE_QUOTA 값들이 앞쪽에 붙여 나온다.
(왜 이름이 count인지 모르겠다.)
cp
hadoop dfs -cp URI [URI ...] <dest>
지정한 파일(들)을 복사한다. 여러 파일이 지정된다면 <dest>는 디렉토리이어야 한다.
du
hadoop dfs -du [-s] [-h] URI [URI ...]
지정한 디렉토리에 있는 파일과 디렉토리의 크기를 표시한다.
디렉토리의 경우 디렉토리 내 포함된 파일 및 서브 디렉토리의 크기를 모두 합한 값이 표시된다.
-s 옵션은 표시된 파일의 총크기를 표시한다.
-h 옵션은 사용자 친화적 형태로 크기를 표시한다. (67108864를 64.0m 표시)
※ 1.0.4에서 -s와 -h는 지원하지 않는 듯~