티스토리 통계 툴 Tistat ver 0.98 공개합니다.

애인님과 요 며칠 낑낑거리며 만든 티스토리 통계 자동화 툴이 완성되었습니다.
이름하야 tistat v0.98
처음에는 댓글 단 사람 통계를 손으로 내기 귀찮아서 -_- 시작한 일이었는데
어쩌다보니 별별 자잘한 것까지 다 찍어주는 큼직한 툴이 되고 말았습니다.

하지만 일단 만들고보니 뿌듯한 마음 금할 길 없군요. 후후후후후후.

애인님은 처음 제가 만든 댓글 통계 스크립트를 보고 재밌다 +ㅅ+ 하고 눈을 반짝이더니, 저의 루비 튜토리얼을 들여다보기도 하고, 이것저것 물어보기도 하고, 이런이런 함수 만들어줘! 하기도 하고, 하여 뚝딱거린 끝에 사흘만에 루비를 마스터한듯 합니다. 이제 하산하거라.

사용법은 간단합니다. 데이터 백업을 위해 티스토리에서 지원하는 XML 파일을 다운받은 다음 설정 파일을 살짝 고쳐주고 실행하면 됩니다.

이리하여 2004년부터 2007년까지 4년간의 블로그 통계를 낼 수 있게 되었습니다.

좀 길기 때문에 접습니다

훌륭하네요.
HTML 출력도 지원하기 때문에 그냥 긁어다 붙이면 자동으로 위와 같은 통계가 생성됩니다. 댓글 단 사람의 링크나 글로 가는 링크도 자동으로 만들어주는 것이 장점이죠. 후후후후.

엑셀에서 읽어들일 수 있는 CSV 파일도 출력해주기 때문에, 엑셀에서 간단히 그래프를 그려볼수도 있습니다.

사용자 삽입 이미지

2004-2007년 방문객 그래프

써보고 싶으신 분은 애인님의 블로그에서 공개한 베타버전을 사용해보시면 될 것 같습니다. (광고광고)

그래프를 보며 재미있는 사실을 발견했는데, 방문객 통계를 보면 2006년 4월경과 2007년 1월경에 방문객이 급격히 감소하는 것을 볼 수 있는데, 각각 학교 서버가 잠시 다운되었던 기간과, 블로그 서버를 학교 서버에서 티스토리로 이전했던 기간과 일치하는 듯 하네요 +ㅅ+ 재미있는 사실이네요.

'자랑 > 공작' 카테고리의 다른 글

2004-2007 블로그 통계  (10) 2008/01/14
스프링노트+파이어폭스로 빠르게 메모하기  (9) 2007/12/14

지난 챕터에서는 1부터 10까지 더하는 코드를 지겹게 만들었었죠. 코드의 반복을 피하기 위해 함수로 만들어봅시다.

# array를 인자로 받아 배열의 모든 원소를 더해서 리턴하는 함수 sum_all 을 만들어 봅시다.
def sumall(array)
  total = 0
  array.each {|num| total += num}
  return total
end

puts sumall(1..10)
# => 55

def 키워드로 시작해서 end 로 끝내면 됩니다. C/Java와 다른 점은 인자와 리턴값의 타입을 명시하지 않는다는 거겠죠? 타입을 명시하지는 않지만 타입 체크를 하지 않는 것은 아닙니다. 엉뚱한 타입을 넘겼을 경우 타입 에러가 돌아옵니다.

# 위 코드에서 계속됩니다.
puts sumall(35)

이 코드를 실행시키면 다음과 같은 에러를 뱉습니다.

method.rb:3:in `sum_all': undefined method `each' for 35:Fixnum (NoMethodError) from method.rb:8

인자로 넘겨준 35는 FixNum, 즉 정수 타입이기 때문에 35.each 함수를 부를 수 없다는 뜻의 에러입니다.

루비에서는 인자를 받지 않는 함수도 만들 수 있고, 인자에 기본값을 줄 수도 있으며, 인자의 개수를 바꿔가며 넣어줄 수도 있습니다.

# 1에서 45 사이의 랜덤한 정수 6개를 돌려주는 함수를 만들어봅시다.
# 인자를 받지 않는 함수의 경우 괄호는 생략해도 됩니다.
def lotto
  # rand(n)은 0..n-1 사이의 랜덤한 정수를 반환합니다.
  [rand(45)+1, rand(45)+1, rand(45)+1, rand(45)+1, rand(45)+1, rand(45)+1]
end

# p는 puts와 비슷하지만 결과값의 내용을 보여주는, 디버깅에 주로 사용되는 함수입니다.
p lotto
# => [19, 37, 13, 42, 22, 37]

