Home > Tips Archive

Tips Archive

C++ ランダムソート 1

C++でファンクタだけでランダムソートを作ろうとちょっとがんばってみたけどだめだった。


template< typename T >
class RandomSorter {

public:
    RandomSorter(){}

    bool operator()(T a, T b) {
        int seedA = 0;
		int seedB = 0;
		int memorySize;
		int randA;
		int randB;

		memorySize = std::min(sizeof(T), sizeof(int));

		memcpy(&seedA, &a, memorySize);
		memcpy(&seedB, &b, memorySize);

		srand(seedA);
		randA = rand();
		srand(seedB);
		randB = rand();

		return randA < randB;
    }
};

引数のa, bを無理やりint型に当てはめて、その値でsrand()することで乱数の値を一定にし、最後の比較をソート処理全体で矛盾が生じないようにしたつもりなんだけども、どうもsrand()のシードの大小とその結果作成されるrand()の大小が一致するようだ。例えばTがint型だったらseedA = a, seedB = bとなるわけだが、ほとんどの場合でseedA > seedB ⇒ randA > randBとなる(VS2008の場合)。したがって意図したようなソートにならない。もう一工夫必要なのか。

以上

構造体の初期化

テイルズ オブ ヴェスペリアの体験版をダウンロード中。

C言語の構造体を初期化します。書記化します。


typedef struct Hoge {
   int a;
   int b;
   char *c;
} Hoge;

int main(int argc, char* argv[]) {
   Hoge hoge = {0, 0, NULL};

   printf("%d, %d, %s", hoge.a, hoge.b, hoge.c);

   return 0;
}

こんな感じです。{}が初期化子です。メンバの値を列挙すればよいです。が、これは省略できます。


int main(int argc, char* argv[]) {
   Hoge hoge = {0};

   printf("%d, %d, %s", hoge.a, hoge.b, hoge.c);

   return 0;
}

省略すると、数が足りないメンバは0で初期化されます。
トリビアルメモです。

以上

CppUnitの使い方 4~登録マクロ生成(おまけ)

CppUnitの使い方のおまけ。
テストコードを書いたはいいものの、テストスイートに登録するためのマクロを書くのが結構めんどくさい。
そういうわけで
test_関数名_連番()
みたいな名前にしてるときにマクロを生成してくれるちょんスクリプトをメモ。
ソースコードをコマンドライン引数で渡す。


<?php

if (empty($argv[1])) exit(1);

$file = file($argv[1]);

$result = array();
foreach ($file as $line) {
    if (preg_match('/.*void\s+(test.+?)\(.*/i', $line, $matches)) {
        $result[] = "CPPUNIT_TEST(" . $matches[1] . ");";
    }
}

foreach ($result as $line) {
    echo $line . "\n";
}

?> 

phpだけど。

以上

CppUnitの使い方 3~ソースの書き方

CppUnitの使い方ラスト。

目次
1.準備
2.プロジェクトの構成
3.ソースの書き方

まずはメイン。コンソールでテキストベースに使う場合はHogeTest.cppを次のように書く。HogeTest.cppやらファイルの構成やらについては前回を参照のこと。


#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>

int _tmain(int argc, _TCHAR* argv[])
{
    CppUnit::TextUi::TestRunner runner;
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
    CppUnit::Outputter* outputter = new CppUnit::TextOutputter(&runner.result(),std::cout);
    runner.setOutputter(outputter);
    int ret = runner.run() ? 0 : 1;
    return ret;
}

Hogeクラスのテストコードは以下のようにする。


// 必要なヘッダの #include
#include "Hoge.h"
#include <stdio.h>
#include <stdlib.h>
#include <string>

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestAssert.h>


class Hoge_Test : public CppUnit::TestFixture {
    // テストスイート作成
    CPPUNIT_TEST_SUITE(Hoge_Test);  // クラス名を指定してテストスイート(テストの集合)を作成する。
    CPPUNIT_TEST(test_foo_001);  // 下で定義したテストコードを登録する。これを書かないとそのテストはテストされない。
    CPPUNIT_TEST_SUITE_END();
private:
    // 必要な メンバ変数/関数

