PHPとJavaScriptで『人材獲得作戦・4 試験問題』を解いてみた(つづく。)

今さらですが、試験問題はここにあります。
http://okajima.air-nifty.com/b/2010/01/post-abc6.html

幅優先検索ロジックでPHPで解いてみまして、そして、同じロジックでJavaScriptで探索経過を再現してみましたが。

まだ、解けていない部分があります。
それは、最短経路ではない。orz

まあ、自分なりの不完全な答えを恥ずかしながらお見せします。w

後日に、最短経路込みの回答を追記します。

  • ここに実際の画面を見れます。

http://benny.sakura.ne.jp/tmp/run.php

ソース

<?php
class map {
    public $map;
    public $sx;
    public $sy;
    public $queue = array();
    public $history_map;

    function __construct(){
        $map = file("./map.txt");
        foreach($map as $row => $line) {
            $line =str_split($line);
            foreach($line as $col => $word) {
                $this->map[$row][$col] = $word;
                //Sの原点を設定
                if($word == "S") {
                    #キューを初期化
                    $this->queue[] = array($row,$col);
                }
            }
        }

    }


    function run()
    {
        while ($this->_getNext()){}
    }

    #次へ行けるポイントを見つける
    private function _getNext()
    {
        #キューが空になったら探索を終了させる
        if(!$this->queue) return false;

        #キューの先頭から今回の探索起点ポイントをゲット
        list($sx,$sy) = array_shift($this->queue);

        if(!$this->history_map) {
            $this->history_map[$sx][$sy] = $this->map;
        }
        
        #方向を見る,順番は↓ → ← ↑
        $nextPoint['down']  = array($sx, $sy + 1);
        $nextPoint['right'] = array($sx + 1, $sy);
        $nextPoint['left']  = array($sx - 1, $sy);
        $nextPoint['up']    = array($sx, $sy - 1);

        #四つの方向から次へ行けるポイントをすべて探し出す
        foreach ($nextPoint as $v)
        {
            list($x, $y) = $v;
            switch($this->map[$x][$y])
            {
                #「*」だったら、殺す
                case "*":
                    continue;
                    break;
                #「G」だったら、描画して終了!
                case "G":
                    $this->_print_map($this->history_map[$sx][$sy]);
                    return false;
                    break;
                
                #空白だったら且つ未探索ポイントだったら、キューに入れる
                default:
                    
                    if(!$this->history_map[$x][$y]){
                        array_unshift($this->queue, $v);
                        $this->history_map[$x][$y] = $this->history_map[$sx][$sy];
                        $this->history_map[$x][$y][$x][$y] = "#";
                    }
                    
                    continue;
                    break;
            }
        }

        return true;

    }

    #経路を描画する
    private function _print_map($map)
    {
        echo "<pre>";
        foreach ($map as $row => $line) {
            foreach ($line as $word) {
                echo $word;
            }
        }
        echo "</pre><br />";
    }


}


$map = new map();

$map->run();


?>
<script type="text/javascript" src="jquery.js"></script>
<script>

$(document).ready(function(){

    //マップ制御実行
    $("#map").map_run();

});

