Tag Archive for 'c++'

TopCoder SRM 407

レッドカーペットみてビール3缶飲んで、さあて寝るかと歯を磨いてたら、TwitterからTopCoderがどうたらというポストが流れていて、あ、今夜24時からだったんだ! と気づいたのが23:50。あわてて参加しました。

250 問題 : SpiralWalking

Problem Statement

あたえられた矩形を時計まわりにぐるぐる周り、すべてのマス目を歩き尽くすまでに道にあったポイントを合計せよという問題。 ぐるぐる周るのってどうするんだっけや、とか壁を表現するのはどういうテクニックをつかうんだったけな、とか試行錯誤してるうちに40分くらいたって、やっとsubmit。ひどす。

コード:

bool visited[52][52];
class SpiralWalking {
   public:
   int totalPoints(vector <string> levelMap) {
     for (int i(0); i<52; i++) for (int j(0); j<52; j++) visited[i][j] = false;
     for (int i(0); i<52; i++) {
       visited[i][0] = true; visited[i][51] = true;
       visited[0][i] = true; visited[51][i] = true;
     }
     int W(levelMap[0].size()), H(levelMap.size());
 
     for (int i(0); i<W+2; i++)
       visited[0][i] = true; visited[H+1][i] = true;
     for (int i(0); i<H+2; i++)
       visited[i][0] = true; visited[i][W+1] = true;
 
     int score(0), x(1), y(1), dx(1), dy(0);
     while (true) {
       int i(0);
       bool turned(false);
       for (; i<4; i++) {
         if (not visited[y+dy][x+dx]) break;
         if (dx > 0) {
           dx = 0; dy = 1; turned = true;
         } else if (dx < 0) {
           dx = 0; dy = -1; turned = true;
         } else if (dy > 0) {
           dx = -1; dy = 0; turned = true;
         } else if (dy < 0) {
           dx = 1; dy = 0; turned = true;
         }
       }
       if (4 == i) break;
       if (not turned) score += levelMap[y-1][x-1]-'0';
       visited[y][x] = true;
       x += dx; y += dy;
     }
     score += levelMap[y-1][x-1]-'0';
     return score;
   }
};

我ながらこれはひどい。でもいちおうSystem Testにパス。

500 問題 : CorporationSalary

Problem Statement

詳しい説明は nishioさんとこを参照

250より500の方が簡単じゃないか! 再帰でいっぱつやん! と5分くらいで書いてsubmit。

コード:

class CorporationSalary {
   public:
   long long calcSalary(int x, vector <long long> &salaries, vector <string> &relations) {
     if (-1 != salaries[x]) return salaries[x];
 
     int count(0);
     int total(0);
     for (size_t i(0); i<relations.size(); i++) {
       if ('Y' == relations[x][i]) {
         total += calcSalary(i, salaries, relations); count++;
       }
     }
     if (0 == count) {
       salaries[x] = 1; return 1;
     }
     salaries[x] = total; return total;
   }
 
   long long totalSalary(vector <string> relations) {
     int N(relations.size());
     vector <long long> salaries(N, -1);
     for (int i(0); i<N; i++)
       salaries[i] = calcSalary(i, salaries, relations);
 
     long long total(0);
     for (int i(0); i<N; i++)
       total += salaries[i];
     return total;
   }
};

しかし、System Test でこけました。 原因を調べてみると、ふつうに桁溢れ。アホすぎる… ><

終わってから、calcSalary の中を s/int total/long long total/ にして Practice Room でテストしてみるとちゃーんとパスしました。もったいない。

ひどい内容だったにもかかわらず、Ratingは +6 で 921 になりました。やっぱりもったいない。


やはりアルコール入れると集中力、注意力ともに低下してよくないですね ><

C++でヘッダファイルに定義を書くかどうかで生成されるシンボルが変わる

不思議な現象に出くわしたので、エントリにしてみるテスト。

C++でプログラミングしていて、ヘッダにクラスのコンストラクタの定義を含めず、宣言だけを書くというのはよくあることだと思います。 次のようなヘッダファイルを考えてみます。

sym.h:

#ifndef _SYM_H_
#define _SYM_H_
class Sym {
public:
  Sym();
 
}; // Sym
#endif // _SYM_H_

実装は sym.cc に書きます。

#include "sym.h"
 
Sym::Sym() {
}
 
int main() {
  Sym sym;
  return 0;
}

