Ruby의 each에 대해 공부하다가 작성해 본 구구단 프로그램입니다. 역시 한줄로 깔끔하게 정리가 되네요.
(2..9).each { |i| (1..9).each { |j| puts "#{i} * #{j} = #{i * j}" } }
C++ 프로그래머로써 이와 비슷한 기능을 구현하려면 어떻게 해야 할까 생각해보았습니다. 일단 한줄에 loop안에서 동작할 함수를 집어 넣기 위해서는 boost::lambda를 사용해야 합니다. 최종적으로 만들어질 문법을 먼저 보시죠.range(2, 9)(1, 9).f2(cout << _1 << '*' << _2 << '=' << _1 * _2 << '\n' );
위의 식은 아래와 같이 수행됩니다. for (int i = 2; i <= 9; ++i)
for (int j = 1; j <= 9; ++j)
cout << i << '*' << j << '=' << i * j << '\n';
여기서 f2는 두개의 인자를 받는 lambda임을 알려주는 멤버 함수입니다. lambda식 자체에서는 그 식이 몇개의 인자를 사용하는지를 알수 없기 때문에 이와 같이 명시적으로 두 개의 인자를 가지는 식이라는 것을 알려주어야만 합니다. ((boost::function이나 boost::function_traits에는 arity 멤버가 있어 이를 통해 그 함수가 몇개의 인자를 사용하는지를 알 수 있습니다.)) 또 하나 신경써야 할 점은 loop가 두번 중첩되었다고 해서 반드시 그 안에서 사용되는 함수가 인자를 두개 사용한다는 뜻은 아니라는 점입니다. 두번 중첩된 loop안에서 사용될 수 있는 함수의 인자수는 0개, 1개 또는 2개가 될 수 있습니다. 즉, 아래와 같이 사용할 수도 있다는 점이죠. range(2, 9)(1, 9).f1(cout << _1 << '\n' );
range(2, 9)(1, 9).f2(cout << _2 << '\n' );
위의 식들은 각각 아래와 같이 수행됩니다. for (int i = 2; i <= 9; ++i)
for (int j = 1; j <= 9; ++j)
cout << i << '\n';
for (int i = 2; i <= 9; ++i)
for (int j = 1; j <= 9; ++j)
cout << j << '\n';
그럼 이제 range 클래스를 구현해보겠습니다. 먼저 range 클래스에서 함수를 호출하기 전에 중첩되는 loop를 셋팅하기 위한 range(2, 9)(1, 9) 부분의 구현을 보시지요. class range
{
public:
range() {
}
range(int s, int e) {
s_.push_back(s);
e_.push_back(e);
}
range& operator()(int s, int e) {
s_.push_back(s);
e_.push_back(e);
return *this;
}
private:
vector<int> s_;
vector<int> e_;
};
간단히 operator()(int, int)를 구현하고 자신의 reference를 리턴하여 계속 vector에 범위가 추가될 수 있도록 하였습니다. 다음으로 구구단 출력을 위한 두개의 loop를 사용하는 f2 함수를 구현해보겠습니다. typedef function<void (int, int)> fn2;
...
void f2(fn2 func) {
switch (s_.size()) {
case 2:
for (int i = s_[0]; i <= e_[0]; ++i)
for (int j = s_[1]; j <= e_[1]; ++j)
func(i, j);
break;
case 3:
for (int i = s_[0]; i <= e_[0]; ++i)
for (int j = s_[1]; j <= e_[1]; ++j)
for (int k = s_[2]; k <= e_[2]; ++k)
func(i, j);
break;
default:
break;
}
}
구현하는 range 클래스에서는 최대 3번까지만 중첩되는 loop까지 지원하도록 하였습니다. ((이 갯수를 늘리려면 위의 함수에서 case 부분을 추가하면 되겠죠?)) 한가지 눈여겨 볼 점은 boost::function을 사용하여 int 두개를 인자로 받는 어떠한 형태의 callable 이든 가능하도록 했다는 점입니다. 물론 여기서는 lambda가 주로 사용될테지만요. :-) 그리고 f2 함수안에서는 중첩이 두번이나 세번인 loop만 지원됨을 알 수 있습니다. 중첩이 한번인 loop는 사용할 수 있는 변수가 하나뿐이기 때문에 이를 가지고 인자가 두개인 f2 함수를 사용할 수는 없겠죠? :-) for (int i = 2; i <= 9; ++i)
cout << i << j << '\n'; // where is j?
그럼 전체 코드를 보실까요? using namespace std;
using namespace boost;
using namespace boost::lambda;
class range
{
public:
typedef function<void ()> fn0;
typedef function<void (int)> fn1;
typedef function<void (int, int)> fn2;
typedef function<void (int, int, int)> fn3;
range() {
}
range(int s, int e) {
s_.push_back(s);
e_.push_back(e);
}
range& operator()(int s, int e) {
s_.push_back(s);
e_.push_back(e);
return *this;
}
...
void f2(fn2 func) {
switch (s_.size()) {
case 2:
for (int i = s_[0]; i <= e_[0]; ++i)
for (int j = s_[1]; j <= e_[1]; ++j)
func(i, j);
break;
case 3:
for (int i = s_[0]; i <= e_[0]; ++i)
for (int j = s_[1]; j <= e_[1]; ++j)
for (int k = s_[2]; k <= e_[2]; ++k)
func(i, j);
break;
default:
break;
}
}
...
void operator()(fn2 func) {
f2(func);
}
...
private:
vector<int> s_;
vector<int> e_;
};
int main()
{
range(2, 9)(1, 9).f2(cout << _1 << '*' << _2 << '=' << _1 * _2 << '\n' );
...
/*
range(1, 3)(4, 6).f0(cout << constant(9) << ' ' ); // 9 9 9 9 9 9 9 9 9
cout << '\n';
...
*/
}
아래쪽의 main 함수의 주석 부분을 보면 많은 다양한 사용 예들이 나와 있습니다. 다음 라인을 한번 살펴볼까요? range(1, 3)(4, 6).f0(cout << constant(9) << ' ' ); // 9 9 9 9 9 9 9 9 9
여기서 constant는 그 식을 lambda로 인식하도록 하기 위해 필요합니다. 만약 constant없이 그냥 cout << 9 << '\n' 이라고 썼다면 그냥 9를 출력하는 식이 되어버리는 것이죠.
좋은내용이네요.. 이 코드를 보고 열심히 공부해볼께요.
ReplyDelete혹시 boost::lambda 등에 대해서는 뭘 보고 공부하셨는지
여쭤봐도 될까요? reference는 boost 메뉴얼이긴하지만
혹시 in-depth 시리즈를 보신건가요?
[...] C++ of the Day #8 - 구구단 프로그램 [...]
ReplyDelete네. 사실 in-depth 시리즈는 나오는 대로 다 읽어오고 있습니다. Bjarne Stroustrup씨가 시리즈 에디터로 있으니 시리즈의 질은 믿을만할 것 같아 내용도 안보고 일단 사서 보는 편입니다.
ReplyDelete그래도 실제 작업할때는 역시 boost 사이트의 문서들을 많이 참고하는 편입니다. 제법 자주 바뀌는 라이브러리들도 있고 또 책 뒤지는 것보단 북마크에서 한번 클릭으로 찾아보는게 빠르니까요. :-)
(사이트에 가보니 ruby 전문가시네요. @_@ ruby 공부 시작했다가 아무래도 저한테는 python이 더 맞는것 같아서 지금은 손 놓고 있는데 문법이 참 재밌는 언어라는 생각이 듭니다. :-))
아...갱장하군요 +_+)=b
ReplyDelete답변감사합니다. 그런데 저는 원래는 java guy 인데;;;
ReplyDelete어쩌다 루비에 맛들려서 빠져버렸습니다. 자바에 빠질때처럼요 ㅎㅎ
요즘은 C++로 다시 선회중입니다. 그런 와중에 이곳을
발견했죠.
ㅋㅋ 저도 하마터면 ruby의 매력에 빠져버릴뻔 했죠. 지금은 ruby보다는 python쪽으로 올인하려고 합니다.
ReplyDelete왠지 ruby 언어는 The Zen of Python중 다음 항목들을 지키지 않는다는 듯한 느낌이 들더라고요. ;-)
Special cases aren't special enough to break the rules.
There should be one-- and preferably only one --obvious way to do it.