(function($){

    $.fn['map_run'] = function(){
        var map = new Array();
        var history_map = new Array();
        var map_dom = $(this);
        var sx;
        var sy;
        var queue = new Array();
        var n = 0;
        var timerID;
        var _fn =
        {
              init : function (){
                          var rows = map_dom.text().split('\n');
                          for (var x = 0; x < rows.length; x++){
                              var line = rows[x].split('');
                              map[x] = [];
                              for (var y = 0; y < line.length; y++){
                                  map[x][y] = line[y];
                                  if(line[y] == "S"){
                                      queue[0] = [x,y];
                                      history_map[x+"_"+y] = this.get_new_map_str(map_dom.text());
                                  }
                              }
                          }
                          
              },
              run : function (){
                timerID = window.setInterval(function (){
                    _fn.get_next();
                }, 100);
              },
              get_next : function (){
                    //キューが空になったら探索を終了させる
                    if(queue.length == 0) return false;

                    //キューの先頭から今回の探索起点ポイントをゲット
                    var sxy = queue.shift();
                    sx = sxy[0];
                    sy = sxy[1];
                    this.print_map(history_map[sx+"_"+sy]);
                    
                    //方向を見る
                    var nextPoint = [];
                    nextPoint['down']   = [sx, sy + 1];
                    nextPoint['right']  = [sx + 1, sy];
                    nextPoint['left']  = [sx - 1, sy];
                    nextPoint['up']    = [sx, sy - 1];

                    //四つの方向から次へ行けるポイントをすべて探し出す
                    for (next in nextPoint) {
                        var x = nextPoint[next][0];
                        var y = nextPoint[next][1];
                        switch (map[x][y]) {
                            //「*」だったら、殺す
                            case '*':
                                continue;
                                break;
                            //「G」だったら、描画して終了!
                            case 'G':
                                this.print_map(history_map[sx+"_"+sy]);
                                clearInterval(timerID);
                                return false;
                                break;
                            default:
                                if(!history_map[x+"_"+y]){
                                    queue.unshift([x,y]);
                                    history_map[x+"_"+y] = this.get_new_map_str(history_map[sx+"_"+sy], x, y);
                                }
                                continue;
                                break;
                        }
                    }
                    return true;
              },
              print_map : function (map){
                    map_dom.text(map);
              },
              get_new_map_str : function (map_str, step_x, step_y) {
                 var rows = map_str.split('\n');
                 var map_array = [];
                  for (var x = 0; x < rows.length; x++){
                      var line = rows[x].split('');
                      map_array[x] = [];
                      for (var y = 0; y < line.length; y++){
                          map_array[x][y] = line[y];
                      }
                  }
                  if(step_x && step_y){
                      map_array[step_x][step_y] = "#";
                  }
                  
                  for (var x = 0; x < map_array.length; x++) {
                        line[x] = map_array[x].join("");
                  }
                  return line.join("\n");
              }
        }

        _fn.init();

        _fn.run();

    };

})(jQuery);
</script>
<pre id ="map" >
**************************
*S* *                    *
* * *  *  *************  *
* *   *    ************  *
*    *                   *
************** ***********
*                        *
** ***********************
*      *              G  *
*  *      *********** *  *
*    *        ******* *  *
*       *                *
**************************
</pre>

第5回 CakePHP勉強会@Tokyoに参加してきましたw

第5回CakePHP勉強会@Tokyoが2010/5/29にトライコーン株式会社 1F セミナールーム開催されました。

福岡、札幌、東海三つのサテライトがust経由で同時開催されまして、さらに、ニューヨークから@yandoさんの発表もSkypeで頂きましたw

すごいです!

勉強会の様子は@ecworks_masapさんがブログにまとめています。ぜひ見てください。


みなさんの発表を聞いて、かわいい「Cakeマシュマロ」をもらって、LTをして、いっぱいご馳走しちゃって、本当に楽しいイベントでした!スタッフのみなさんありがとうございました。また参加したいと思いますw

LTでCakePHP + eZ Publishの連携の話をしましたが、eZは日本ではまだまだ知られていないようですね。
ほんとに素晴らしいCMSなので、ぜひ、興味がある方触ってみてください。

ついでに勉強会から告知をします!

PHPMatsuriは2010年10月2日(土)〜2010年10月3日(日)にかけて
晴海グランドホテルで開催されます!!

詳細は@phpmatsuriをフォローしてください!

最後、@nycomさんのブログ記事をパクって発表者のTwitter IDをここで晒しちゃいます〜

  • イントロ

『特報目玉イベント2010』

(@yando)

  • メインセッション

『CakePHP1.3 stable』

市川さん(@cakephper)

『Ktai Library on CakePHP1.3』

滝下さん(@ecworks_masap)

『コアライブラリのエレガントなハック』

清水さん(@hiromi2424)

『twitterとcloud serverとcakeで新規サービス』

谷井さん(@takamunetanii)

『WordPressとCakePHP連携』

原さん(@kara_d)

『CakePHPでjQueryを使ってみた』

(@nano_eight)

『Cakephp tips for my next projec』

(@evert0n)

『あのCMS eZ publishをCakePHPのModelにしちゃう』(自分w)

(@leebenny)

『実"戦"CakePHP Plugin』

(@k1LoW)

CakePHPでdebug=0の際にset_error_handler

Configure::write('debug', 0);に設定すると、エラーとかが全く出力しなくなるので、ちょっと困った場合があります。
本番にファイルをアップしたら、いざ画面が真っ白になったり、原因不明で設定したシステムエラーページに飛んだりすると、本当にドキドキします。しかも、意図的に投げたエラーだったら、まだカスタマログから調べられますが、普通のwaningとかphpエラーだったら、サーバー上PHPが吐き出したエラーログしか調べられません。
そこで、debug=0の際にカスタマエラーハンドリングを設定してみました。