これらをg++でコンパイルして、生成されるオブジェクトファイルを覗いてみると、驚きました。

% nm sym | c++filt
0000200c D _NXArgc
00002008 D _NXArgv
00001fde T Sym::Sym()
00001fd6 T Sym::Sym()
00002000 D ___progname
00001fc8 t __dyld_func_lookup
00001000 A __mh_execute_header
00002004 D _environ
         U _exit
00001fe6 T _main
00002010 d dyld__mach_header
00001fb4 t dyld_stub_binding_helper
00001f74 T start

なんで Sym::Sym()が2つ存在するんだ?

そして、ヘッダファイルに定義も含めると、1つになります。

#ifndef _SYM_H_
#define _SYM_H_
class Sym {
public:
  Sym() {}
}; // Sym
#endif // _SYM_H_
nm sym | c++filt
0000200c D _NXArgc
00002008 D _NXArgv
00001fb6 T Sym::Sym()
         U ___gxx_personality_v0
00002000 D ___progname
00001f90 t __dyld_func_lookup
00001000 A __mh_execute_header
00002004 D _environ
         U _exit
00001f9e T _main
00002010 d dyld__mach_header
00001f7c t dyld_stub_binding_helper
00001f3c T start

なぜだー。Mac OS X / Linux で確認しています。

使えるメモリが極めて限られている状況だと、こういう小さなところでも無駄は減らしたい。でもヘッダファイルに実装を書きまくるのは、できれば避けたいものです。

原因が分かるかたいらっしゃれば、ご教授ください ><


再現するためのコード一式を、symbols.zip に固めました。 Makefile中の CXXFLAGS += -DCOMPILE_BY_SPLIT のところを無効にするとヘッダファイルでコンストラクタの定義を行い、有効にすると.ccの中で定義する、というふうにしています。

Gainer++ その2

昨日のつづき。 今日のコミットで、以下の動作が確認できました。

  • コンストラクタでのモード切り替え
  • アナログ入力
  • デジタル入力

アナログ入力の確認中に、どうも値がヘンなことになってて30分くらい悩んだのですが、参考書によると、アナログ端子はアンテナみたいになるので値が不定になるよ、とのこと。そんなことも知らない素人です。

gainer-ruby のコードでは、デジタル入力が1ポートしか取れないことになっていたので、ビット演算したりして4ポートぶんとれるようにしました。

それにしてもプロトコルの仕様が知りたいよ、とうろうろしていたら、ちゃーんと公開されてました。さすが。ちゃんと調べなきゃですね。

Gainer++ – GainerをC++から使う

Gainerを、C++から使えるようなラッパを書いています。 Rubyから扱えるgainer-rubyのコードをC++に書き移して、Gainer++としてCodeReposに登録しておきました。コードはこちら

まだI/Oポート周りのコードを移していないのですが、Gainer I/OモジュールのLEDを点灯させるサンプルプログラム (gainer-rubyのページにあるもの) は動いています。(gainer-led.cc)

せっかくより書き易いRubyのライブラリがあるのにC++で書くなんて逆行してるよ! と自分でもつっこまざるを得ないのですが、Gainerをより低レイヤ (CoreMIDIとか)と連携させてみたい、というのが動機です。

つかったりコード直したりしてみてくださいね。

関連

Boost on TopCoder

TopCoderの問題では、よく文字列をスペースで区切る必要があります。

Javaとかだと、あっさりString#splitがあるのでラクすぎるのですが、C++だとそうもいかない。 でも、Boost String Algorithms Library が使えれば、 splitが用意されているので

string "yey i'll be splitted";
vector <string> out;
boost::algorithm::split(out, string, boost::algorithm::is_space());

なんてするだけで、スペースで区切った文字列の配列を得ることができて、ちょう便利です。

果たして、TopCoderの環境でも、これが使えるのか試してみました。

Boostのバージョンを確かめる

stringを返すようなProblem StatementをPracticeから探します。 そんで、

#include <boost/version.hpp>
...
return BOOST_LIB_VERSION;
...

とかして、Compile → Test し、結果を確認。1_32が返されてきました。1.32か。

splitしてみる

なにはともあれ、上で書いたようなsplitのコードを Compile してみます。

/usr/include/boost/config/compiler/gcc.hpp:92:7: warning: #warning “Unknown compiler version – please run the configure tests and report the results”