위에서 선언한 함수 lotto에는 return 키워드가 없는 걸 볼 수 있습니다. 루비의 함수는 return 이 없을 경우 마지막 줄의 리턴값(지난 챕터에서 말씀드렸듯, 루비의 모든 실행문에는 리턴값이 존재합니다.)을 리턴합니다. 따라서 위와 같은 한줄짜리 함수는 return 없이도 간단히 쓸 수 있습니다.

이번에는 인자를 받지 않을때는 위 함수와 똑같이 동작하지만, 정수의 개수와 최대값을 인자로 넣어줄 수도 있는 함수를 만들어 봅시다.

def lotto(num = 6, max = 45)
  # Array.new는 새 배열을 만들어주는 함수입니다.
  # 인자 하나(num)와 블록 하나( {rand(max)+1} )를 받아서,
  # num 크기의 배열을 {rand(max)+1} 로 채워줍니다.
  Array.new(num) {rand(max)+1}
end

p lotto
# => [30, 20, 23, 32, 5, 29]
p lotto(3, 25)
# => [25, 3, 10]

임의 개수의 인자를 받으려면 인자 이름 앞에 *을 붙여줍니다. 함수 내에서는 해당하는 이름의 배열로 자동 변환됩니다.

def hello(*list)
  list.each {|name| puts "Hello, " + name + "!"}
end

hello("C", "C++", "Java")
# => Hello, C!
# => Hello, C++!
# => Hello, Java!

2. There is more than one way to do it

저는 국민학교 때 본 정체불명의 컴퓨터 학습만화를 통해 처음 GW-BASIC이라는 프로그래밍 언어가 있다는 것을 알게 되었습니다. (2진수에 대해서도 덤으로 알게 되었지만 GW-BASIC과 2진수 사이에 도무지 무슨 관련이 있는지는 한참동안 알지 못했습니다.) GW-BASIC은 함수도 없고, 함수가 없으니 당연히 지역변수도 없고, (모든 변수는 전역변수였습니다.) 배열 외에는 쓸만한 자료구조도 없고, 끔찍한 줄번호와, GOTO, IF, FOR 만으로 모든 것을 헤쳐나가야 하는 언어였습니다. 그때 제가 책을 참고해가며 사흘을 낑낑거려 처음 짠 코드는 바로 이거였습니다.

10 REM "용서하세요. 그땐 아무도 코드 들여쓰기를 가르쳐주지 않았답니다!"
20 SUM = 0
30 FOR I = 1 TO 10
40 SUM = SUM + I
50 NEXT I
60 PRINT SUM

문법이 맞는지 지금은 가물가물하군요. BASIC을 배워본 적이 없는 행운아분들을 위해 이 코드를 간단히 설명하자면, for문으로 I를 1부터 10까지 증가시켜 가며 SUM에 그 값을 더해서 출력하는 간단한 코드입니다. 이 코드를 실행시키면 55라는 답이 나왔습니다.

BASIC이라는 언어는 1963년에 개발되었습니다. Matz가 처음 Ruby를 발표한 것은 1995년이지요. 30년이라는 시간이 흘렀지만 for 문의 사용법은 크게 변하지 않은 것처럼 보이는군요.

# 이제는 들여쓰기를 배웠지요!
sum = 0
for i in 1..10
    sum = sum + i
end
puts sum

C 스타일 for 문과는 많이 다릅니다만 BASIC의 for 문과는 거의 똑같아 보이는군요.

그러나 똑같은 코드를 루비에서는 이렇게도 짤 수 있습니다.

# 변수 i 주변을 둘러싼 괄호는 shift + \ 를 누르면 나오는 세로줄 기호입니다.
sum = 0
(1..10).each { |i| sum = sum + i }
puts sum

Smalltalk에 익숙한 분이 아니라면 정말 생소한 코드일 겁니다. 저도 처음 봤을 땐 뭐 이런 코드가 있어? 싶었으니까요. 그러나 루비에 조금만 익숙해지면 for 문 따위 아예 잊어버리게 되실 겁니다. each가 훨씬 재밌거든요!

똑같은 코드를 조금만 풀어 써 봅시다. 위 코드는 사실 다음 코드와 동일한 의미를 갖습니다.

sum = 0
list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list.each { |i| sum = sum + i }
puts sum

그러면 이제 이 코드가 대체 뭘 하자는 건지 조금 알게 될 것 같습니다. 1..10 은 사실 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]을 나타내는 방법 가운데 하나입니다. (사실 두 가지 표현은 약간 다른 의미를 갖지만 지금은 중요하지 않으니 넘어갑시다.) { |i| sum = sum + i } 이 부분은 변수 i를 받아서, sum = sum + i 를 계산하는 코드 조각이지요. (코드 조각이 대체 뭐하는 거야? 하는 의문은 조금만 미뤄둡시다.) 그러면 list.each 의 역할은?
"list 객체의 모든 원소에 대해 { ... } 안에 들어있는 코드 조각을 각각 실행해라" 라는 의미를 갖는 멤버 함수입니다.