Step1 /app/custom_error_handler.php (下記のファイル)を作成する

class CustomErrorHandler extends Object
{

    function &getInstance() {
        static $instance;
        $instance =& new CustomErrorHandler();
        return $instance;
    }
    
    function setup(&$debugger) {
        if(Configure::read() == 0) {
            set_error_handler(array(&$debugger, '_error_handler'));
        }
    }

    function _error_handler()
    {
            list($number, $message, $filename, $line, $vars) = func_get_args();

            $errorType = array (
                    E_WARNING      => 'Warning',
                    E_USER_ERROR   => 'User Error',
                    E_USER_WARNING => 'User Warning',
            );
            if ( in_array($number, array(E_NOTICE, E_USER_NOTICE, E_STRICT)) )
            {
                    return true;
            }

            $type = $errorType[$number];
            if ( ! $type )
            {
                    $type = 'Unknown(' . $number . ')';
            }

            $err = $type . ' - ' . $message . ' - ' . $filename . '(' . $line . ')';

            $this->log($err,"php_error");
    }
}

Step2 /app/config/bootstrap.php に下記の記述を追記します

#本番モードの際のカスタマエラーハンドリング設定
require_once APP.'custom_error_handler.php';
CustomErrorHandler::setup(CustomErrorHandler::getInstance());

そうすると、debug=0の本番モードでも、/app/tmp/log/php_error.phpにちゃんと役に立ちそうなログを残してくれます。

あともうひとつの方法があります

/app/config/bootstrap.phpの中にphpエラーログを常に/app/tmp/log/php_error.logに出力するとか
例えば:

if ( Configure::read('debug') == 0 )
{
    error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
    ini_set('display_errors', 0);
    ini_set('log_errors', 1);
    ini_set('error_log', LOGS . DS . 'php_error.log');
}

で、独自のエラーハンドリングを設定したい場合はStep1とStep2の方法を使ってください。

【注意】二つの方法が併用できません!

Twitterの@Anywhereをザックリ見てみる

公式ドキュメントを見ながら、テストページを作ってみました。

ここで登録した情報は後から変更できるので、開発用のテスト環境の情報でとりあえず登録してもかまわない。完了ページでは埋め込み用サンプルコードが自動生成されるので、そのまま使用できるが、中身の実行用のコードは実際に合わせて書いたほうがよいと思います。
ということで、この部分だけに入れる。

<script src="http://platform.twitter.com/anywhere.js?id=あなたのキー&amp;v=1">
  • 特定のユーザーをfollowするボタンを作る
<span id="follow-placeholder"></span>
<script type="text/javascript">
twttr.anywhere(function (T) {
    T("#follow-placeholder").followButton('leebenny');
});
</script>

こんな感じ↓


  • 「@」文字から始まる単語に自動的にtwitterのリンクを付ける
<span id="linkify-this-content">
    @leebenny <=リンク貼られているよ<br />
    @ura_benny <=こちらも<br />
    @ded, @dsa, @todd, @danwrong, @noradio, 
</span>
<script type="text/javascript">
twttr.anywhere(function (T) {
    T("#linkify-this-content").linkifyUsers();
});
</script>

こんな感じ↓

ちなみに↓こうすると、影響範囲はbody全体を対象とする

T.linkifyUsers();
  • 「@」から始まる単語にリンクと吹き出しを付ける
<span id="hovercards-this-content">
    @leebenny <=吹き出しがでるぞ<br />
    @ded, @dsa, @todd, @danwrong, @noradio,
</span>
<script type="text/javascript">
twttr.anywhere(function (T) {
    T("#hovercards-this-content").hovercards();
});
</script>

こんな感じ↓

言うまでもなく↓こうすると、影響範囲はbody全体を対象とする

T.hovercards();
  • 「@」から始まる単語にリンク付けずに、吹き出しを出す(単語にはすでに別のリンクが付けられた場合は便利ですね)
<span id="target-content">
    @leebenny
    <br />
    ↓これ以降の@単語は全部無視するので、要注意
    <br />
    @ded, @dsa, @todd, @danwrong, @noradio,
</span>
<script type="text/javascript">
  twttr.anywhere(function (T) {
    T("#target-content").hovercards({
      infer: true
    });
  });
</script>

ただし、影響範囲内の初めの@単語にしか効かないよ
こんな感じ↓

ほかにはタグの一部の値をusernameとして指定したりすることも可能です。
例:画像のalt属性をusernameに指定してあげるとか

