'2007/04'에 해당되는 글 11건

  1. 2007.04.30 [루비 사용자 가이드] 메소드
  2. 2007.04.30 [루비 사용자 가이드] 객체 지향적 사고
  3. 2007.04.30 [루비 사용자 가이드] 이터레이터(Iterator)
  4. 2007.04.29 [루비 사용자 가이드] 컨트롤 구조들
  5. 2007.04.29 [루비 사용자 가이드] 예제 다시 살펴보기 (1)
  6. 2007.04.28 [루비 사용자 가이드] 배열(Array)
  7. 2007.04.28 [루비 사용자 가이드] 정규식(Regular Expression)
  8. 2007.04.27 [루비 사용자 가이드] 스트링
  9. 2007.04.27 [루비 사용자 가이드] 간단한 예제
  10. 2007.04.27 [루비 사용자 가이드] 처음 시작하기

[루비 사용자 가이드] 메소드

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.30 17:09

메소드란 무엇일까요? OO프로그래밍에서는 객체의 바깥에서 데이터를 직접 조작하는 것을 생각하지 않습니다. 대신 객체는 (그렇게 해 달라고 제대로 요청받았을 경우) 자신을 어떻게 조작할지에 대한 지식을 가지고 있습니다. 우리는 객체에 메시지를 전달하고, 그 메시지가 어떤 종류의 동작이나 의미있는 답을 일으킨다고 이야기하기도 합니다. 이런 일련의 일들은 객체 내부가 실제로 어떻게 동작하는지에 대해서 알거나, 주의를 기울일 필요 없이 이루어져야 합니다. 객체가 수행하도록 요청할 수 있는 동작(혹은 객체가 이해할 수 있는 메시지)을 객체의 메소드라 합니다.

루비에서는 도트 표기법을 사용해 메소드를 호출합니다(C++이나 자바와 동일합니다). 메시지를 전달받는 객체는 도트의 왼쪽에 표시됩니다

ruby> "abcdef".length
   6

직관적으로, 이 스트링 객체는 얼마나 긴지 질문을 받고 있습니다. 기술적으로, 우리는 "abcdef" 객체의 length 메소드를 호출하고 있습니다.

다른 객체는 length를 조금 다르게 해석하거나, 이해하지 못할 수도 있습니다. 어떤 메시지에 응답 하는 방법을 결정하는 것은 프로그램 실행 도중, 객체가 메시지를 받는 시점에 이루어지며, 어떤 동작이 일어나는지는 변수가 어떤 객체를 가리키고 있느냐에 따라 변할 수 있습니다.

ruby> foo = "abc"
   "abc"
ruby> foo.length
   3
ruby> foo = ["abcde", "fghij"]
   ["abcde", "fghij"]
ruby> foo.length
   2

length가 의미하는 바는 그 메시지를 받는 객체가 어떤 것이냐에 따라 달라 질 수 있습니다. 앞의 예에서 처음 foo 에 길이를 물어봤을 때, foo 가 단순한 스트링을 참조하고 있었고, 의미 있는 답은 한 가지 밖에 없었습니다. 두 번째 foo 는 배열을 가리키고 있고, 이때 논리적으로 가능한 답은 2 아니면 5나 10일 것입니다. 하지만 가장 일반적으로 수긍할만한 답은 물론 2 입니다(원한다면 다른 종류의 답을 낼 수도 있습니다).

ruby> foo[0].length
   5
ruby> foo[0].length + foo[1].length
   10

여기서 명심해야 할 것은 배열 객체가 배열이 되는 것이 어떤 의미인지를 알고 있다는 것입니다. 루비의 데이터는 그런 지식을 가지고 있기 때문에 외부의 요청을 적절한 방법으로 만족시켜줄 수 있습니다. 이런 구조는 프로그래머가 수 없이 많은 구체적인 함수 이름을 외우는 것을 막아줍니다. 왜냐하면 우리가 일상 언어로 표현할 줄 아는 개념에 대응하는, 상대적으로 적은 갯수의 메소드를 여러 종류의 데이터에 적용할 수 있고, 그 결과도 우리가 예상할 수 있는 것과 같을 것이기 때문입니다. 이런 OO 프로그래밍 언어의 특징을 (내 생각에 자바는 이런 부분을 별로 개발하지 못했습니다) 다형성(polymorphism)이라고 부릅니다.

객체가 이해할 수 없는 메시지를 받게 되면, 에러가 "발생(raise)"됩니다:

ruby> foo = 5
   5