というwarningがでますが、ちゃんとmakeできた模様。やった。 TopCoderのコンパイルファームにてBoostをビルドした環境 (gcc on ???) が、確認されていないプラットフォームだったのではないでしょうか。

まとめ

TopCoderの環境でも、文字列の分割にBoostがつかえることが分かりました。バージョンは2007.09.21 現在で 1.32でした。

他にも文字列の分割方法はいろいろあるので、あとでまとめておこうと思います。

std::map, std::set

TopCoder::SRM::351 に取り組んでいました。先週は参加できなかったので、2週間ぶりです。

いつもどおり、250点問題を無難にこなしたあと、500点問題へ。 なんとかの一つ覚えみたいに、幅優先探索で挑んでみたのですが、どうしてもできない。 なんでかなぁといろいろ試してみたところ、STLの使い方に問題があったのでした。

やりたいこと

struct Combi {
  int g, s, b;

  Combi(int gg, int ss, int bb) : g(gg), s(ss), b(bb) {}
};

みたいな構造体を、コンテナに入れたい。

トラブル : std::set に insert できない、std::map で find できない

operator <がないよ、とコンパイラに諭されてしまいます。

ならばと、3つのメンバg, s, b について

bool operator <(const Combi &rhs) const {
  return g < rhs.g || s < rhs.s || b < rhs.b;
}

みたいなoperatorを書いてみたところ、コンパイルは通るのですが、insert/find ともに結果が妙なことになってしまいます。どうも、operator <のせいで、異なる値を持つ2つのオブジェクトが同一視されてしまうようなのです。

異なる値をもつオブジェクトをsetにinsertしようとしても、同じオブジェクトと見なされてinsertできない、あるいは要素xがないはずのmapにfind(x)をかけても見つかってしまうという。


うーん、とそんなところで悩んでいろいろしているうちに時間切れに… どんなよい刀でも、手に馴染んでいなければ自分を切ってしまうということですね。

しかし、なぜ同一視されてしまうのだろう…

std::stringのコンストラクタ

TopCoder::SRM::348::500::LostParentheses で提出したコードがChallengeされて撃沈した件を調べていました。

原因

std::stringのコンストラクタの仕様を間違って捉えていた。

詳細

数字と+,-から成る文字列が与えられたときに、-でsplitするコードをどう書くか。 string::findを使って、順に部分文字列を取得しようと思いました。

ダメなコード

 1 #include <string>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 void split(const string &str) {
 6    string::size_type left(0);
 7    string::size_type right(str.find('-'));
 8 
 9    while (string::npos != right) {
10       cout << string(str, left, right) << endl; // XXX : まちがい
11 
12       left = right + 1;
13       right = str.find('-', left);
14    }
15 
16    cout << string(str, left, right) << endl;
17 }
18 
19 int main() {
20    const char *strs[] = {
21       "55-50+40",
22       "00009-00009+0009-09+09-09+09-09"
23    };
24 
25    for (int i=0; i<2; i++) {
26       cout << "str : " << strs[i] << endl;
27       split(strs[i]);
28       cout << endl;
29    }
30 
31    return 0;
32 }

ところが、これでは2つめのケースがおかしくなります。

実行結果

% ./strfind
str : 55-50+40
55
50+40

str : 00009-00009+0009-09+09-09+09-09
00009
00009+0009-09+09 <<<<
09+09-09+09-09
09+09-09
09

‘<<<<’の行が、ちゃんとsplitできていないことが分かります。

理由

標準C++ライブラリ string> によると、std::stringのコンストラクタで(別の文字列, 数値1、数値2) を渡すと別の文字列の数値1の位置から数値2の長さぶんだけコピーした文字列がつくられるとあります。ここを、別の文字列の数値1の位置から数値2の位置までの部分文字列をコピーした文字列がつくられると勘違いしていたのがまちがいのもとでした。

修正

問題の10行目を、

 cout << string(str, left, right-left) << endl; // XXX : OK

とするだけでOKでした。

所感

考えたアルゴリズム自体は、遠回りだけれど間違っていないものだっただけに、文字列処理のところでミスるなんていうのは痛いところです。もったいない。 C++の標準ライブラリのリファレンスがやっぱり手元に欲しいなとか思いました。 Mac OSXだとmanで引けないんだよなぁ。Ubuntu 7.04だとあるんだけれど。

契約による設計 in C++

オブジェクト指向入門 第2版 原則・コンセプト を読んでいます。 契約による設計という考えに触発され、自分でもやってみようと思いたちました。