<img alt="leebenny" src="http://a1.twimg.com/profile_images/446785392/myface_normal.gif" id="myImg" />
<script type="text/javascript">
  twttr.anywhere(function (T) {
    T("img#myImg").hovercards({
      username: function(node) {
        return node.alt;
      }
    });
  });
</script>


  • 自分のサイト内にtwitterボックスを入れる
<div id="tbox"></div>
<script type="text/javascript">
  twttr.anywhere(function (T) {
    T("#tbox").tweetBox({
      height: 100,
      width: 400,
      counter: true,
      label: "你在干什&#20040;",
      defaultContent: "<これはボックス内のディフォルトメッセージ>"
    });
  });
  
</script>

こんな感じ↓

で、onTweetというcallback関数設定できるパラメータもありますが、どうもうまくいってない、後でもうちょっと試します。

  • ログイン、ログアウトボタンを付ける
<span id="login"></span>
<script type="text/javascript">
  twttr.anywhere(function (T) {
    T("#login").connectButton();
  });
</script>
<button id="signout" type="button">Sign out of Twitter</button>


  • その他

そして、現在のユーザーのログイン状況を判定するメソッド「T.isConnected()」を用意いて、いろいろ自分のサイトでやれることは増えますね。
さらに、現在のログインしたユーザーのObject「currentUser」も用意してあるので、ユーザーの自己紹介とか名前とかフォロー状況とか簡単に表示できるようになっている。

とにかく、@Anywhereを使えば、すごく簡単にtwitterのUIを会員サイトとかに取り組むことができますね。

CakePHP1.2で気軽に違うデザインの多言語対応(ちょっとbypath)

翻訳ファイルの処理について言っておくこと

  • bakeコマンドは使用しません。
  • moファイル使わない、potファイル生成しない。
  • すべて、poファイルを手動で管理。

正式なやり方でやりたい場合下記をご参照ください。

CakePHP のおいしい食べ方 CakePHP1.2の簡単国際化

想定した要件

  • 英語と日本語をサポート想定する
  • 英語と日本語のデザインが多少違う、よって使用するいくつ特定のテンプレートは違う、でもテンプレートの名前同じにしたい
  • 英語と日本語は同じコントローラ、同じモデルを使用する

[Step1]翻訳ファイルを用意

下記のようなディレクトリとファイルを作成

  • /locale/eng/LC_MESSAGES/default.po の中身
#コメントは「#」を先頭に置いて書く
msgid "Language"
msgstr "English"

  • /locale/jpn/LC_MESSAGES/default.po の中身
#コメントは「#」を先頭に置いて書く
msgid "Language"
msgstr "日本語"

msgid "My name is %s."
msgstr "私の名前は%sです。"

...

[Step2]英語サイトをプラグインとして用意

要件の同じコントローラ、モデルで違うテンプレートに満たすために英語サイトをプラグインにしちゃいました。
このように↓

ちなみにこのプラグインのcontrollersとmodelsを基本的になにも書かなくてよい。

代わりに、親のcontrollersとmodelsを呼び出すためにen_app_controller.phpをこのように必要なcontrollerをimportする必要がある!
en_app_controller.phpの中身

/**
 * 検索画面
 *
 **/
App::import('Controller', 'Search');

/**
 * 詳細画面
 **/
App::import('Controller', 'View');

class EnAppController extends AppController {
}

[Step3]魔法の__()を使って、好きなところに翻訳文を書く

__("Language");  //翻訳文は自動echoされます。テンプレート用
__("Language",true);//翻訳文はreturn値として返します。プログラムの引数として利用

ちなみに、このような使い方もできます。

echo sprintf(__("My name is %s.", "bennylee"));
//日本語: 私の名前はbennyleeです。
//英語:  My name is bennylee.

※注意:翻訳文がない場合そのまま引数が出力されます。

echo __("unknow",true);
//日本語: unknow
//英語:  unknow

これを利用して、英語のpoファイルはほとんどなにも書かなくてよいw

[Step4]翻訳文が更新される場合

翻訳文を更新するには該当言語のpoファイルを弄るだけでOK
でも、poファイルが初めて実行される際、キャッシュファイルが自動生成されます!
poファイルを修正するたびに必ず手動で下記のキャッシュファイルを削除しなければなりません。

/app/tmp/persistent/cake_core_***_eng
/app/tmp/persistent/cake_core_***_jpn