감이 잘 안오시나요? 그럼 이렇게 해보죠.

# ["R", "u", "b", "y"]의 원소들을 각각 str로 받아 puts str로 출력합니다.
["R", "u", "b", "y"].each { |str| puts str }
# 루비의 for 문은 기존의 언어에 익숙한 사람들을 위한 일종의 서비스입니다.
# 사실 루비의 for 문은 each를 이용해 구현되었기 때문에
# 다음 코드의 의미는 완전히 같습니다.
for str in ["R", "u", "b", "y"]
    puts str
end
R
u
b
y

짜잔. 리스트의 내용을 전부 출력하는 아주 간단한 코드가 만들어졌군요.

for 루프를 도는 두 가지 방법을 배웠습니다. (사실 더 있지만 여러분의 머리를 복잡하게 만들고 싶지 않군요. :P ) if문은 어떨까요?

# 제 나이가 이렇다는 건 아니구요 :P
age = 19

if age >= 40
    puts "대선에 출마할 수 있습니다"
elsif age >= 19
    puts "투표할 수 있어요"
else
    puts "투표할 수 없어요"
end
#=> 투표할 수 있어요

#if문과 실행문을 한줄에 붙여 쓰려면 사이에 then을 넣어줘야 합니다.
if age >= 18 then puts "술을 마실 수 있어요" end
#=> 술을 마실 수 있어요

아하. 간단합니다. C의 전통적인 { ... } 괄호 대신 if .. (then) .. elsif .. else .. end로 블록을 구분한다는 점이 다르군요.

그러나 여기부턴 또 새로운 세계입니다.

age = 19
# 조건문과 실행문의 위치를 이렇게 뒤집을 수도 있어요.
# 한줄에 붙여쓰고 싶다면 사실 이게 더 편리합니다.
# end 문을 안 넣어도 된다는 장점이 있거든요.
puts "술을 마실 수 있어요" if age >= 18
#=> 술을 마실 수 있어요

# 루비의 모든 문장은 항상 반환값을 갖습니다. 심지어 if문도 반환값을 갖지요.
# 여기서는 if문이 string을 반환하기 때문에
# puts가 값을 받아서 출력할 수 있습니다. puts (if age >= 19 then "투표할 수 있어요" else "투표할 수 없어요" end) #=> 투표할 수 있어요

if 문을 사용하는 방법도 한두가지가 아니군요. 뭐가 이렇게 복잡한가요?

그건 루비의 모토가 "There is more than one way to do it" 이기 때문입니다. 어떤 일을 하는데 방법은 한 가지가 아니라는 거죠.

지금까지 제가 접해본 언어들은 대부분 한 가지 일을 하는데 한가지, 많아야 두어가지 정도의 방법밖에 없었습니다. 루비는 좀 다릅니다. 입으로 말을 할 때 단어를 고르는 것처럼, 코딩을 할 때도 더 간단하게, 더 짧게, 더 재미있게, 더 우아하게 등등등 가운데 취향에 맞는 걸로 골라서 코딩할 수 있습니다.
물론 너무 코드를 간단하고 우아하게 만드는데 재미를 붙이다 보면 오히려 알아볼 수 없는 코드를 만들어내는 경우도 생기고, 정작 할일을 하는데 오히려 방해가 되는 경우도 생기죠. 사실 요즘 인기있는 또다른 언어인 파이썬은 그런 이유로 전통적인 "There is only one way to do it" 을 고수하고 있습니다.
마음에 드는 쪽으로 고르시면 됩니다. 그치만 루비 쪽이 좀더 재미있지 않나요?

루비에는 물론 while문도 있습니다.

sum = 0
i = 1
while i <= 10
    sum = sum + i
    i += 1
end
puts sum

# => 55

while을 실행문 뒤에 붙이는 것도 가능합니다. begin ... end 문을 사용하고 뒤에 while을 붙이면 됩니다.

sum = 0
i = 1
# begin ... end 는 C/Java의 { } 블록처럼 블록을 나타내는 루비의 방법입니다.
begin
    sum = sum + i
    i+=1
end while i <= 10
puts sum

# => 55

마지막으로 case .. when .. else .. end 만 살펴보고 지겨운 조건문들은 건너뛰도록 하죠.

i = 3

case i
when 1
    # i가 1이라면 1을 출력합니다.
    puts "1"
when 2, 3, 4
    # 여러 조건을 지정해 줄 수 도 있습니다.
    puts "2..4"
when 5..7
    # 범위도 지정해 줄 수 있구요
    puts "5..7"