ruby> foo.length
ERR: (eval):1: undefined method `length' for 5(Fixnum)

따라서 어떤 객체가 메소드를 처리하는 방법을 알 필요는 없지만, 그 객체가 어떤 메소드를 처리할 수 있는 지를 아는 것이 필요합니다.

메소드에 인자가 주어질 때는 보통 다음과 같이 괄호로 그 인자를 둘러쌉니다.

object.method(arg1, arg2)

하지만, 모호성이 없는 경우에는 괄호를 생략할 수도 있습니다.

object.method arg1, arg2

루비에는 self라는 특별한 변수가 있습니다. 그 변수는 메소드를 처리하는 객체를 참조합니다. 객체가 자기 자신의 메소드를 호출하는 일은 너무 자주 일어나기 때문에, 편의를 위해서 자기 자신의 메소드를 호출할 경우 "self."를 생략할 수 있습니다:

self.method_name(args...)

는 다음과 동일합니다.

method_name(args...)

전통적으로 함수 호출(function call)이라고 생각해 왔던 것들은 루비에서 단지 짧게 표기된 self에 대한 메소드 호출일 뿐입니다. 이렇게 해서 루비는 순수한 객체지향 언어로 남아있을 수 있습니다. 이런 함수같은 메소드는, 루비의 함수 호출이 어떻게 객체의 메소드 호출인지 알지 못하는 사람들이 편하게 사용할 수 있도록, 다른 프로그래밍 언어에서의 함수와 매우 유사하게 동작합니다. 원한다면 마치 객체의 메소드가 아닌 것 처럼 함수에 대해 말할 수 있습니다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 객체 지향적 사고

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.30 14:46

객체지향(Object oriented)은 캐치프레이즈입니다. 무언가를 객체지향적이라고 말하는 것으로, 여러분이 아주 똑똑해 보이도록 할 수 있습니다. 루비는 객체지향적 스크립트 언어라고 주장합니다. 하지만, 도대체 "객체지향"이란 정확히 어떤 뜻일까요?

그 질문에 대해 다양한 답이 있어왔지만, 아마도 그 모두가 동일한 것으로 귀결될 것입니다. 결론을 성급히 말하기 보다, 전통적인 프로그래밍 패러다임에 대해서 잠깐 생각해 보도록 합시다.

전통적으로 프로그래밍 문제는 어떤 종류의 데이터 표현(data representation)과 그 데이터를 다루는 처리절차(procedure)를 가지고 공략되어 왔습니다. 이런 모델 아래서 데이터는 수동적이고, 스스로 동작하지 않으며, 무력한 존재입니다. 데이터는 단지 능동적이고, 논리적이며, 전능하고, 커다란 처리 절차가 명령하는 데로 바뀔 뿐입니다.

이런 접근 방법의 문제는 프로그램이 사람에 불과한, 머리속에 세세한 부분을 명확히 기억하는 것이 제한되어 있는, 프로그래머에 의해 작성된다는 것입니다. 프로젝트가 커져감에 따라, 절차적인 핵심 부분들은 어떻게 전체가 돌아가는지를 기억하는 것이 어려울 정도로 커져버립니다. 그에 따라 생각의 사소한 오류나 단순한 타이핑 실수가 찾기 어려운 버그의 근원이 될 가능성도 더 커져버립니다. 복잡하고 의도하지 않은 상호 작용이 절차 부분 내에서 생겨나기 시작하고, 그런 상호작용을 잘 처리하는 것은, 싱싱한 산낙지가 빨판을 입 천장에 붙이지 못하도록 막는 것 만큼이나 어려운 일입니다. 전통적인 프로그래밍 패러다임 하에서 버그를 최소화하고 국소화 하도록 도울 수 있는 여러 프로그래밍 가이드라인이 있습니다만, 작업하는 방법을 근본적으로 바꾸는 것을 포함하는 더 좋은 해결 방안이 있습니다.

객체지향 프로그래밍은 평범하고 반복적인 논리적 작업을 대부분 데이터 자체에 위임하는 것입니다. 따라서 데이터에 대한 개념이 수동적인 것에서 능동적인 것으로 바뀝니다. 다른 식으로 이야기하자면,

  • 각각의 데이터를 뚜껑이 열려 있어서, 그 안의 내용물을 꺼내거나 여러 물건을 넣을 수 있는 박스로 생각하는 것을 중단합니다.
  • 각각의 데이터를 뚜껑이 닫혀있고, 잘 표시된 몇 개의 스위치와 다이얼이 달려있는 잘 작동하는 기계로 다루는 것을 시작합니다.

앞에서 "기계"라고 설명한 존재의 내부는 단순할 수도 있고, 복잡할 수 있습니다. 하지만 내부가 복잡한지 단순한지를 외부에서 알 수는 없으며, 기계를 열어보는 것도 (그 기계의 설계가 무언가 잘못되었다는 것이 절대적으로 확실하지 않은 한) 허용되지 않습니다. 따라서, 데이터를 처리하기 위해 스위치를 켜고 끄거나, 다이얼을 읽는 것과 같은 일만 하도록 허용됩니다. 일단 그런 기계가 만들어지면, 기계 내부가 어떻게 돌아가는지에 대해서 관심을 가질 이유가 없습니다.

이 이야기를 듣고, 아마도 쓸데 없는 일을 일부러 더 만들어내고 있다고 생각할수도 있습니다. 하지만, 이런 접근 방법은 모든 일이 한꺼번에 잘못되는 것을 멋지게 막을 수 있습니다.

실제적 가치는 없지만, 앞에서 설명한 개념의 적어도 일부분라도 보여주는 단순한 예를 들겠습니다. 자동차에는 주행 거리계가 있습니다. 그 목적은 가장 마지막으로 리셋 버튼이 눌린 시점부터 차가 얼마나 많은 거리를 주행했는지 저장하는 것입니다. 이것을 프로그래밍 언어로 어떻게 모델링할까요? C언어에서 주행 거리계는 단순한 수를 저장하는 변수이고, 아마도 float 타입으로 할 수 있을겁니다. 프로그램은 그 변수를 조금씩 증가시키고, 때때로 필요할 때 0으로 초기화하면서 사용할 것입니다. 이 구조에서 잘못된 것이 무엇일까요? 수많은 이유로 인해 버그가 잘못된 값을 변수에 대입할 수 있습니다. C로 프로그램을 짜 본 사람이라면 며칠이나 버그를 잡았는데, 결국에는 어이없게도 너무나 단순한 이유로 인해서 그 버그가 발생했음을 알게된 허탈한 경우를 겪어보았을 것입니다(그런 버그를 찾는 그 순간은 보통 이마를 때리는 찰싹 소리로 알 수 있습니다).

똑같은 문제가 객체지향적인 환경에서는 확연히 다른 방식으로 처리될 수 있을겁니다. 프로그래머가 주행 거리계를 디자인할 때, 첫번째 질문은 "이것과 가장 잘 들어맞는 나에게 익숙한 데이터타입이 뭐가 있을까?"가 아니고, "이것이 정확히 어떻게 동작해야 할까?"입니다. 그 둘은 심오한 차이가 있습니다. 정확히 주행 거리계가 어떤 것이고, 외부 세계가 그 주행거리계와 어떻게 상호작용 하는지를 결정하는 것은 약간의 시간이 필요합니다. 우리는 증가시키고, 리셋할 수 있고, 값을 읽을 수 있지만 다른 기능은 제공하지 않는 작은 기계를 만들기로 결정합니다.

우리는 주행 거리계에 특정 값을 지정하는 방법을 제공하지 않습니다. 왜일까요? 왜냐하면 주행 거리계는 그렇게 동작하지 않기 때문입니다. 주행거리계를 가지고 할 수 있는 일은 몇 가지 되지 않고, 우리가 허용하는 기능은 그게 전부입니다. 따라서 만약 프로그램에서 무언가가 주행 거리계의 값을 실수로 다른 어떤 값으로 변경하려고 한다면(예를 들어 자동차의 온도 조절 시스템의 목표 온도), 잘못된 일이 벌어졌다는 것을 바로 알 수 있습니다. 우리는 프로그램을 실행할 때 (언어의 특성에 따라서는 컴파일하는 동안에) 임의의 값을 주행거리계에 대입할 수 없다는 메시지를 받게 됩니다. 정확히 동일한 메시지는 아니어도, 최소한 그와 어느정도 비슷한 메시지를 받을 수는 있을겁니다. 그것이 에러를 방지하지는 못합니다. 그렇지요? 하지만 그로 인해 에러의 원인이 무엇인지를 빨리 알 수 있습니다. 이것이 OO 프로그래밍이 시간 낭비를 줄여주는 수많은 경우 중 한 가지입니다.

보통 이 수준보다 한 단계 더 추상화를 진행합니다. 왜냐하면 개별 기계를 만드는 것 만큼 기계를 만드는 공장을 만드는 것도 쉬운 것으로 드러났기 때문입니다. 보통 주행 거리계를 한 개만 만드는 일은 없을 것입니다. 대신 한 가지 패턴으로 여러 주행 거리계를 제작할 수 있도록 준비할 것입니다. 패턴(혹은 주행 거리계 공장)은 우리가 클래스(class)라 부르는 것에 해당하고, 그 패턴으로 제작되는 (혹은 그 공장에서 생산되는) 개별 주행 거리계는 객체(object)에 해당합니다. 대부분의 OO언어에서는 개별 객체를 생성하기 전에 클래스를 정의할 필요가 있지만, 루비는 그렇지 않습니다.

여기서 객체지향 언어를 사용하는 것이 좋은 객체지향 디자인을 하도록 강제로 만들수는 없다는 것을 밝힐 가치가 있을겁니다. 실제로 어떤 언어로든 불명확하고, 너저분하며, 부주의하고, 버그 투성이의 불안정한 프로그램을 짤 수 있습니다. 루비가 제공하는 것(특히 C++과 대비했을 때)은 객체지향 프로그래밍의 기법들이 자연스럽게 느껴질 수 있도록 해서, 아주 작은 프로그램을 작성할 때 마저도 노력을 줄이기 위해 나쁜 기법의 코드를 작성할 필요를 느낄 수 없게 하는 것입니다. 우리는 루비가 이런 경탄할만한 목적을 어떻게 달성하는지에 대해 이 가이드를 진행해 나가면서 살펴볼 예정입니다. 다음에 다룰 주제는 "스위치와 다이얼"(객체의 메소드)이고, 그로부터 "공장" (클래스)으로 진행해 나갈 것입니다. 여러분, 이 여행을 계속 함께 하십시다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 1 : Comment 0

[루비 사용자 가이드] 이터레이터(Iterator)

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.30 08:17

이터레이터(interator)라는 개념이 루비에서 처음 비롯된 것은 아닙니다. 그것은 객체지향 언어에서 일반적으로 사용되는 개념입니다. 비록 이터레이터라 불리지는 않았지만 Lisp에서도 동일한 것이 있습니다. 이터레이터라는 개념은 친근하지 않은 것이기 때문에 좀 더 자세히 설명할 필요가 있습니다.

아다시피 이터레이트(iterate)라는 동사는 같은 일을 여러 번 반복하는 것을 의미합니다. 따라서 이터레이터는 같은 일을 여러번 반복하는 어떤 것을 의미합니다.

코드를 작성할 때 여러 상황에서 루프가 필요합니다. C에서는 forwhile을 사용합니다. 예를 들면 다음과 같습니다.

char *str;
for (str = "abcdefg"; *str != '\0'; str++) {
  /* process a character here */
}

C의 for(...) 문법은 루프와 관련된 추상화(abstraction)를 돕지만, 프로그래머가 스트링의 내부 구조를 알아야만 *str 을 null과 비교하는 테스트를 작성할 수 있습니다. 이런 특징으로 인해 C가 저수준 언어처럼 느껴지게 됩니다. 고수준 언어는 이터레이션을 더 유연하게 지원하는 특징이 있습니다. 다음 sh 셀(shell) 스크립트를 살펴보세요:

#!/bin/sh

for i in *.[ch]; do
  # ... 여기서 각각의 파일에 대해 작업을 진행
done

현재 디렉터리에 있는 모든 C 소스 파일과 C 헤더 파일이 처리됩니다. 그리고 커맨드 셀이 파일 이름을 하나씩 얻어서 변수 i에 저장하는 작업의 세부 사항을 알아서 처리합니다. 나는 이렇게 동작하는 것이 C보다 더 고수준이라 생각합니다. 여러분은 안 그러신가요?

또한, 더 고려해야 할 것이 있습니다. 어떤 언어가 내장 데이터 타입에 대해 이터레이터를 제공하는 것은 좋은 일이지만, 프로그래머가 작성한 데이터 타입에 대해서는 저수준의 루프를 작성해야만 한다면 실망스러운 일입니다. OOP(객체지향프로그래밍,Object Oriented Programming)에서 프로그래머는 여러 데이터 타입을 정의하기 때문에, 중대한 문제가 됩 수 있습니다.

따라서, 모든 OOP 언어는 이터레이션을 위한 기능을 몇 가지 포함하고 있습니다. 몇몇 언어는 이러한 목적으로 특별한 클래스를 제공하기도 합니다. 반면 루비는 이터레이터를 직접 정의할 수 있도록 되어 있습니다.

루비의 String 타입에는 몇 가지 쓸모 있는 이터레이터가 포함되어 있습니다:

ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n"
<a><b><c>
   nil

each_byte는 스트링의 각 문자를 이터레이션합니다. 각각의 문자는 지역 변수 c에 저장됩니다. 같은 작업을 C언어 같은 스타일로 처리할수도 있습니다...

ruby> s="abc";i=0
   0
ruby> while i<s.length
    |    printf "<%c>", s[i]; i+=1
    | end; print "\n"
<a><b><c>
   nil

... 하지만 each_byte을 사용하는 것이 개념적으로 더 단순하고, 게다가 String 클래스(class)가 나중에 크게 변경되더라도 계속 동작할 가능성이 더 큽니다. 이터레이터를 사용하는 것의 잇점중 하나는, 대상 클래스 구현이 변경되더라도 프로그램이 망가지는 일이 더 적다는 것입니다. 이런 특성은 일반적으로 좋은 코드의 특징 중 하나입니다.(네~네~, 성질 급한 독자는 클래스(class)가 뭐냐고 벌써 질문을 하시는군요. 조금만 참아주세요. 나중에 다~ 나오게 됩니다.)

String이 제공하는 또 다른 이터레이터는 each_line입니다.

ruby> "a\nb\nc\n".each_line{|l| print l}
a
b
c
   nil

C에서 프로그램을 짤 때 가장 많은 노력이 들어가는 부분(구분자(delimiter) 찾기, 서브스트링 얻기, 등)을 이터레이터를 사용하면 쉽게 해결할 수 있습니다.

이전의 글에 나타난 for 문은 each 이터레이터로 이터레이션을 합니다. Stringeacheach_line과 동일합니다. 따라서 앞의 프로그램은 for를 가지고 다음과 같이 쓸 수도 있습니다:

ruby> for l in "a\nb\nc\n"
    |   print l
    | end
a
b
c
   nil

이터레이션 루프와 함께 retry를 사용할 수 있습니다. retry를 사용하면 루프를 이터레이션의 시작부터 다시 재시작합니다.

ruby> c=0
   0
ruby> for i in 0..4
    |   print i
    |   if i == 2 and c == 0
    |     c = 1
    |     print "\n"
    |     retry
    |   end
    | end; print "\n"
012
01234
   nil

앞의 예의 retryredo로 바꾸면 전체 이터레이션이 아닌 현재의 이터레이션만 다시 시작하게 되며, 다음과 같은 결과를 얻게 됩니다:

012
234

yield는 이터레이터의 정의 내부에 자주 보입니다. yield는 컨트롤을 이터레이터에 전달된 코드 블럭으로 옮깁니다(이것에 대해서는 프로시저 객체에서 더 자세히 설명할 예정입니다). 다음 예는 코드 블럭을 주어진 횟수만큼 반복하는 repeat라는 이터레이터를 정의합니다.

ruby> def repeat(num)
    |   while num > 0
    |     yield
    |     num -= 1
    |   end
    | end
   nil
ruby> repeat(3) { puts "foo" }
foo
foo
foo
   nil

retry를 가지고, 루비가 제공하는 표준 while처럼 동작하는 이터레이터를 작성할수도 있습니다.

ruby> def WHILE(cond)
    |   return if not cond
    |   yield
    |   retry
    | end
   nil
ruby> i=0; WHILE(i<3) { print i; i+=1 }
012   nil

이제 이터레이터가 어떤 것인지 감을 잡으셨나요? 약간의 제약이 있긴 하지만, 여러분 자신만의 이터레이터를 만들 수도 있습니다. 그리고 실제로 새로운 데이터 타입을 정의할 때마다 적절한 이터레이터를 함께 정의하는 것이 편리할 경우가 많습니다. 이런 면에서 앞에서 들었던 이터레이터의 예제들은 그리 많이 유용하지는 않습니다. 실용적인 이터레이터에 대해서는 클래스가 무엇인지를 더 잘 이해한 다음에 이야기할 수 있을겁니다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 컨트롤 구조들

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.29 17:58

이번 장은 루비의 컨트롤 구조를 더 자세히 살펴봅니다.

case

case는 순차적으로 여러 조건을 검사할 때 사용합니다. 이것은 겉보기에는 자바나 C의 switch와 비슷해 보입니다. 그러나, 나중에 살펴보게 되면 알게 되겠지만, 그보다 훨씬 더 강력합니다.

ruby> i=8
ruby> case i
    | when 1, 2..5
    |   puts "1..5"
    | when 6..10
    |   puts "6..10"
    | end
6..10
   nil

2..5는 2부터 5까지의 범위(range)를 나타내는 식입니다(2와 5를 모두 포함). 다음 식은 i의 값이 이 범위 안에 속하는지 검사합니다:

(2..5) === i

case는 여러 조건을 검사하기 위해 내부적으로 관계 연산자 ===를 사용합니다. 루비의 객체 지향적 특성에 따라 ===when 조건에 있는 객체의 특성에 맞게 해석됩니다. 예를 들어 다음 코드는 첫 번째 when에서는 스트링의 동등성 테스트를 진행하고 두번째의 when에서는 정규식의 패턴 매치를 수행합니다.

ruby> case 'abcdef'
    | when 'aaa', 'bbb'
    |   puts "aaa or bbb"
    | when /def/
    |   puts "includes /def/"
    | end
includes /def/
   nil

while

다음 번 글에서 이터레이터(iterators)를 사용하는 방법을 알게 되면, 명시적으로 루프를 작성할 필요는 거의 없어지겠지만, 루프를 만드는 편리한 방법도 있습니다.

while
if를 여러 번 반복하는 것과 같습니다. 우리는 이 명령을 이미 단어 추측 퍼즐과 정규식 프로그램에서 사용했습니다(예전의 글을 참조하세요). 거기서 while condition ... end으로 코드 조각을 둘러싸서, condition이 참인 동안 코드를 반복 수행 했습니다. 그렇지만 whileif는 한 문장에도 쉽게 적용할 수 있습니다:

ruby> i = 0
   0
ruby> puts "It's zero." if i==0
It's zero.
   nil
ruby> puts "It's negative." if i<0
   nil
ruby> puts i+=1 while i<3
1
2
3
   nil

때때로 테스트 할 조건의 역을 취할 필요가 있습니다. unlessif를 뒤집은 것이고, untilwhile를 뒤집은 것입니다. 이 명령어를 가지고 실험해 보는 것은 독자들의 몫으로 남겨놓겠습니다.

루프 내부에서 루프의 진행을 바꾸는 방법이 네가지 있습니다. 첫째로, break는 C에서와 마찬가지로 루프에서 완전히 벗어날 때 사용합니다. 두번째로, next는 루프의 컨트롤을 다음 번 반복(iteration)의 시작부분으로 넘깁니다(C의 continue와 같습니다). 세번째로, redo가 있습니다. redo는 현재의 반복을 다시 시작합니다. 다음 C코드는 break, next, redo의 의미를 goto로 보여주는 것입니다:

while (condition) {
label_redo:
   goto label_next;        /* ruby's "next" */
   goto label_break;       /* ruby's "break" */
   goto label_redo;        /* ruby's "redo" */
   ...
   ...
label_next:
}
label_break:
...

루프 내부에서 밖으로 나가는 네 번째 방법은 return입니다. return을 계산하게 되면 루프에서 뿐 아니라 그 루프를 포함하고 있는 메소드에서도 빠져나가게 됩니다. 만약 return에 인자가 주어지면, 그 값이 메소드 호출의 반환값으로 전달되며, 인자가 없으면 nil이 반환됩니다.

for

C 프로그래머라면 "for" 루프를 어떻게 만들 수 있을지 궁금할 것입니다. 루비에서는 for가 동일한 목적으로 제공되지만, 더 유연합니다. 다음 코드의 루프는 한 컬렉션(collection) (배열, 해시, 숫자 시퀀스, 등.)의 각 원소를 한번씩 방문합니다. 하지만 프로그래머가 컬렉션의 원소를 참조하기 위한 인덱스에 대해 고민할 필요는 없습니다:

for elt in collection
  # ... 여기서, elt는 컬렉션의 한 원소를 가리킴
end

컬렉션은 또한 값의 범위일 수도 있습니다(for 루프라 할 때는 대부분 이 경우입니다):

ruby> for num in (4..6)
    |    puts num
    | end
4
5
6
   4..6

다음 예에서는 배열의 원소를 차례로 처리합니다:

ruby> for elt in [100,-9.6,"pickle"]
    |    puts "#{elt}\t(#{elt.class})"
    | end
100    (Fixnum)
-9.6   (Float)
pickle (String)
   [100, -9.6, "pickle"]

하지만, 더 알아야 할 내용이 있습니다. for는 실제로는 each를 쓰는 다른 방법입니다. 따라서, each는 우리가 처음 다루게 되는 이터레이터의 예가 됩니다. 다음 두 코드는 동일합니다:

#  만약 C나 자바에 익숙하다면 이쪽이 더 좋게 느껴질겁니다.
for element in collection
  ...
end

#  스몰토크(Smalltalk) 프로그래머는 이것을 더 좋아할겁니다.
collection.each {|element|
  ...
}

이터레이터는 전통적인 루프를 대부분 대치할 수 있습니다. 그리고, 일단 한 번 익숙해지면 전통적인 루프보다 더 사용하기 쉽습니다. 따라서, 이제 이터레이터에 대해 더 자세히 배우도록 합시다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 예제 다시 살펴보기

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.29 15:47

이제 이전에 나왔던 예제를 자세히 살펴봅시다.

다음은 간단한 예제부분에 있던 내용입니다.

def fact(n)
  if n == 0
    1
  else
    n * fact(n-1)
  end
end
puts fact(ARGV[0].to_i)

이게 최초로 코드를 설명하는 부분이니까 각각의 행을 따로따로 자세히 들여다 봅시다.

팩토리얼(Factorials)

def fact(n)

첫 줄에서 def은 함수(정확히는 메소드(method))를 정의하는 명령입니다. 메소드가 무엇인지에 대해서는 나중에 이야기할겁니다. 여기서는 함수 factn으로 참조되는 유일한 인자(argument)만을 받는다는 것을 지정하고 있습니다.

if n == 0

if는 조건을 검사하기 위한 것입니다. 조건이 참이면 다음에 오는 코드가 계산되고, 조건이 거짓이면 else 다음에 오는 코드가 계산됩니다.

1

if 문의 결과 값은 조건이 참인 경우 1입니다.

else

조건이 거짓이면 여기부터 end 사이에 있는 코드들이 계산됩니다.

n * fact(n-1)

조건이 만족되지 않을 때 if식의 값은 nfact(n-1)를 곱한 값입니다.

end

첫 번째 endif 문을 닫는데 사용되었습니다.

end

두 번째 enddef 문을 닫기 위해 사용되었습니다.

puts fact(ARGV[0].to_i)

이 식은 우리의 fact()함수를 커맨드라인에서 제공받은 값을 가지고 호출해서, 그 결과를 출력합니다.

ARGV는 커맨드라인 인자를 저장하는 배열입니다. ARGV의 원소는 모두 스트링이기 때문에 fact()를 호출할 때 인자로 쓰기 위해서는 to_i를 사용해 정수로 변환해 주어야만 합니다. 루비는 펄(perl)과 달리 스트링을 자동으로 정수로 변환하지 않습니다.

만약 이 프로그램에 음수를 입력으로 넣으면 어떤 일이 벌어질까요? 한번 그 버그를 고쳐보시렵니까?

스트링

다음으로 우리는 스트링에 대한 글에서 다룬 퍼즐 프로그램을 살펴볼것입니다. 이 프로그램은 조금 더 길기 때문에 줄에 번호를 붙여서 설명시 참조하겠습니다.

01 words = ['foobar', 'baz', 'quux']
02 secret = words[rand(3)]
03
04 print "guess? "
05 while guess = STDIN.gets
06   guess.chop!
07   if guess == secret
08     puts "You win!"
09     break
10   else
11     puts "Sorry, you lose."
12   end
13   print "guess? "
14 end
15 puts "the word is ", secret, "."

이 프로그램에서는 새로운 컨트롤 구조인 while이 사용되었습니다. while과 그에 대응하는 end 사이에 있는 코드가 지정한 조건이 만족되는 한 반복해서 수행됩니다. 이 프로그램에서는 guess=STDIN.gets이 실제 수행되는 명령(사용자 입력을 받아서 guess라는 이름으로 저장)인 동시에, 조건(만약 입력이 없는 경우 전체 guess=STDIN.gets 식의 값을 담아두는 guess는 nil값을 가지고, while 루프는 중단됩니다)이기도 합니다.

STDIN은 표준 입력 객체입니다. 보통 guess=getsguess=STDIN.gets와 같은 일을 합니다.

2번 줄의 rand(3)은 0부터 2 사이의 수를 무작위로(random) 반환합니다. 이 무작위 수가 words 배열의 원소를 하나 꺼내는 데 사용됩니다.

5번 줄에서 우리는 STDIN.gets를 사용해 표준 입력에서 한 줄을 읽습니다. 만약 EOF (파일의 끝,end of file) 이라면 getsnil을 반환합니다. 따라서 이 while 명령 안의 코드는 사용자가 입력의 끝을 의미하는 ^D(도스(DOS)나 윈도(Windows)에서는 ^ZF6지요)를 입력할 때 까지 반복 수행됩니다.

6번 줄의 guess.chop!guess의 마지막 글자를 지웁니다. 여기서는 항상 guess의 끝 문자가 개행문자(newline)입니다. 왜냐하면 gets가 사용자가 키보드로 입력한 Return 키의 값을 저장하기 때문입니다. 하지만 그 문자는 우리에게는 불필요한 것이므로 여기서 제거합니다.

15번 줄에서는 미리 정한 비밀의 단어를 출력합니다. 그 작업은 puts로 이루어집니다(putsputstring의 약자입니다). puts에 새 개의 인자가 주어졌는데, 각각은 차례대로 출력됩니다. 동일한 작업을 한 개의 스트링만 puts로  전달하면서 할 수 있습니다. 이때 secret#{secret}라고 써서 그것이 그대로 출력되야 하는 리터럴이 아니고 계산되어야 하는 변수라는 점을 분명히 할 수 있습니다:

puts "the word is #{secret}."

많은 프로그래머들이 이 방법이 더 깔끔하다고 생각합니다. 이 방법은 한 개의 스트링을 표시하고, puts에 유일한 인자로 제공되기 때문입니다.

이제 스크립트에서 puts를 출력을 위해 사용하는 것에 익숙해졌습니다. 하지만, 이 스크립트의 4번 줄과 13번 줄에서는 print도 사용합니다. printputs는 완전히 같은 것은 아닙니다. print는 정확하게 주어진 인자를 출력합니다. puts는 인자의 출력과 함께 출력의 줄을 바꾸는 것을 보장해줍니다. print를 4번 줄과 13번 줄에서 사용할 때, 커서는 다음 줄로 이동하지 않고 막 출력된 내용 다음에 머무르게 됩니다. 그로 인해 사용자의 입력을 기다리는 프럼프트를 알아볼 수 있게 해줍니다. 일반적으로 다음의 네가지 출력 함수 호출은 동일합니다:

# puts는 출력될 대상에 개행문자가 있지 않은 경우 개행문자를 추가해줍니다:
puts  "Darwin's wife, Esmerelda, died in a fit of penguins."

# print를 사용시에는 개행문자를 명시적으로 넣어주어야 합니다:
print "Darwin's wife, Esmerelda, died in a fit of penguins.\n"

# 여러 스트링을 +로 붙여서 출력할 수 있습니다:
print 'Darwin's wife, Esmerelda, died in a fit of penguins.'+"\n"

# 혹은 여러개의 스트링을 출력 함수에 전달할 수도 있습니다:
print 'Darwin's wife, Esmerelda, died in a fit of penguins.', "\n"

알아둘 것 한가지: 때때로 텍스트 윈도우는 속도 향상을 위해 버퍼를 사용하여 구현될 수 있습니다. 즉, 각각의 출력 문자를 메모리에 모아서 개행문자를 출력하게 되거나 모인 문자가 일정 갯수 이상이 될 때만 출력해주는 경우가 있습니다. 따라서, 위의 단어 추축 게임 스크립트가 프럼프트를 표시하지 않는 경우, 버퍼링이 주범일 수 있습니다. 이런 일이 벌어지지 않게 확실히 하려면 프럼프트를 출력한 다음에 flush를 호출하면 됩니다. 그렇게 하면 표준 출력 디바이스(STDOUT라는 이름의 객체)에게, "기다리지 말고 버퍼에 있는 내용을 즉시 출력해."하고 명령하게 됩니다.

04 print "guess? "; STDOUT.flush
  ...
13 print "guess? "; STDOUT.flush

사실 우리는 다음 스크립트에서 이미 버퍼를 고려해 작성된 예를 보았습니다.

정규식

마지막으로 정규식에 대해 설명한 부분에서 보였던 예제를 살펴보겠습니다.

01 st = "\033[7m"
02 en = "\033[m"
03
04 puts "Enter an empty string at any time to exit."
05
06 while true
07   print "str> "; STDOUT.flush; str=gets.chop
08   break if str.empty?
09   print "pat> "; STDOUT.flush; pat=gets.chop
10   break if pat.empty?
11   re = Regexp.new(pat)
12   puts str.gsub(re, "#{st}\\&#{en}")
13 end

6번 줄에서 while 문의 조건이 true으로 하드코딩 되어있습니다. 따라서 무한 루프를 구성합니다. 하지만 8번과 10번 줄에서 루프를 탈출하기 위해 break 명령을 사용합니다. 이 두 break는 또한 "if 수식어(if modifier)."의 좋은 예이기도 합니다. if 수식어는 주어진 조건을 만족할 경우에만 if의 왼쪽에 있는 식을 수행합니다. 이것은 논리적으로 오른쪽에서 왼쪽으로 동작한다는 점에서 별난 구조입니다. 하지만 루비에서 이런 기능을 제공하는 것은 많은 사람들이 말을 할 때 사용하는 비슷한 패턴을 본따기 위한 것입니다. 또한 이 문장은 인터프리터에게 조건문에 얼마나 많은 코드가 포함되어야 하는지를 알려주기 위해 end를 사용할 필요가 없다는 점 때문에 간결하다는 장점도 지니고 있습니다. if 수식어는 관습적으로 조건과 명령이 스크립트 한 줄에 들어갈 수 있을 정도로 짧은 경우에만 사용됩니다.

단어 추축 게임과의 사용자 인터페이스 차이를 살펴보세요. 이 스크립트는 사용자가 Return 키만 입력할 경우 종료하게 되어 있습니다. 우리는 존재하지 않는지 검사하지 않고, 입력 스트링이 빈 스트링인지를 검사합니다.

7번과 9번 줄에서 우리는 "파괴적이지 않은(non-destructive)" chop을 사용합니다. 여기서 우리는 gets를 사용해 입력 받으면 항상 들어오게 되는 원치 않는 개행문자를 제거합니다. 이 설명에 덧붙여서, "파괴적인(destructive)" chop도 있습니다. 둘의 차이는 무엇일까요? 루비에서는 보통 '!'나 '?'를 특정 메소드 이름의 뒤에 붙입니다. 느낌표(!, 때때로 "뱅(bang!)"이라고 크게 발음합니다)는 무언가 잠재적으로 파괴적인 것을 표시합니다. 즉, 대상이 되는 것의 값 자체를 변경하는 경우를 말합니다. chop!은 스트링에 직접적으로 작용하지만, chop은 원본 스트링을 손상시키지 않고, 원본에서 마지막 문자만 없앤 복사본을 돌려줍니다. 다음에 둘 사이의 차이를 보여주는 예가 있습니다.

ruby> s1 = "forth"
  "forth"
ruby> s1.chop!       # s1을 바꿉니다.
  "fort"
ruby> s2 = s1.chop   # 변경된 복사본이 s2에 저장됩니다,
  "for"
ruby> s1             # ... 이때 s1에는 변화가 없습니다.
  "fort"

때때로 chompchomp!가 사용되는 걸 볼 수 있습니다. 이것들은 (chop보다) 더 선택적으로 작동합니다. 즉, 스트링의 맨 긑이 개행문자인 경우에만 끝의 문자가 제거됩니다. 따라서 예를 들어 "XYZ".chomp!이라고 해도 아무 변화가 없습니다. 만약 여러분이 둘 사이의 차이를 기억하기 위한 요령이 필요하다면, 무언가를 한 입 베어물기로 결정하기 전에 그게 먹을만한 것인지 결정하는 동물이나 사람과 그에 반해 무조건 잘라내는 도끼를 생각하면 됩니다. (역주-영어로 chop은 "잘라내다"라는 뜻이고, chomp는 "한 입 베어물다"라는 뜻입니다. 이 차이를 chop과 chomp 메소드의 차이를 설명하는 데 사용한 것입니다.)

8번과 10번줄에 다른 명명 규약이 나타납니다. 그것은 물음표(?, 때때로 "허?(huh?)"라고 크게 발음합니다)입니다. 물음표는 true이나 false 중 하나를 반환하는 "서술어(predicate)" 메소드입니다.

11번 줄은 사용자가 입력한 스트링에서부터 정규식 객체를 만듭니다. 실제 작업은 12번 줄에서 이루어집니다. 그 줄에서 gsub가 사용자가 입력한 정규식에 매치되는 부분 스트링을 안시(ANSI) 마크업으로 둘러싼 것으로 전역적으로(globally) 치환(substitute)합니다. 그리고 동일한 줄에서 치환의 결과가 출력됩니다.

우리는 12번째 줄을 다음과 같이 별개의 줄로 분리할 수 있습니다:

highlighted = str.gsub(re,"#{st}\\&#{en}")
puts highlighted

또는 "파괴적인" 방법으로 다음과 같이 할 수도 있습니다:

str.gsub!(re,"#{st}\\&#{en}")
puts str

12번째 줄의 나머지 부분을 다시 살펴봅시다. sten는 1번 줄과 2번 줄에서 텍스트를 역상과 보통으로 표시하도록 만드는 ANSI시퀀스로 정의되어 있습니다. 12번 줄에서 그 둘은 #{}안에 들어가서 그것 자체로 해석됩니다(따라서 우리는 변수 이름들이 출력되는것을 볼 수 없습니다). 그 두 변수 사이에 \\&가 있습니다. 이것은 약간 교묘합니다. 치환할 스트링이 큰 따옴표로 둘러싸여 있기 때문에 두개의 백슬래시는 하나의 백슬래시로 해석됩니다. 따라서 gsub가 실제로 보게 되는 것은 \&입니다. 이것은 패턴과 매치된 내용을 참조할 때 사용하는 특별한 코드입니다. 따라서 새로운 스트링이 출력되면 사용자가 두번째로 입력한 스트링에서 처음 입력한 패턴과 일치하는 부분들이 역상으로 표시됩니다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 1

[루비 사용자 가이드] 배열(Array)

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.28 09:35

배열(array)은 원소를 각괄호([])안에 콤마로 구분해 나열해서 생성할 수 있습니다. 루비의 배열은 다양한 타입의 객체를 포함할 수 있습니다(역주:C/C++, Java등에서 배열은 동일한 타입의 객체의 모임입니다).

ruby> ary = [1, 2, "3"]
   [1, 2, "3"]

배열은 스트링과 마찬가지로 서로 합쳐지거나 반복될 수 있습니다.

ruby> ary + ["foo", "bar"]
   [1, 2, "3", "foo", "bar"]
ruby> ary * 2
   [1, 2, "3", 1, 2, "3"]

배열의 일부분을 참조할 때 수(number) 인덱스를 사용합니다. 0이 배열의 맨 첫 원소입니다.

ruby> ary[0]
   1
ruby> ary[0,2]
   [1, 2]
ruby> ary[0..1]
   [1, 2]
ruby> ary[-2]
   2
ruby> ary[-2,2]
   [2, "3"]
ruby> ary[-2..-1]
   [2, "3"]

(음수 인덱스는 배열의 끝에서부터 인덱싱을 합니다. -1이면 배열의 맨 끝 원소입니다.)

배열을 스트링으로 만들 수도 있고, 스트링에서 배열을 얻어낼 수 도 있습니다. 각각 joinsplit을 사용합니다:

ruby> str = ary.join(":")
   "1:2:3"
ruby> str.split(":")
   ["1", "2", "3"]

해시(Hashes)

연관 배열(associative array)은 원소를 수 인덱스가 아닌 키(keys) 값으로 억세스할 수 있는 배열입니다. 키는 임의의 값이 될 수 있습니다. 이런 배열을 해시(hash)사전(dictionary)이라 부릅니다; 루비 세계에서는 해시라는 말을 더 선호합니다. 해시는 키와 값 쌍을 큰괄호({}) 안에 나열해서 만들 수 있습니다. 수를 이용해 배열에서 원소를 가져올 수 있었던 것처럼, 키를 사용해 해시에 저장된 값을 찾아낼 수 있습니다.

ruby> h = {1 => 2, "2" => "4"}
   {1=>2, "2"=>"4"}
ruby> h[1]
   2
ruby> h["2"]
   "4"
ruby> h[5]
   nil
ruby> h[5] = 10    # 원소 추가
   10
ruby> h
   {5=>10, 1=>2, "2"=>"4"}
ruby> h.delete 1   # 키를 기준으로 원소 삭제
   2
ruby> h[1]
   nil
ruby> h
   {5=>10, "2"=>"4"}

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 정규식(Regular Expression)

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.28 02:08

좀 더 재미있는 프로그램을 살펴보도록 합시다. 이번에는 어떤 스트링이 간결한 패턴(pattern)으로 인코딩한 기준에 들어맞는지를 판단할 것입니다.

패턴에는 특별한 의미를 지니는 문자나 문자 조합이 있습니다. 그 중 몇 가지는 다음과 같죠:

[] 범위 지정 (예를 들어, [a-z]a부터 z까지의 모든 문자를 의미합니다.)
\w 숫자 혹은 알파벳; [0-9A-Za-z]와 같습니다.
\W 숫자도 알파벳도 아닌 문자
\s 화이트 스페이스; [ \t\n\r\f]과 같습니다.
\S 화이트 스페이스가 아닌 문자
\d 숫자; [0-9]와 같습니다.
\D 숫자가 아닌 문자
\b 백스페이스(0x08) (범위 지정 안에서만 유효합니다.)
\b 단어 경계 (범위 지정 밖에서만 유효합니다.)
\B 비(non) 단어 경계
* * 직전에 있는 패턴의 0번 혹은 그 이상의 반복
+ + 직전에 있는 패턴의 1번 이상의 반복
{m,n} {m,n} 직전에 있는 패턴의 최소 m번 이상, 최대 n번 이하의 반복
? 최대 1번 이하의 반복; {0,1}과 같습니다.
| | 좌우의 두 가지 패턴 중 한 가지와 일치
() 그룹으로 묶기

위에 설명한 이상한 패턴을 부르는 일반적인 이름은 정규식(regular expressions)입니다. 루비에서 - 펄과 마찬가지로 - 정규식은 큰따옴표가 아니라 슬래시로 둘러싸여 있습니다. 예전에 정규식을 본 적이 없는 분들에게는 그렇게 일반적이고 자주 보던(regular) 것으로 보이지는 않을 겁니다. 하지만, 정규식에 익숙해지도록 시간을 약간 투자하는것이 현명한 일입니다. 정규식은 여러분이 골머리를 앓는 일이 없도록 해주는(그리고 코드의 크기도 크게 줄여주는) 효율적인 표현 능력을 제공합니다. 패턴 매치나 검색이나 텍스트 스트링을 처리할 다른 작업이 필요할 때 유용하게 쓸 수 있습니다.

예를 들어 어떤 스트링이 다음 설명에 들어맞는지 체크하고 싶다고 합시다. "스트링이 소문자 f로 시작해서 정확히 한개의 대문자가 나온 다음, 선택적으로 여러개의 소문자가 아닌 문자가 올 수 있습니다." 만약 경험이 풍부한 C 프로그래머라면 이미 머리속으로 몇십줄의 코드를 짜고 있을겁니다. 그렇지 않나요? 그렇게 직접 문제를 해결하기로 한다고 해도 이런 문제를 풀 수 있을겁니다. 하지만 루비에서는 스트링을 /^f[A-Z][^a-z]*$/라는 정규식과 비교하기만 하면 됩니다.

"<과 > 사이에 있는 16진 수"는 어떻게 처리할까요? 문제 없습니다.

ruby> def chab(s)   # "<과 > 사이에 있는 16진 수"
    |    (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
    | end
  nil
ruby> chab "Not this one."
  false
ruby> chab "Maybe this? {0x35}"    # 잘못된 괄호
  false
ruby> chab "Or this? <0x38z7e>"    # 사이비 16진 숫자
  false
ruby> chab "Okay, this: <0xfc0004>."
  true

정규식을 처음 보면 곤혹스러울 수 있지만, 조금만 써보면 생각을 너무나 경제적으로 표현할 수 있다는 점으로 인해 만족하게 됩니다.

여기 정규식을 테스트해 볼 수 있는 프로그램이 있습니다. regx.rb라는 이름으로 저장하고 "ruby regx.rb"를 커맨드라인에서 입력해서 실행해 보세요.

# ANSI 터미널에서 실행하세요!

st = "\033[7m"
en = "\033[m"

puts "Enter an empty string at any time to exit."

while true
  print "str> "; STDOUT.flush; str = gets.chop
  break if str.empty?
  print "pat> "; STDOUT.flush; pat = gets.chop
  break if pat.empty?
  re = Regexp.new(pat)
  puts str.gsub(re,"#{st}\\&#{en}")
end

이 프로그램은 두 번 입력을 요청합니다. 한 번은 스트링을 입력하는 것이고, 다른 한번은 정규식을 입력하는 것입니다. 스트링이 정규식과 매치되는지 검사를 해서 일치되는 부분은 역상(reverse)으로 표시됩니다. 세세한 부분에 너무 신경쓰지 마세요. 곧 이 코드에 대해 분석하게 됩니다.

str> foobar
pat> ^fo+
foobar
~~~

위에서 빨간색으로 표시된 것이 프로그램의 출력에서 역상으로 나타납니다. "~~~"라인은 텍스트 기반의 브라우저를 사용하는 사용자에게 위치를 보여주기 위한 것입니다.

몇 가지 입력을 더 테스트해봅시다.

str> abc012dbcd555
pat> \d
abc012dbcd555
   ~~~    ~~~

만약 이 결과가 여러분을 놀라게 했다면 이 페이지의 맨 처음에 보여드렸던 테이블을 살펴봐 주세요. \d는 문자 d와는 아무 관계가 없고 숫자 하나와 매치됩니다.

만약 패턴과 매치되는 방법이 하나 이상 있는 경우엔 어떻게 될까요?

str> foozboozer
pat> f.*z
foozboozer
~~~~~~~~

fooz 대신에 foozbooz가 매치됩니다. 정규식 매치는 가장 긴 매치 가능한 서브스트링에 대해 이루어집니다.

다음은 콜론으로 구분된 시간 필드를 찾아내는 패턴입니다.

str> Wed Feb  7 08:58:04 JST 1996
pat> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb  7 08:58:04 JST 1996
           ~~~~~~~~

"=~"는 정규식에 대해 매치하는 연산자입니다; 이 연산자는 스트링과 패턴이 매치가 된 위치를 반환하거나, 매치가 이루어질 수 없으면 nil을 반환합니다.

ruby> "abcdef" =~ /d/
   3
ruby> "aaaaaa" =~ /d/
   nil


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 스트링

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.27 23:52

루비는 수 뿐 아니라 스트링도 잘 다룰 수 있습니다. 스트링은 큰따옴표("...")나 작은 따옴표('...')를 사용할 수 있습니다.

ruby> "abc"
   "abc"
ruby> 'abc'
   "abc"

큰따옴표와 작은 따옴표는 몇 가지 차이가 있습니다. 큰 따옴표 스트링은 백슬래시를 사용한 문자 이스케이프와 #{}를 사용하여 스트링에 내포된 식의 계산을 허용합니다. 작은 따옴표 스트링은 이런 변환을 하지 않습니다. 따라서 작은 따옴표 스트링에서는 여러분의 눈에 보이는 것이 실제 그 스트링과 정확히 일치합니다. 다음은 몇 가지 예제입니다:

ruby> puts "a\nb\nc"
a
b
c
   nil
ruby> puts 'a\nb\n'
a\nb\nc
   nil
ruby> "\n"
   "\n"
ruby> '\n'
   "\\n"
ruby> "\001"
   "\001"
ruby> '\001'
   "\\001"
ruby> "abcd #{5*3} efg"
   "abcd 15 efg"
ruby> var = " abc "
   " abc "
ruby> "1234#{var}5678"
   "1234 abc 5678"

루비에서는 C에서보다 더 자연스럽고 똑똑하게 스트링을 다룰 수 있습니다. 예를 들어 스트링을 +로 서로 붙일 수 있고, *로 여러 번 반복할 수 있습니다:

ruby> "foo" + "bar"
   "foobar"
ruby> "foo" * 2
   "foofoo"

C에서 스트링을 붙이는 것은 명시적으로 메모리를 관리해야 하기 때문에 훨씬 더 복잡합니다:

char *s = malloc(strlen(s1)+strlen(s2)+1);
strcpy(s, s1);
strcat(s, s2);
/* ... */
free(s);

하지만 루비에서는 스트링이 메모리를 얼마나 사용할지를 프로그래머가 고민할 필요가 없습니다. 우리는 모든 메모리 관리로부터 자유롭습니다.

스트링을 가지고 할 수 있는 일은 어떤 것이 있을까요?

붙이기:

ruby> word = "fo" + "o"
   "foo"

반복하기:

ruby> word = word * 2
   "foofoo"

문자 뽑아내기(루비에서는 문자가 정수라는 것에 주의하세요):

ruby> word[0]
   102            # 102는 `f'의 아스키코드입니다.