※注意:新しい言語を追加した場合例えば中国語のpoファイルを新しく作りました。
この際に/app/tmp/persistent/cake_core_core_pathsも削除しなければなりません。

最後

あまりにも強引なやり方しか見えませんが、ご参考になれればうれしいです。

もっといい方法があれば、ぜひ教えてくださいm(_ _)m

Google Mapの吹き出し(infowindow)が崩れる?その対応策。

やりたいこと

Google Map上でピンを立て、infowindowをデフォルトで吹き出します。
中身は写真、説明付きのHTMLを表示。

ハマったこと

写真は横幅固定で管理画面から登録して、リサイズしたもの。なので、横幅しか指定しませんでした。
これによって、たまに(ほぼ五分の一の倍率で)デフォルトの吹き出しが崩れます。
ただし、ピンをクリックして、吹き出しを閉じてから、再度ピンをクリックして、吹き出しを表示すると、無事に崩れずに
正しく表示されます。

原因

いろいろ調べましたが、どうやら画像(または画像を囲まれるdivなど)の縦幅を指定してあげないと、Google Map Api吹き出しの中身の縦幅を検知できず、結果的に吹き出しの背景枠が小さめの縦幅になっていて、中身の写真などは外にはみ出してしまいます。

試行錯誤

画像のheightを固定にします(widthは指定しない)。ですが、結局IEには無事対応できたらしい、Firefox3には対応できていません。

実施した対応策

どうやら、こういう対応策があるらしい↓
http://da-studio.blogspot.com/2008/09/googlemap.html
略すると、画像の場所に固定幅の四角形divを用意、その中に画像を背景として表示すること。

そのアイディアをちょっと変えて、下記のようにして実施しました。
1、画像のheightを固定に100pxする。
2、画像の外側にdivを用意、divのheightは同じく100pxにする。

これでIEFireFoxも、問題なさそうです。

IEでcloneしたradioとtextareaを動的にrenameするために(Jqueryベース)

やりたいこと

  • radio群とtextareaが含まれているセクションを丸ごとCloneして、AppendToし、さらにClone元とname値の衝突をしないように、一番上のセクションから数えて、すべてのinputのname値を順番付きで振り直します。

ハマったこと

IE以外はすんなり下記のようにできました。

$.fn.reset_name_num = function(){
        var count = 1;
        //name_1の形のnameを数字の部分だけ現在位置の順番でリネーム
        $('.sys-clone-target',this).each(function(){

            $('input:not(:button)',this).each(function(){

                $(this).attr('name',$(this).attr("name").replace(/_(\d+)/g,'_'+count));
            });

            count++;

        });

でも、IE6だとradioのname値がうまくrenameされません。さらにIE7,6ともにtextareaのrenameはされません。

原因

いろいろ調べて、下記のような理由ではないかと思います。
// Microsoft JScript allows the name to be changed at run time.
// HOWEVER!
// This does not cause the name in the programming model to change
// in the collection of elements, but it does change the name used
// for submitting elements. The NAME attribute cannot be set at run time
// on elements dynamically created with the createElement method.
// To create an element with a name attribute, include the attribute
// and value when using the createElement method.

略して、IEでは特定の部品をcreateElementで生成した後に、動的NAME属性を変えるのは無理、
やりたければ、createElementで生成する際に同時にNAME属性を指定しなければなりません。

対策

  • Textareaには単純にouterHtmlを取得し、nameの部分に対して順番付きに変更したら、outerHtmlソースを動的置換する
if(!+"\v1") {
   // IE
  //Textarea
  var strHTML = $('textarea',this).parent().html();
  if(strHTML){
     strHTML = strHTML.replace(/_(\d+)/g,'_'+count);
     $('textarea',this).parent().html(strHTML);
      }
  }
  • radioに対してはlabelにも対応するようにidとlabelも順番付きで書き換えます。
if(!+"\v1") {
    // IE
    //reset Radio ID and Label
    $(this).attr('id',new_name + $(this).attr("value"));
    $(this).next('label').attr('for',new_name + $(this).attr("value"));
    var strHTML = $(this).parent().html();
    strHTML = strHTML.replace(/_(\d+)/g,'_'+count);
    $(this).parent().html(strHTML);

} else {

    $(this).attr('name',new_name);
    //reset Radio ID and Label
    $(this).attr('id',$(this).attr("name") + $(this).attr("value"));
    $(this).next('label').attr('for',$(this).attr("name") + $(this).attr("value"));
}

おまけ

IEかどうかの判定は(!+"\v1")だけでできる
 http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html