when 8, 9..10
    # 여러 개의 범위나 조건을 같이 쓸 수도 있지요
    puts "8..10"
else
    # case문에서 else는 C/Java의 default와 같은 역할을 합니다.
    puts "1..10 사이의 수가 아니네요"
end

#=> 2..4

C/Java에서처럼 break 문을 쓰지 않아도 된다는 점이 편리한데, 이걸 혹시 단점이라고 생각하실 분이 있을지도 모르겠습니다. C/Java의 switch 문은 break 문을 쓰지 않고 여러 조건을 이어서 써 주면 여러 개의 조건에서 동일하게 동작하도록 코드를 짤 수 있지요. 그러나 루비는 여러 조건을 콤마로 구분해서 같이 지정해 주면 의도는 같지만 더 알기 쉬운 코드를 짤 수 있습니다. 루비의 승리네요 +ㅅ+

프로그래머의 미덕은 부지런함이 아니라 게으름이라고 합니다.
게으른 프로그래머일수록 더 효율적인 코드를 만들고, 다른 게으른 이들의 인생을 더 편하게 만들어줄 줄 알기 때문입니다.

게으른 프로그래머는 두자리 수 이상의 곱셈을 절대 손으로 하지 않습니다.
게으른 프로그래머는 반복되는 귀찮은 작업을 사람이 직접 손으로 하는 것을 죄악이라 믿습니다.
게으른 프로그래머는 또한 코드 반복이 일어나면 하늘이 무너진다고 믿습니다.

여기 게으른 우리들을 위한 프로그래밍 언어 루비가 있습니다.

루비는 마츠모토 유키히로(서양 쪽에서의 애칭은 Matz입니다.)라는 일본인이 만든 인터프리터 언어입니다. 펄, 파이썬, 스몰토크 등 기존의 스크립트 언어에서 이것저것 장점을 따다가 합쳐 만들어냈습니다. 루비는 코드의 반복을 죄악이라 믿고, 읽기에 귀찮지 않아야 하며, 무엇보다 코딩을 할 때 스트레스를 받지 않아야 한다고 믿는 사람들이 만든 언어입니다.

튜토리얼을 보고 대충 따라오시다 보면 다른 "귀찮은" 언어로는 돌아갈 수 없는 자신을 발견할지도 모릅니다.
다만, 가끔 설명이 좀 과하게 짧아도 이해해 주시기 바랍니다. 저도 게으른 프로그래머랍니다 :)



-1. if (programmer) { goto 0; } else { return; }

The three chief virtues of a programmer are: Laziness, Impatience and Hubris.
  - Larry Wall

시작하기 전에

당신은 프로그래밍 언어에 익숙하신가요? 이 튜토리얼은 당신이 C/C++/Java 가운데 한가지 언어에 익숙하다는 가정 하에 진행됩니다. 당신이 이 언어들에 별로 익숙하지 않다는 얘기는 아예 프로그래밍 언어를 접해본 적이 없다는 얘기겠지요. 그런 분들에게는 초보자를 위한 다른 튜토리얼을 추천합니다. 루비는 프로그래밍을 처음 해보는 사람에게도 훌륭한 안내자가 됩니다만, 이 튜토리얼은 프로그래밍을 해본 사람들에게 루비를 좀더 빠르게 이해시키기 위해 썼습니다.

C/C++/Java 가운데 한가지 언어에 익숙하시다면 아래 튜토리얼에 나온 예제들을 따라해 보세요. 모든 프로그래밍 언어 안내서에서 하는 말이지만, 백문이 불여일견(百問 不如一見)이고 백견이 불여일타(百見 不如一打)입니다. 프로그래밍 언어를 배우는데 직접 쳐보는 것보다 더 빠른 방법은 없습니다.

당신이 혹시 python이나 perl에 익숙하다면 한 2챕터 정도까지는 그냥 훑어만 보셔도 무방합니다. 인터프리터 언어에 이미 익숙한 분들이라면 루비가 정말 재밌어지는 건 한 2~3챕터 지난 다음부터일 겁니다.

그러면 정말로 시작해볼까요.



0. puts "Hello, ruby!"

Zero is the most natural number.
  - Dijkstra

루비를 시작하려면 먼저 인터프리터가 있어야겠죠. 윈도우 사용자라면 여기서 인스톨러를 다운받아서 그냥 설치하시는 게 제일 속편합니다. 안 게으르시다면 인터프리터랑 인터랙티브 쉘을 따로 받으셔도 되고, 아예 소스 코드를 다운받아서 컴파일하셔도 됩니다만, 우리는 게으르니까요. 다른 운영체제의 경우엔 죄송하지만 그냥 넘어가겠습니다. 맥 OS에는 그냥 구입할때부터 설치되어 있고, 리눅스도 데비안이나 레드햇 계열은 아주 쉽게 설치할 수 있다는군요.