ruby> word[-1]
   111            # 111은 `o'의 아스키코드입니다.

([]안에서 음수를 사용하면 스트링의 끝에서부터 인덱싱을 합니다. 양수인 경우엔 물론 앞에서부터 하지요.)

서브스트링 뽑아내기:

ruby> herb = "parsley"
   "parsley"
ruby> herb[0,1]
   "p"
ruby> herb[-2,2]
   "ey"
ruby> herb[0..3]
   "pars"
ruby> herb[-5..-2]
   "rsle"

같은지 비교하기:

ruby> "foo" == "foo"
   true
ruby> "foo" == "bar"
   false

이제 이런 특징을 사용해 무언가 재미있는 일을 해 봅시다. 이 예는 "단어 추측하기" 퍼즐입니다. 아마도 "퍼즐"이란 단어는 다음과 같은 프로그램에 붙이기엔 너무 고상한 말이겠지만요. ;-)

# guess.rb라는 이름으로 저장하세요.
words = ['foobar', 'baz', 'quux']
secret = words[rand(3)]

print "guess? "
while guess = STDIN.gets
  guess.chop!
  if guess == secret
    puts "You win!"
    break
  else
    puts "Sorry, you lose."
  end
  print "guess? "
end
puts "The word was ", secret, "."

지금 현재로써는 위의 코드 안의 자세한 부분에 대해 모른다고 너무 괴로워하지 마세요. 다음은 위 프로그램을 실행시킨 한 예입니다.