    // テスト用ツール関数はこの辺に定義しとく。以下はサンプル。
    
    /**
     * 整数を文字列に変換する。
     */
    std::string itos(int i) {
        char buff[50];
        sprintf(buff, "%d", i);
        return std::string(buff);
    }

    /**
     * 乱数を発生させる。0以上N未満。
     */
    int my_rand(int N) {
        return (int)((double)rand() / ((double)RAND_MAX + 1) * N);
    }





public:

    /*
     * コンストラクタ/デストラクタ
     */
    Hoge_Test() {
    }

    ~Hoge_Test() {
    }

    /*
     * 初期化。
     * 各テストコードを実行する前に呼ばれる。
     * 内部変数を初期化したいとか、そういうのを書く。
     */
    virtual void setUp() {
    }

    /*
     * 後始末。
     * 各テストコード終了後に実行される。
     * たぶん、テストに失敗しても呼ばれるんじゃないかな。
     */
    virtual void tearDown() {
    }


    /**
     * 実際のテストコードを書く。
     * 名前はたぶんなんでもいいが、分かりやすさのため
     * test_関数名_番号()
     * としておく。引数・戻り値はvoid。
     */
    void test_foo_001() {
        Hoge instance;
        
        CPPUNIT_ASSERT(true == instance.foo());  // foo()がtrueを返すかどうかをテスト。
        CPPUNIT_ASSERT("foo test", true == instance.foo());  // 上と同じ。エラーのとき「foo test」って出る。
        CPPUNIT_ASSERT_EQUAL(true, instance.foo());  // instance.foo()がtrueと同じかのテスト。これ使わなくても上でいいんじゃね?
        CPPUNIT_ASSERT_DOUBLES_EQUAL(0.1, instance.foo(), 0.001);  // instance.foo()が0.1を返すかどうか。浮動小数点なので。誤差0.001。
        CPPUNIT_FAIL("error");  // 失敗する。エラーパスに仕込むのかな。あるいはまだ中身作ってないテストとか?
    }

};

/*
 * テストスイート(このクラスの冒頭で登録した奴)をテストとして登録。
 * これをしないとこのスイートはテストされない。
 * 逆に言うと、これさえ書いておく、あるいは消しておけばメインを変更しなくてもテストするクラスを指定できる。
 */
CPPUNIT_TEST_SUITE_REGISTRATION(Hoge_Test);