설치하셨다면 실행 -> cmd 해서 커맨드 프롬프트를 열어 주세요. 그 다음 irb라고 치면 다음과 같은 프롬프트가 나옵니다.

irb(main):001:0>


irb는 인터랙티브 루비(interactive ruby)의 약자라고 합니다. 루비 커맨드를 실행하면 결과가 바로 돌아오는 편리한 쉘 환경입니다. 단도직입적으로, 여기에 우리의 첫번째 프로그램을 써볼까요.

irb(main):001:0> puts "Hello, ruby!"
Hello, ruby!
=> nil

아하, 쉽습니다. irb에서 나가시려면 exit라고 치면 됩니다.

다른 방법으로는 루비 인터프리터인 ruby를 사용할 수 있습니다. irb는 코드를 저장하고 고치고 할 수 없으니 대개의 루비 프로그램이 이 방법으로 실행되겠죠? 선호하시는 텍스트 에디터를 열어 아까 쓴 코드를 hello.rb라는 이름의 파일로 저장합니다. 그리고 커맨드라인에서 다음과 같이 인터프리터를 부릅니다.

ruby hello.rb

결과는 마찬가지로 Hello, ruby! 를 출력합니다.

Hello, ruby!

통합 개발환경에 더 익숙하신 분은 같이 인스톨러에 딸려오는 통합 개발환경인 SciTE나 FreeRIDE를 함께 사용해 보세요. 코딩하고 F5를 누르면 결과창에 결과가 출력될 겁니다.
eclipse에 익숙하신 분들은 eclipse에서 RDT를 설치하시면 루비를 지원합니다.
추가: 요즘 루비 커뮤니티에서 떠오르고 있는 IDE로 netbeans라는 IDE와 Aptana Studio라는 IDE가 경쟁중입니다. Aptana Studio는 예쁘고 기능이 다양하지만 아직 버그가 많다는 것이 많은 사람들의 의견이고, netbeans가 보다 안정적이고 사용하기도 편하다는군요. 저는 요즘들어 netbeans를 사용하고 있습니다.



1. print "ruby is easy!\n"

Example isn't another way to teach, it is the only way to teach.
  - Albert Einstein

(이 장에서 진행되는 코드는 irb에서 실행하시는 것이 편합니다.)


실행하는 법을 알았으니 방금 실행한 코드가 무엇을 뜻하는지 알아볼까요.

puts는 문자열을 출력하는 루비의 내장 함수입니다. 정확히 말하면, 문자열을 출력하고 뒤에 줄바꿈(\n)을 덧붙입니다. "Hello, ruby!"는 말할 것도 없이 문자열 타입이지요. 그런데 Java나 C/C++에 익숙하신 분이라면 puts의 문법이 좀 이상하게 느껴질 겁니다. 함수를 호출하는데 괄호가 없군요?

루비에서 함수를 호출할 때 괄호는 그냥 옵션입니다. 아래 코드 두 줄이 의미하는 바는 정확히 같습니다.

puts "Hello, ruby!"
puts("Hello, ruby!")


저는 괄호를 붙이지 않는 쪽을 선호합니다. 기존 언어에 익숙하신 분들은 좀 읽기 어려우실지도 모르지만 조금만 익숙해지면 훨씬 읽기 편하다는 것을 알게 될 겁니다.

문자열을 출력하되 뒤에 줄바꿈을 덧붙이지 않으려면 puts 대신 print를 쓰면 됩니다. 줄바꿈을 덧붙이느냐 아니냐의 차이 뿐 두 함수의 동작은 동일합니다.

irb(main):001:0> puts "ruby is easy!"
ruby is easy!
=> nil
irb(main):002:0> print "ruby is easy!"
ruby is easy!=> nil
irb(main):003:0>

문자열만 자꾸 출력하고 있으려니 프로그래밍 언어라는 생각이 안 드는군요. 이제는 진짜 계산을 해봅시다.

irb(main):001:0> 4+3
=> 7
irb(main):002:0> 3-8
=> -5
irb(main):003:0> 3*2
=> 6
irb(main):004:0> 4/3
=> 1
irb(main):005:0> 3**3
=> 27
irb(main):006:0> 2**100
=> 1267650600228229401496703205376


쉽군요. 벌써 짐작하셨겠지만 3**3은 33을 의미합니다. 그런데 잠깐, 마지막 숫자의 크기가 너무 터무니없지 않나요? 2100? 100비트 정수?