% ruby guess.rb
guess? foobar
Sorry, you lose.
guess? quux
Sorry, you lose.
guess? ^D
The word was baz.

(아마도 이보다 더 잘 할수 있었겠죠. 확률은 1/3이니까...)



신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 간단한 예제

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.27 09:33

팩토리얼(factorial)을 계산하는 프로그램을 짜 봅시다. 수학적으로 n 팩토리얼은 다음과 같이 정의되지요:

n! = 1                (n==0일 떄)
   = n * (n-1)!       (다른 경우)

루비에서는 다음과 같이 쓸 수 있습니다:

def fact(n)
  if n == 0
    1
  else
    n * fact(n-1)
  end
end

end가 반복되는걸 보실 수 있을겁니다. 루비는 이 때문에 "알골(Algol)하고 유사"하다고 일컬어집니다(실제로는 루비 문법은 에펠(Eiffel)이라는 언어를 많이 닯았습니다). 여러분은 또 return 명령이 없다는 사실을 알 수 있습니다. 루비 함수는 함수 내부에서 가장 마지막에 계산된 식의 값을 반환합니다. 그래서 루비가 return을 제공하기는 하지만 프로그램 작성시 굳이 사용하지는 않습니다.

작성한 팩토리얼 함수를 한번 돌려봅시다. 한 줄만 더 입력하면 동작하는 프로그램으로 만들 수 있습니다:

# 팩토리얼을 계산 하는 프로그램
# fact.rb라는 이름으로 저장하세요.

def fact(n)
  if n == 0
    1
  else
    n * fact(n-1)
  end
end

puts fact(ARGV[0].to_i)

여기서 ARGV는 커맨드라인 인자를 저장하고 있는 배열입니다. 그리고, to_i는 스트링을 정수로 변환합니다.

% ruby fact.rb 1
1
% ruby fact.rb 5
120

40을 인자로 넘기면 잘 동작할까요? 보통 계산기는 오버플로우가 나는 큰 수일텐데요...

% ruby fact.rb 40
815915283247897734345611269596115894272000000000

잘 동작하네요. 실제로 루비는 컴퓨터 메모리가 허락하는 한 모든 정수를 처리할 수 있습니다. 따라서 400!도 계산 가능합니다:

% ruby fact.rb 400
64034522846623895262347970319503005850702583026002959458684
44594280239716918683143627847864746326467629435057503585681
08482981628835174352289619886468029979373416541508381624264
61942352307046244325015114448670890662773914918117331955996
44070954967134529047702032243491121079759328079510154537266
72516278778900093497637657103263503315339653498683868313393
52024373788157786791506311858702618270169819740062983025308
59129834616227230455833952075961150530223608681043329725519
48526744322324386699484224042325998055516106359423769613992
31917134063858996537970147827206606320217379472010321356624
61380907794230459736069956759583609615871512991382228657857
95493616176544804532220078258184008484364155912294542753848
03558374518022675900061399560145595206127211192918105032491
00800000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000

