2008年5月30日金曜日

FizzBuzz Questions

思いつきでFizzBuzz Questionsをやってみる。お題はこちら:

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".


普通に解くと
#include <iostream>
using namespace std;

void FizzBuzz( int n ){
if( n % 15 )
if( n % 5 )
if( n % 3 )
cout << n;
else
cout << "Fizz";
else
cout << "Buzz";
else
cout << "FizzBuzz";
cout << endl;
}

int main(){
for( int i = 1; i < 101; i++ )
FizzBuzz( i );
}
こんな感じ?

当たり前ですが実行時に分岐しています。でもTMP(Template MetaProgramming)を使えばコンパイル時に分岐できます。
#include <iostream>
using namespace std;

template<int n, int mod3 = n % 3, int mod5 = n % 5>
struct FizzBuzz{
static ostream& print(){
return FizzBuzz<n-1>::print() << n << endl;
}
};

template<int n, int mod5>
struct FizzBuzz<n, 0, mod5>{
static ostream& print(){
return FizzBuzz<n-1>::print() << "Fizz" << endl;
}
};

template<int n, int mod3>
struct FizzBuzz<n, mod3, 0>{
static ostream& print(){
return FizzBuzz<n-1>::print() << "Buzz" < endl;
}
};

template<int n>
struct FizzBuzz<n, 0, 0>{
static ostream& print(){
return FizzBuzz<n-1>::print() << "FizzBuzz" << endl;
}
};

template<> ostream& FizzBuzz<0>::print(){
return cout;
}

int main(){
FizzBuzz<100>::print();
}
本当はループ展開とかもTMPでできるかも…でもやり方がよくわかりませんでした。

しかし更にすごい方法が…
#include <iostream>
#include <boost/preprocessor/arithmetic/mod.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>
using namespace std;

#define FizzBuzz( z, n, data ) \
BOOST_PP_IF( BOOST_PP_MOD( n, 15 ), \
BOOST_PP_IF( BOOST_PP_MOD( n, 5 ), \
BOOST_PP_IF( BOOST_PP_MOD( n, 3 ), \
#n, \
"Fizz" ), \
"Buzz" ), \
"FizzBuzz" ) \
"\n"

int main(){
cout << BOOST_PP_REPEAT_FROM_TO( 1, 101, FizzBuzz, );
}
コンパイル前にすべての処理を終わらせてしまい、preprocessorだけで単一の文字列を作り上げてしまうことも。いやはや。

2008年5月16日金曜日

SslStream

Boost.Asioを書いたけど、.NET Frameworkにも同様にSslStreamクラスが用意されている。
使い方もほぼ同じで

var c = new TcpClient( "ホスト名", 443 );
var s = new SslStream( c.GetStream() );

s.AuthenticateAsClient( "ホスト名" );

s.Write( Encoding.Default.GetBytes( "GET / HTTP/1.0\r\n\r\n" ) );

var r = new StreamReader( s, Encoding.Default );
Console.WriteLine( r.ReadToEnd() );
こんな感じ。違いは
  • Windows環境ではgetservbyname()が信用できないので直接443と書いている点
  • .NET FrameworkはByteとCharを区別しているので明示的な変換が必要になる点
  • Byte→Char変換を中断するのは面倒くさいのでReadToEnd()ですべて読み込んでしまっている点
でしょうか。

Boost.Asio

BoostというC++ライブラリが気になっていたのですが、1.35がリリースされ、Boost.Asioという非同期I/Oライブラリが追加されていることを知りました。このライブラリはWindows、Linux、各種Unix上で動作し、各種コンパイラをサポートしています。さらによく見たらOpenSSLによるSSLまでサポートしていました。

まずは試しに同期I/Oでhttps clientを書いてみました。

io_service i;
ssl::context c( i, ssl::context::sslv3_client );
ssl::stream s( i, c );
s.lowest_layer().connect( *ip::tcp::resolver(i)
.resolve( ip::tcp::resolver::query( "ホスト名", "https" ) ) );

s.handshake( ssl::stream_base::client );

write( s, buffer( "GET / HTTP/1.0\r\n\r\n" ) );

asio::streambuf res;
error_code e;
while( read( s, res, transfer_at_least(1), e ) )
cout << &res;
うーん、これだけで動いちゃいました。