루비가 다루는 정수에는 크기 제한이 없습니다. 무제한 크기의 Bignum 타입을 기본으로 지원하기 때문에 "혹시 0x7fffffff보다 커지면 어쩌지..." 이런 고민 없이 그냥 계산하시면 됩니다. "그래도... 혹시라도... 무제한 크기의 정수 타입이 계산 효율이 떨어지면 어쩌죠?" 라고 물으시는 분들은, 그냥 32비트가 넘는 계산을 하지 마세요!

상수 말고 변수도 있겠지요?

irb(main):001:0> a=2
=> 2
irb(main):002:0> b=3.2
=> 3.2
irb(main):003:0> c=a+b
=> 5.2
irb(main):004:0> d="Ruby"
=> "Ruby"
irb(main):005:0> e=" is fun!"
=> " is fun!"
irb(main):006:0> f=d+e
=> "Ruby is fun!"
irb(main):007:0> puts c, f
5.2
"Ruby is fun!"
=> nil

변수를 선언할 때 "int a=2;" 라던가 "float b=3.2;" 같이 타입을 선언해줄 필요는 없습니다. 변수의 타입 정도는 루비가 알아서 처리해 줍니다.

루비의 모든 것은 오브젝트입니다. 숫자도 오브젝트, 문자열도 오브젝트죠. 그래서 숫자나 문자열에서 함수를 호출할 수 있습니다.

irb(main):001:0> -3.abs
=> 3
irb(main):002:0> 5.2.to_i
=> 5
irb(main):003:0> "Hello".length
=> 5
irb(main):004:0> "Hello".reverse
=> "olleH"
irb(main):005:0> "4.3".to_f
=> 4.3
irb(main):006:0> "4".to_i
=> 4
irb(main):007:0> -3.to_s
=> "-3"

슬슬 재밌어지죠?

진짜 프로그래밍 언어라면 배열이 있어야겠죠. 배열은 꺾은 괄호 안에 콤마로 구분해서 표현합니다.

irb(main):001:0> a = [-3, "Array", 2.6]
=> [-3, "Array", 2.6]
irb(main):002:0> a[1]
=> "Array"
irb(main):003:0> a[0] = -4
=> -4
irb(main):004:0> a
=> [-4, "Array", 2.6]
irb(main):005:0> a.reverse
=> [2.6, "Array", -4]
irb(main):006:0> a.length
=> 3
irb(main):007:0> [3, 1, 2].sort
=> [1, 2, 3]

문자열이나 상수와 마찬가지로 타입을 선언해줄 필요가 없습니다.
게다가 보시다시피 배열 안에 들어있는 오브젝트의 타입들이 서로 달라도 개의치 않습니다.
좀더 장난을 쳐볼까요?

irb(main):001:0> a = [-4, "Array", 2.6]
=>
[-4, "Array", 2.6]
irb(main):002:0> a[3] = 2.8
=> [-4, "Array", 2.6, 2.8]

irb(main):003:0> a << 3.0
=> [-4, "Array", 2.6, 2.8, 3.0]
irb(main):004:0> b = a + ["a", "b", "c"]
=> [-4, "Array", 2.6, 2.8, 3.0, "a", "b", "c"]
irb(main):005:0> c = b - ["Array", "b", 2.6, 3.0, -4]
=> [2.8, "a", "c"]
irb(main):006:0> c[2] = [1, 2]
=> [1, 2]
irb(main):007:0> c
=> [2.8, [1, 2], "c"]
irb(main):008:0> c * 3
=> [2.8, [1, 2], "c", 2.8, [1, 2], "c", 2.8, [1, 2], "c"]

보시다시피 크기도 마음대로 변하고, 덧셈, 뺄셈에 곱셈까지 가능합니다! 결과가 직관적이라는 건 말할 것도 없겠지요.

인덱스를 가지고도 장난을 쳐봅시다.

irb(main):001:0> a = ["A", "B", "C", "D", "E"]
=> ["A", "B", "C", "D", "E"]
irb(main):002:0> a[1..3]
=> ["B", "C", "D"]
irb(main):003:0> a[2, 2]
=> ["C", "D"]
irb(main):004:0> a[-1]
=> "E"
irb(main):005:0> a[-2]
=> "D"
irb(main):006:0> a[2..-1]
=> ["C", "D", "E"]
irb(main):007:0> a[1..2] = ["F", "G"]
=> ["F", "G"]

irb(main):008:0> a
=> ["A", "F", "G", "D", "E"]


짐작하셨겠지만 [1..3]은 1에서 3까지의 원소를 가져오고 [2, 2]는 2부터 시작해서 2개의 원소를 가져옵니다.
-1이나 -2 같은 음수를 가지고 인덱싱하는 것은 생소하게 느껴지겠지만 굉장히 편리합니다. 잘 이해가 안되신다면 이렇게 생각하세요

인덱스 크기가 0..4까지 있을 때