そういうわけで、privateなメソッドをテストするためにHogeクラスは以下のようにしておく。(参考:前方宣言


class HogeTester;   // 前方宣言

class Hoge {
   friend HogeTester;

private:
   bool foo;
   void bar();
};

C言語のソースでも同じように適当にテストクラス作ってテストコード作ってやればいい。ファイルスコープ関数(staticなやつ)をテストするには*.cファイルをインクルードするとかいう荒業もありか。(なしかも)

以上

CppUnitの使い方 2~プロジェクトの構成

そういうわけでCppUnitの使い方、個人的な使い方の続きを。

目次
1.準備
2.プロジェクトの構成
3.ソースの書き方

みたいな感じで今回は「2.プロジェクトの構成」

VCだとソリューションがあって、その下にいくつかプロジェクトをぶら下げられるので、メインプロジェクトとテストプロジェクトを同じソリューション下におく。また、前回で作成したlibやcppunitのヘッダをインクルードするため、それらをテストプロジェクト下におく。
例えばディレクトリ構成は下記のようにする。

プロジェクト名:Hoge(=ソリューション名)
Hoge/
  -source/                # この下はメインプロジェクトのソース
      -Fuga.cpp
  -header/                # この下はメインプロジェクトのヘッダ
      -Fuga.hpp
  -HogeTester/
      -HogeTest.cpp             # テストプロジェクトのmainソース
      -Fuga_Tester.cpp         # Fuga.cppのテスト用ソース
      -cppunit-1.12.0/          # cppunitのいるとこ抜粋してコピーする
          -include/                   # cppunit/ とか msvc6/ とかを丸々置いておく
          -lib/                  # 作成したlibファイル群(cppunit.lib, cppunitd.libなど)

HogeTesterプロジェクトのプロパティでリリースモード、デバッグモードそれぞれでパスを設定する。

・追加のインクルードディレクトリ (C/C++)
  ../header               # メインプロジェクトのヘッダ
  cppunit-1.12.0/include         # cppunitのヘッダ
・追加のライブラリディレクトリ (リンカ)
  cppunit-1.12.0/lib
・追加の依存ファイル (リンカ)
[リリースモードの場合]
cppunit.lib TestRunner.lib
[デバッグモードの場合]
cppunitd.lib TestRunnerd.lib

C/C++のコード生成でランタイムライブラリは前回の通り

[リリースモードの場合]
マルチスレッドDLL
[デバッグモードの場合]
マルチスレッドデバッグDLL

としておく。
あと経験上、プリコンパイル済みヘッダーは使用しないほうがトラブルに巻き込まれずにすむ。プロジェクトが固まってきたら使用してもいいかも知れんけど。でっかいプロジェクトだとコンパイル遅いしな。

次回は実際のコードのテンプレートを示す。

以上

CppUnitの使い方 1~準備編

そういうわけで、CppUnitの私なりの使い方を3回くらいに分けてメモっておく。
VCでコマンドプロンプトでテキストベースで使うという、限定的な使い方だけども!

目次
1.準備
2.プロジェクトの構成
3.ソースの書き方

見たいな感じで今回は「1.準備」

CppUnitは要するにC++のユニットテストをするためのフレームワーク。
いろんな組み込み方はあるだろうけども、私はlibファイルを作って静的リンクにより利用するのが簡単で好きだ。
そういうわけで、まずはここからダウンロード
解凍したらsrcフォルダのVCプロジェクトを開く。
そんでもってビルド!リリースビルドとデバッグビルドをやっておく。
ここでなんかしらんけども、プロジェクトのランタイムライブラリはマルチスレッドDLL(リリースの場合)、マルチスレッドデバッグDLL(デバッグの場合)じゃないとうまくできなかった。そういうわけで、テスト対象もこのどっちかになってしまう。うーん、どうにかなるのかね?
ビルドしたらlibファイルができる。dllとかGUIっぽいexeもできるがそんな軟弱なものはとりあえず使わない。そんなのはすげぇなれてから、ひまでしかたなかったらやってみればいい(と思って3年くらい使ってない)。

というわけで、実際のプロジェクトからlibファイルをリンクして使うわけだが、そのときのディレクトリ構成とかは次回メモる。

以上

Doxygenでヘルプファイルを作りたいんだけど

CPPUNITは最初の導入がめんどいな。今度書く。

で、Doxygenなんですけど1.5.4あたりからヘルプファイル(.chm)を作ったときに日本語が文字化けする。
1.5.5が出てたんですけど、やっぱり文字化けする。
というわけでとりあえずの対処法。

1.普通にヘルプファイルを作ろうとする。文字コードはインプットとアウトプットと両方指定しておいたほうがいいかも。指定できる文字コードはたぶんこれ。アウトプットはUTF-8がいい気がする。
2.できたヘルプファイルを開いて左側のメニューが文字化けしていることを確認する。あと経験ではプロジェクト名に日本語を使っていると本文のプロジェクト名の部分も?????になる。(これはもう英語にするしか?)
3.index.hhcファイルをテキストエディタで開き、SHIFT_JISで保存する。メモ帳ではだめかもわからん。とにかくUTF-8⇒SHIFT_JISへの変換を行う。
4.コマンドプロンプトで”C:\適当なパス\hhc.exe index.hhp”を実行する。

以上

(2010/04/21 追記)
いつの間にか「CHM_INDEX_ENCODING」という設定項目ができていた。
OUTPUTもCHM_INDEX_ENCODINGも文字コードに「CP932」を指定しておけば、めんどくさいことをしなくても文字化けしないchmが作れる。

以上

エクセルの不便

Excel2003の話ですが。
「名前をつけて保存」で保存先のパスに半角の「[」とか「]」を含んでいると保存できなかった。

以上

CakePHPではまったこと20(サニタイズ)

おでこのめがねさえあれば毒餃子問題なんてすぐに解決するだろうに、NH○はいったい何をやっとるのか。

サニタイズっていうとデータをきれいにすることですか。
例えばJavascriptを書き込んだらそれがそのまま動いてしまうような掲示板は恐ろしいです。
そういうわけでタグをはずしたりしないとだめなんです。
でCakePHPにもそういうことをしてくれるSanitizeコンポーネントがあります。
use(‘sanitize’)を呼んどいて、あとはSanitize::clean($data)などのスタティックなメソッドを使えばいいです。
そしたらデータをきれいにしてくれる。
でも、自宅ではうまくいったんですが、サーバにアップロードしてみると文字化けした。ううーむ、よくわからん。

というわけで、WEBぷろぐらま~ システム開発エトセトラさんが紹介されている方法をほぼそのままで利用しています。
これは便利。感謝。

以上

Yahoo!の形態素解析APIをPHPから使う

そんな話はそこら中にあるでしょうが、まあ念のため。
結果をいただくだけなら大したことないけど、結果の使い方を忘れちゃうので。
ひょっとしたら変なこと、やばいことをしている可能性もありますが。

まずは結果をいただくところ。


/**
 * Yahoo!形態素解析を行う。
 * @param $appid string アプリケーションID
 * @param $text string 解析対象テキスト
 * @param $results string 結果の種類 "ma" or "uniq" or "ma,uniq"
 * @return SimpleXML 結果
 */
function yahoo($appid, $text, $results = "uniq") {
	$data = array("appid" => $appid, "sentence" => $text, "results" => $results);
	
	$response = http_post("http://api.jlp.yahoo.co.jp/MAService/V1/parse", $data);
	if ($response === false) {
		return false;
	} else {
		return simplexml_load_string($response);
	}
}

/**
 * http postを行う。
 * @param $url string URL
 * @param $data array postするデータ。連想配列で渡す。?a=1&b=愛 なら array('a'=>1, 'b'=>'愛')
 * @param $optional_header array 追加で渡したいリクエストヘッダ
 * @return string 結果
 */
function http_post($url, $data, $optional_header = null) {
	$data = http_build_query($data, "", "&");
	
	//header
	$header = array(
		"Content-Type: application/x-www-form-urlencoded",
		"Content-Length: ".strlen($data)
	);
	if (null !== $optional_header) {
		array_push($header, $optional_header);
	}
	$context = array(
		"http" => array(
			"method"  => "POST",
			"header"  => implode("\r\n", $header),
			"content" => $data
		)
	);
	
	return file_get_contents($url, false, stream_context_create($context));
}

yahoo()にappid(Yahoo!デベロッパーなんたらでもらえる)=$appidと解析対象のデータ=$textを渡せばSimpleXMLで結果を返します。$resultsは”uniq”か”ma”を渡すことができて、”uniq”だと形態素がディスティンクトされて(同じものはマージされて)帰ってきます。”ma”ならそんなことせずに返します。まあ、実は両方渡せたり(”ma,uniq”)、ほかにもいろいろパラメータあったりしますがそんなのはめんどくさいので無視です。できるだけ簡単に使いたいんです。
http_post()は特にyahooに特化しているわけではないです。単にpostする関数です。どっかから拾ってきてぱくってきた気がします。

で、結果を使うとき。


$xml = yahoo("xxxxxx", "特にyahooに特化しているわけではないです。単にpostする関数です。どっかから拾ってきてぱくってきた気がします。", "ma");
foreach ($xml->ma_result->word_list->word as $word) {  // "uniq"にしたときは$xml->uniq_result
        // ma_resultの下にあるword_listのさらに下にwordがいっぱいぶら下がってます。
	$words[] = (string)$word->surface;  // 形態素はsurfaceです。「特化」とか「いる」とか「関数」とかそんなん。
	if ($word->pos == "名詞") {
                // 品詞はposに入っています。
                // 形容詞,形容動詞,感動詞,副詞,連体詞,接続詞,接頭辞,接尾辞,名詞,動詞,助詞,助動詞,特殊
                // があります。
		$meishi[] = (string)$word->surface;
	}
}

投げっぱなしですが・・・・

以上

Home > Tips Archive

Return to page top