単純なマクロ

さっそく、C++でやってみようということで、

#define REQUIRE
#define ENSURE

といった空マクロを用意しました。

int square10(int x) { // 10 以下の正数のみを対象とする二乗関数
  REQUIRE {
    assert(0 < x);
    assert(10 > x);
  }

  int ret(x * x);

  ENSURE {
    assert(x == sqrt(ret));
  }
  return ret;
}

と、一応は書けます。

しかし、本来ならヘッダファイル内の宣言に含めて インターフェイスとしてクライアントに提示したいものです。 さらに欲を言えば、Doxygenなどで自動的に抽出してほしい。

実現のために

  1. それぞれの関数の入口/出口に、制約条件をチェックするhookを仕掛ける
  2. 制約条件を関数/クラス宣言内に書ける
  3. Doxygenのタグを拡張

といったことが必要になりそうです。

1を実現するには、auto変数をうまく利用すればいいんじゃない? と思いました。 2を実現するには、それぞれの制約条件を表現するクラスを、クラス内クラスとして定義すればよさそう。 3はどうしたらいいんだろう…

NDEBUG環境では、制約条件のオブジェクトを生成しないように、マクロを使えばよさそうです。

#include <cassert>

// constrain macro.
#ifdef NDEBUG
#define CONSTRAIN(method)
#else // NDEBUG
#define CONSTRAIN(method) 
   method##_constrain _constrain(*this)
#endif // NDEBUG

class Bird {
public:
   Bird() : // awake and not flying at first.
      flying_(false),
      sleeping_(false) {}

   void fly() {
      CONSTRAIN(fly);
      flying_ = true;
   }

   void sleep() {
      CONSTRAIN(sleep);
      sleeping_ = true;
   }

   friend class fly_constrain;
   friend class sleep_constrain;

private:
   bool flying_;
   bool sleeping_;

   // ----------------------------------------------------------------
   // constrains
   class fly_constrain {
   public:
      fly_constrain(Bird &b) : bird_(b) {  // requirement
         assert(false == bird_.flying_);
         assert(false == bird_.sleeping_);
      };

      ~fly_constrain() {                   // ensure
         assert(true == bird_.flying_);
         assert(false == bird_.sleeping_);
      }

   private:
      Bird &bird_;
   }; // fly_constrain

   class sleep_constrain {
   public:
      sleep_constrain(Bird &b) : bird_(b) { // requirement
         assert(false == bird_.sleeping_);
         assert(false == bird_.flying_);
      };

      ~sleep_constrain() {                  // ensure
         assert(true == bird_.sleeping_);
         assert(false == bird_.flying_);
      }

   private:
      Bird &bird_;
   }; // sleep_constrain

}; // Bird

// test
int main(int argc, char *argv[]) {
   Bird piyoko;

   piyoko.fly();
   piyoko.sleep(); // cannot sleep while flying !

   return 0;
}

実行結果