실제 인덱스:  0  1  2  3  4  0  1  2  3  4
음수 인덱스: -5 -4 -3 -2 -1  0  1  2  3  4


특히 [2..-1] 같은 인덱싱은 "2에서 맨 끝까지"를 표현할 수 있는 아주 편리한 방법입니다.

문자열도 일종의 배열이므로, 문자열에서 할 수 있는 웬만한 장난은 문자열에서도 다 가능합니다. 다만 주의하실 것이 있습니다.

irb(main):001:0> a = "ruby is fun!"
=> "ruby is fun!"
irb(main):002:0> a[0..3]
=> "ruby"
irb(main):003:0> a[0]
=> 114
irb(main):004:0> a[0, 1]
=> "r"
irb(main):005:0> a[0] == a[0, 1]
=> false
irb(main):006:0> a[0] == ?r
=> true

보시다시피, 문자열의 일부를 잘라내면 여전히 문자열이지만 문자열에서 글자를 하나만 인덱싱하면 하나의 바이트로 취급하기 때문에 정수가 반환됩니다.
그러나 첫번째 글자가 r인지 알기 위해서 꼭 r의 아스키 코드인 114를 알아야 하는 건 아닙니다. 위에서처럼 a[0, 1]로 한 글자 크기의 문자열을 잘라내서 "r"과 비교하는 방법이 있고, 그거 말고도 r이라는 글자 앞에 ? 기호를 붙이면 r에 해당하는 아스키 코드로 자동으로 변환해줍니다.

마지막으로 열어볼 자료형은 해시(Hash)입니다. 해시는 키워드(key)를 넣어주면 값(value)이 돌아오는 자료구조입니다.
C++ STL의 std::map이나, Java의 HashMap, 또는 TreeMap을 생각하시면 됩니다.

irb(main):001:0> a = {"ten" => 10, "hundred" => 100, "thousand" => 1000}
=> {"thousand"=>1000, "hundred"=>100, "ten"=>10}
irb(main):002:0> a["ten"]
=> 10
irb(main):003:0> a["thousand"]
=> 1000
irb(main):004:0> a["sparta"] = 300
=> 300
irb(main):005:0> a
=> {"thousand"=>1000, "hundred"=>100, "ten"=>10, "sparta"=>300}
irb(main):006:0> a[0] = "zero"
=> "zero"
irb(main):007:0> a
=> {0=>"zero", "thousand"=>1000, "hundred"=>100, "ten"=>10, "sparta"=>300}
irb(main):008:0> a["array"] = [1, 2, 3]
=> [1, 2, 3]
irb(main):009:0> a
=> {0=>"zero", "thousand"=>1000, "array"=>[1, 2, 3], "hundred"=>100, "ten"=>10, "sparta"=>300}

간단하지만 편리하죠.

이렇게 해서 루비의 기본 자료형을 전부 다 보셨습니다. 손이 좀 아프지만 보람찬 챕터였군요.
루비는 일본의 '마츠'라는 사람이 개발한 스크립트 언어입니다. 루비를 이용해 만든 웹 개발 프레임워크인 루비 온 레일스는 엄청나게 빠른 웹 개발 프로세스로 국내에서도 명성이 높죠. 이 사이트에 가보면 어떤 사람이 루비 온 레일스를 이용해 코멘트 기능과 관리자 기능이 포함된 그럴싸한 블로그를 15분만에 뚝딱 만들어내는 충격적인 동영상을 보실 수 있습니다.

루비의 첫인상은 뭐랄까... 굉장히 장난스럽습니다. 언어에 포함된 기본적인 기능과 함수들을 가지고 여러가지 장난을 쳐볼 수 있죠. 예를 들면

5.times { print "A" }
#=> AAAAA

[1, 2, 3, 4].each { |i| print i * 2 }
#=> 2468

이런 코드라던지

string = "hello, world!"
string['hello'] = 'goodbye'
print string
#=> goodbye, world!

이런 코드를 보면 "재밌다!" 는 생각이 한편 들면서도, 한편으로는 "재미있지만 좀 쓸데가 없어보인다" 싶기도 합니다. 그렇지만 루비는 이 정도가 전부가 아닙니다. 조금만 파보면 이 언어의 심상치 않음을 차차 깨닫게 됩니다.

먼저, 루비는 철저한 객체지향 언어입니다. 파이썬과 비슷한 특징이죠. 때문에 위에서처럼 5.times { print "A" }같은 함수가 가능해집니다. 5라는 "상수"가 마치 객체인 것처럼 멤버 함수 times 를 불러서 { print "A" } 라는 "코드 조각"을 마치 객체처럼 함수 인자로 넘겨줄 수 있는 거죠. 파이썬과 비슷한 특징이지만, 파이썬보다 훨씬 쉽고 강력하게 해치웁니다.