한 눈에 이 값이 제대로 된 것인지 체크할 수는 없지만, 올바른 답임에 틀림 없습니다. :-)

입력/계산(Input-evaluation) 루프

여러분이 루비를 인자 없이 실행하면, 표준 입력에서 명령을 읽어들여서 모든 입력이 끝난 후 그 명령을 실행합니다:

% ruby
puts "hello world"
puts "good-bye world"
^D
hello world
good-bye world

위에서 ^D는 컨트롤-D를 의미합니다. 그것은 유닉스(Unix)계열의 OS에서 입력의 끝(end-of-input)을 시스템에 입력하는 편리한 방법입니다. 도스(DOS)나 윈도(Windows)에서는 F6^Z를 살포시 눌러주시면 됩니다.

(역주: 이 단락에서 설명하는 eval.rb 대신 irb를 사용해도 됩니다.) 루비에는 eval.rb라는 프로그램도 포함되어 있습니다. 그 프로그램은 인터액티브 환경에서 여러분이 명령을 키보드로 입력하고, 그것을 실행한 결과를 여러분에게 보여주도록 되어 있습니다. 이 가이드에서는 그 환경을 많이 사용하게 될 겁니다.

만약 여러분이 ANSI 기능을 지원하는 터미널을 사용한다면(거의 대부분의 유닉스 시스템이나 ANSI.SYS이나 ANSI.COM을 실행시킨 도스환경이 이에 해당됩니다; 윈도 XP는 그렇게 하는게 거의 불가능하지요. 하지만 적당한 터미널 프로그램을 구하면 됩니다.) 향상된 eval.rb를 사용해서 인덴테이션 지원과 경고사항 보고, 그리고 컬러 하이라이팅 기능을 사용할 수 있을겁니다. 여기 짧은 eval.rb 세션을 하나 보여드립니다:

% ruby eval.rb
ruby> puts "Hello, world."
Hello, world.
   nil
ruby> exit

hello world가 code>puts를 사용해 출력됩니다. 다음 줄에 나오는 nil은 가장 마지막에 계산된 식의 값입니다. 루비는 명령문(statements)식(expressions)의 구분이 없기 때문에 코드를 계산한다는 말과 코드를 실행한다는 말이 동일한 말입니다. 여기서 nilputs가 의미있는 값을 반환하지 않는다는 것을 의미합니다. 우리는 이 인터프리터를 exit라는 명령을 입력해서 종료할 수 있습니다(물론 ^D도 여전히 동작합니다).

이 가이드 안에서, "ruby>"는 eval.rb프로그램의 입력 프럼프트를 나타냅니다.



신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

[루비 사용자 가이드] 처음 시작하기

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.27 01:53

우선 여러분의 컴퓨터에 루비가 설치되어 있는지를 알아야 합니다. 셀 프럼프트(여기서는 "%"로 표시되니 부디 %를 입력하지는 말아주세요)에서, 다음을 쳐 보세요.

% ruby -v

(-v는 인터프리터에게 루비의 버전을 출력하라고 명령하는 겁니다.) 그리고 나서 Enter 키를 과감히 눌러주세요. 만약 루비가 설치되어 있다면 다음과 비슷한 무언가가 출력될겁니다:

% ruby -v
ruby 1.8.3 (2005-09-21) [i586-linux]

만약 루비가 설치되어 있지 않다면 시스템 관리자에게 전화해서 설치해 달라고 하거나, 직접 설치를 해야 합니다. 루비는 자유 소프트웨어이기 때문에 설치나 사용에 아무 제약이 없습니다.

이제 루비와 놀아봅시다. 루비 프로그램을 커맨드라인에서 -e 옵션을 사용해 다음과 같이 입력할 수 있습니다:

% ruby -e 'puts "hello world"'
hello world

더 일반적으로 루비 프로그램은 파일에 저장될 수도 있습니다.

% echo "puts 'hello world'" > hello.rb
% ruby hello.rb
hello world

여기 있는 코드같이 간단한 경우라면 모르지만, 복잡한 프로그램을 짤 때는 진짜 텍스트 에디터를 써야 할 겁니다.

아주 놀랍고도 유용한 일을 커맨드라인 한줄에 들어맞을 정도로 작은 프로그램으로도 할 수 있습니다. 예를 들어 다음 코드는 현재 디렉터리의 모든 C 소스 파일과 헤더 파일을 ".bak" 확장자를 붙여서 백업하고, 원본에서는 foobar로 바꿔치기 합니다:

% ruby -i.bak -pe 'sub "foo", "bar"' *.[ch]

다음 프로그램은 UNIX의 cat 커맨드와 같습니다(애석하게도 cat보다는 느립니다):

% ruby -pe 0 file
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 : Comment 0

티스토리 툴바