% c++ -Wall -g dbc.cxx
% ./a.out
dbc.cxx:57: failed assertion `false == bird_.flying_'
zsh: abort      ./a.out

鳥は飛びながら眠ることはできないという制約にひっかかっているの図です。

まとめ

C++でも契約による設計は実現できそうです。 しかしC++らしく手間がやたらとかかると。 CONSTRAINマクロは、メソッド名を自動抽出できればもう少しうまく書けそう。

他にも良いアイデアがあればぜひ教えてください。

参考リンク


オブジェクト指向入門 第2版 原則・コンセプト
バートランド・メイヤー 酒匂 寛
翔泳社 (2007/01/10)
売り上げランキング: 13136
おすすめ度の平均: 5.0
5 オブジェクト指向の基礎からみっちり学べる

文字列の中で同じ文字が一番長く続くところを見つける

プログラマの日々の鍛錬として、小さな関数を書く練習というのが有効だと思います。あちこちから探してきてちょこちょこと書いてみるつもり。

今日はJoel on Software p.p.181:

5 . 文字列の中で同じ文字が一番長く続くところを見つける

をやってみます。

アルゴリズム

文字列の終端まで最初から1文字ずつ読んでいって、状態を記録していくという単純なアプローチで。 状態は、

  • 現在の文字
  • 現在の文字へのポインタ
  • 現在の文字が連続している回数
  • これまでで最大の連続回数
  • これまでで最大だったときの文字へのポインタ

だけあれば十分そう。

コード

/*!
 * @brief 文字列の中で同じ文字が一番長く続くところを見つける
 *
 * @param [in] str 探索する文字列
 * @return 一番長く同じ文字が連続する部分文字列へのポインタ
 */
char *find_max_continuous_part(const char *str)
{
   /* A1. [init] */
   char *cur = (char *)str;
   char *max_from = cur;
   int count = 0;
   int max_count = 0;
   char ch = cur[0];

   /* A2. finish? */
   for (; *cur != ''; cur++)
   {
      /* A3. continuous ? */
      if (*cur == ch) // continuous
      {
         count++;
      }
      else /* A5 discontinuous */
      {
         if (count > max_count) // update
         {
            max_count = count;
            max_from = cur - count;
         }
         count = 1;
         ch = *cur;
      }
   }

   if (count > max_count) // update
   {
      max_count = count;
      max_from = cur - count;
   }

   return max_from;
}

実行例

./find_max_length_part hhhhaaaaaaabbccceeeeffffffffffffeeeaa

=> ffffffffffffeeeaa

所感

アルゴリズムを描くのに5分くらい。 コードは10分くらいで書けました。

初期化と、ループを抜けた後の処理 (update)に重複があるのが美しくありません。どう改善したらよいかな。


Joel on Software
Joel on Software
posted with amazlet on 06.10.22
Joel Spolsky 青木 靖
オーム社

More Effective C++::Item 5. Be wary of use-defined conversion functions

暗黙的な型変換に注意しようという話。

暗黙的な型変換には、3とおりの方法がある。

  1. 引数を一つだけ取るコンストラクタ、あるいは 引数に対してデフォルトの値を設定しているコンストラクタ
  2. operator sometype() const みたいな型変換演算子

しかし、暗黙的な型変換は予期せぬ振る舞いに発展する可能性があるので、できれば避けたほうがよい。あなたの意図したものと、コンパイラが解釈した結果は異なる場合があるからだ。

型変換演算子

たとえば、ペットショップの犬を表すクラスを考える。 それぞれの犬にはIDが振られている。

class Dog {
   public:
      void bark();
      operator int() const { return id_; }
   private:
      int id_;
      char *name_;
};

いま、犬の名前を表示しようとして、うっかりDogクラスには operator char*()がないことを忘れているとすると、

Dog pochi;
std::cout << pochi << std::endl;

なんてやってしまう。結果は、pochiのIDが表示されるというもの。

経験を積んだC++プログラマは、たいてい型変換演算子を避ける。 たとえば、std::stringにはoperator char*()の代わりに c_str()が存在する。

引数を一つだけ取るコンストラクタ

こいつはよりたちがわるい。

ふつうにクラスを設計していても、こうしたコンストラクタを記述することはままある。そして、こうしたコンストラクタも、暗黙的な型変換に一役買ってしまうことがあり得る。

class Array {
   public:
      Array(int n); // n個の要素で初期化するコンストラクタ
};


Array a(10);
Array b(10);

for (int i=0; i<10; i++) {
   if (a == b[i]) {        // ここでミスしている !
      ...
   }
}

なんて風に、a[i] == b[i]と書くべきだったところを書き間違えても、コンパイラは文脈から if (a == static_cast(b[i])) といったように解釈してしまう。意図が正確に反映されていないばかりか、見つけにくいバグになってしまっている。

対策

explicitキーワードをコンストラクタにつける。このキーワードがついたコンストラクタは、暗黙的な型変換には使えなくなる。

もう一つ方法はあるけど、それはトリッキーになるので略 (ヒント: proxy class (see Item 30))。


所感

暗黙的な型変換は、こちらがコーディングをミスったときに、コンパイラがプログラムをコンパイルできるように、都合良く解釈してしまうため、どこで間違いを犯したか分かりづらいことが問題。

既存の大規模プログラムに手を入れたり、バグを発見するような場合を考えると、こうした言語仕様は厄介だ。

自分の意図と異なる振る舞いをプログラムが見せることほど腹立たしいものはない。コードの字面の下で何が行われているかを、正確に把握していないと正しいコードが書けないような言語は人に優しくない。これで大規模なプロジェクトをまともに作ろうという方が無理だと思う。

自分の意図を自然に表現でき、そして書いたとおりのことがストレートにコンピュータに伝わり、実行されることが重要だろうと思う。