함수를 객체처럼 사용할 수 있는 특징은 함수형 프로그래밍의 여러가지 장점을 명령형 언어에서 간편하게 표현하도록 도와줍니다.

[1, 2, 3, 4, 5].map { |x| x * 2 }
#=> [2, 4, 6, 8, 10]

[1, 2, 5, 6, 7, 8, 11].select { |x| x % 2 == 0 }
#=> [2, 6, 8]

이런 코드를 짤 수 있는 거죠. 물론 코드 조각을 받아서 실행하는 프로그램을 직접 만들 수도 있습니다. yield 명령어로 인자로 들어온 코드 블록을 실행하면 됩니다.

# sum_for_10 함수는 코드를 받아 코드에 1..10까지의 숫자를 던져넣은 결과를 합해줍니다.
def sum_for_10

  sum = 0
  for num in 1..10
    sum += yield num
  end
end

sum_for_10 { |x| x }
#=> 55
sum_for_10 { |x| x*x }
#=> 385

이렇게요.

루비의 또 한가지 재미있는 점은, 모든 클래스 정의가 오픈되어 있다는 점입니다. 이미 정의된 클래스를 나중에 오버로딩하고 덧붙이는 것이 가능합니다. 그런데 아까도 말했듯 루비의 모든 것(변수, 상수, 코드, 함수 등등등)은 객체, 즉 클래스입니다. 그러므로 언어 자체의 동작을 코드 내에서 쉽게 바꿀 수 있다는 뜻이 됩니다. 예를 들어, 리스트(루비에서는 Array)에서 합을 구해주는 함수 sum을 추가해 볼까요

# Array를 열어 새 함수를 추가합니다.
class Array
  def sum
    result = 0
    each { |x| result += x }
    result
  end
end

[1, 2, 3, 4, 5].sum
#=>15

간단하군요. +ㅅ+

덕분에 심지어는 이런 코드도 가능해집니다.

# Numeric은 정수와 실수를 포함한 루비의 모든 수를 나타내는 클래스입니다.
class Numeric
  def minutes
    # 자기 자신에 60을 곱해 분으로 표현합니다.
    self * 60
  end
  def hours
    self * 60.minutes
  end
  def days
    self * 24.hours
  end
  def years
    self * 365.days
  end
end

3.years + 13.days < (3.1).years
#=> true

다른 언어에서라면 Date 클래스를 따로 만들고 연산자를 오버로딩하고 어쩌구 저쩌구 했어도 이렇게 짧고 우아하게 해결하지는 못했을 겁니다.

그러면 지금까지 나온 두가지, 함수형 프로그래밍과 열린 클래스 구조를 이용해서 어떤 일이 가능해지는지 볼까요

# HTML이라는 클래스를 정의합시다.
class HTML
  # method_missing은 메소드가 호출되었지만 그런 이름을 가진 메소드가 없을 때 불려집니다.
  def method_missing (method_id)
    # 메소드 이름을 받아 문자열로 변환해줍니다.
    tag_name = method_id.to_s
    # 코드 블록이 함께 주어졌다면 안쪽의 내용을 태그로 감싸서 출력합니다.
    # 아니면 그냥 태그 한줄만 출력합니다.
    if block_given? then
      puts "<" + tag_name + ">"
      yield
      puts "</" + tag_name + ">"
    else
      puts "<" + tag_name + "/>"
    end   
  end
end

tag = HTML.new

tag.html {
  tag.head {
    tag.title { puts "이름" }
  }
  tag.body {
    tag.h1 { puts "제목" }
    tag.p { puts "문단" }
  }
}

결과는 간단한 html 코드가 됩니다.

<html>
<head>
<title>
이름
</title>
</head>
<body>
<h1>
제목
</h1>
<p>
문단
</p>
</body>
</html>

루비는 이런 식으로 지금까지 제가 "프로그래밍"에 대해 갖고 있던 생각을 완전히 뒤집어 놓았습니다. 지금까지 제가 알고 있던 프로그래밍이 어떤 과제를 수행하는데 적합한 언어를 고르고, 그 언어의 문법으로 그 과제를 기술하는 일이었다면, 루비로 코딩하는 것은 과제를 언어의 문법에 맞추는 동시에 언어의 문법을 과제에 맞춰나가는, 양방향 과정입니다. 요즘 루비 커뮤니티에서 유행하는 말로 하면, domain-specific language를 만드는 거죠.

한줄요약 : 루비는 정말 재미있습니다.

'자랑 > 삽질' 카테고리의 다른 글

vim quickfix 새 탭에서 열기  (0) 2010/05/13
루비에 심취해 있습니다.  (4) 2007/03/27
아이고하느님 - 데스크탑 3호기에 바치는 부고  (4) 2004/04/18
tag 루비