array_splice
マニュアル
http://www.php.net/manual/ja/function.array-splice.php
Perlのspliceと同じように使ったら間違っていたのでメモ。
array_spliceは配列の値を削除したり挿入したり置き換えたり
いろんなことができる関数です。
<?php $array1 = range(0, 9); array_splice($array1, 5); // 添字5から先を削除 print_r($array1); /* Array ( [0] => 0 [1] => 1 [2] => 2 [3] => 3 [4] => 4 ) */ $array2 = range(0, 9); array_splice($array2, 4, 2); // 添字4から2個削除 print_r($array2); /* Array ( [0] => 0 [1] => 1 [2] => 2 [3] => 3 [4] => 6 [5] => 7 [6] => 8 [7] => 9 ) */ $array3 = range(0, 9); array_splice($array3, 2, 0, 100); // 添字2の位置に100を挿入 print_r($array3); /* Array ( [0] => 0 [1] => 1 [2] => 100 [3] => 2 [4] => 3 [5] => 4 [6] => 5 [7] => 6 [8] => 7 [9] => 8 [10] => 9 ) */ $array4 = range(0, 9); array_splice($array4, 2, 1, 100); // 添字2の値を100に置き換える print_r($array4); /* Array ( [0] => 0 [1] => 1 [2] => 100 [3] => 3 [4] => 4 [5] => 5 [6] => 6 [7] => 7 [8] => 8 [9] => 9 ) */ $array5 = range(0, 9); array_splice($array5, 1, 3, 100); // 添字1から3個削除し、100を挿入する print_r($array5); /* Array ( [0] => 0 [1] => 100 [2] => 4 [3] => 5 [4] => 6 [5] => 7 [6] => 8 [7] => 9 ) */
多次元の配列を扱うときは少し注意が必要で、、、
<?php $array = array( array(1, 2, 3), array(4, 5, 6), array(7, 8, 9), ); array_splice($array, 1, 0, array(101, 102, 103)); print_r($array);
こんな感じに書くと出力は
Array ( [0] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [1] => 101 [2] => 102 [3] => 103 [4] => Array ( [0] => 4 [1] => 5 [2] => 6 ) [5] => Array ( [0] => 7 [1] => 8 [2] => 9 ) )
こうなってしまいます。
array_spliceは複数の値を挿入するとき
引数を増やすのではなくて第4引数に挿入したい値の配列を渡してやります。
なのでこの場合は
<?php $array = array( array(1, 2, 3), array(4, 5, 6), array(7, 8, 9), ); array_splice($array, 1, 0, array(array(101, 102, 103))); print_r($array);
とするとうまくいくわけです。
array_pushとかarray_unshiftは引数を増やすタイプなのに
なんでarray_spliceだけは違うのだろうか、、、
mb_encode_numericentityとは
マニュアル
http://www.php.net/manual/ja/function.mb-encode-numericentity.php
http://www.php.net/manual/ja/function.mb-decode-numericentity.php
文字を数値参照に変換してくれる関数なのだが
変換したい領域の指定方法が複雑だったので書いておく
領域は第二引数に配列で指定する
添字0がUnicode値の始まり、1が終わり、2が変換後の位置、3がマスク値である
領域を複数指定するには、同じ順序で値を追加してやる
つまり2つ目の領域を指定するには添字の4から7を使う、3つ目は8から11を使う
<?php $string = "<>"; $convmap = array( 0x3C, 0x3C, 0, 0xFFFF, // < 0x3E, 0x3E, 0, 0xFFFF // > ); $result = mb_encode_numericentity($string, $convmap, "UTF-8"); var_dump($result); // string(10) "<>"
添字2の値は変換後の数値に加算され、そのあとで添字3の値でマスク(ビット演算の&)される
<?php $string = "<>"; $convmap = array( 0x3C, 0x3C, 2, 0xFFFF, // < 0x3E, 0x3E, 2, 0xFFFF // > ); $result = mb_encode_numericentity($string, $convmap, "UTF-8"); var_dump($result); // string(10) ">@"
mb_decode_numericentityはその逆を行う
数値参照から添字2の値を引いた値が領域に含まれる場合はそれを文字に戻す
添字3の値は無視される
<?php $string = "<>"; $convmap = array( 0x3C, 0x3C, 0, 0xFFFF, // < 0x3E, 0x3E, 0, 0xFFFF // > ); $result = mb_encode_numericentity($string, $convmap, "UTF-8"); var_dump($result); // string(10) "<>" $result = mb_decode_numericentity($result, $convmap, "UTF-8"); var_dump($result); // string(2) "<>"
配列の各要素を正規表現で置換する
perl map 置換とかで検索するとこんな感じのコードが出てくる
my @before = ('foo', 'bar', 'baz'); my @after = map {$_ =~ s/b/B/; $_} @before;
これでもうまくいくんだけど、少々つっこみを
見た目の問題なんだけど、パターンマッチ演算子(=~)は省略すると $_ が使われるのでこの場合は省略できる
$_ は各要素のエイリアスなので @before の値も変更されてしまう
元の値がいらなければ
my @before = ('foo', 'bar', 'baz'); s/b/B/ for @before;
これで良いし
必要なら
my @before = ('foo', 'bar', 'baz'); s/b/B/ for my @after = @before;
で良い
PHPでPerl相互の正規表現を使ってみる 1日目
参考: http://www.php.net/manual/ja/book.pcre.php
初めての正規表現
文字列中に特定のパターンが含まれるか確認するにはpreg_match関数を使用する
この関数は第一引数に正規表現を取り、第二引数に対象の文字列を取る
そして、対象の文字列にパターンが含まれる場合は1を返し、含まれない場合は0を返す
PHPの正規表現はデリミタで囲われたパターンとその後の修飾子で構成される
例えば /fo+/im という文字列の場合 / がデリミタ、 fo+ がパターン、 im が修飾子である
デリミタには英数字、バックスラッシュ、空白文字以外の任意の文字を使うことができる
開始デリミタが開きかっこの場合は対になる閉じかっこが終了デリミタになる
単純に文字列を検索するには検索対象をデリミタで囲うだけで良い
サンプル: 文字列fooの検索
<?php if ( preg_match('/foo/', "foobarbaz") ) { echo "fooが含まれる\n"; } else { echo "fooが含まれない\n"; }
正規表現には特別な意味のあるメタ文字があり
これらを単純な文字として扱うにはバックスラッシュでエスケープする必要がある
\ ^ $ . [ ] | ( ) ? * + { }
サンプル: 文字列C++の検索
<?php if ( preg_match('/C\+\+/', "Perl PHP C++ Java") ) { echo "C++が含まれる\n"; } else { echo "C++が含まれない\n"; }
文字を表すメタ文字列
1種類以上の1文字を表すメタ文字列を以下に示す
. LF(0x0A)以外の1文字にマッチ \0 ヌル文字にマッチ \a アラート(0x07)にマッチ \C 1バイトにマッチ \d 数字にマッチ \D 数字以外にマッチ \e エスケープ文字(0x1B)にマッチ \f 改ページ(0x0C)にマッチ \h 水平方向の空白(0x09, 0x20)にマッチ \H 水平方向の空白文字(0x09, 0x20)以外にマッチ \n LF(0x0A)にマッチ、普通改行を示すにはこれを使う \N LF(0x0A)以外にマッチ、{}が続くとエラーになる \r CR(0x0D)にマッチ \R 改行(0x0A-0x0D)にマッチ \s 空白(0x09, 0x0A, 0x0C, 0x0D, 0x20)にマッチ \S 空白(0x09, 0x0A, 0x0C, 0x0D, 0x20)以外にマッチ \t タブ文字(0x09)にマッチ \v 垂直方向の空白(0x0A-0x0D)にマッチ \V 垂直方向の空白(0x0A-0x0D)以外にマッチ \w 単語構成文字(0-9, A-Z, a-z, _)にマッチ \W 単語構成文字(0-9, A-Z, a-z, _)以外にマッチ
サンプル: 数字の検索
<?php if ( preg_match('/\d/', "foo012bar") ) { echo "数字が含まれる\n"; } else { echo "数字が含まれない\n"; }
正規表現を使う際に注意することその1
正規表現として評価される前に文字列として評価されることを忘れてはならない
"/\n/" という文字列は \n がダブルクォート中でエスケープシーケンスなので
/LF/ となり LF 1文字にマッチする
"/\\n/" という文字列は \\ がダブルクォート中でエスケープシーケンスなので
/\n/ となり \n は正規表現のメタ文字列なので LF という意味を持ち
結果的に前者と同じく LF 1文字にマッチする
"/\v/" という文字列は \v がダブルクォート中でエスケープシーケンスなので
/VT/ となり VT 1文字にマッチする
"/\\v/" という文字列は \\ がダブルクォート中でエスケープシーケンスなので
/\v/ となり \v は正規表現のメタ文字列なのでアスキーコードの 0x0A-0x0D のいずれかという意味を持ち
前者と異なる結果になる
"/\w/" などのエスケープシーケンスでないものはそのまま /\w/ となるが、将来もそうであるとは限らない
これらを意識しなくて済むように正規表現を書くときにはシングルクォートを使った方が良い
量指定子
量を指定するメタ文字列を以下に示す
* 0個以上の連続にマッチ + 1個以上の連続にマッチ ? 0個または1個の連続にマッチ {n} n個の連続にマッチ {n,} n個以上の連続にマッチ {n,m} n個以上m個以下の連続にマッチ
サンプル: 2個以上連続した o の検索
<?php if ( preg_match('/o{2,}/', "foobarbaz") ) { echo "連続したoが含まれる\n"; } else { echo "連続したoが含まれない\n"; }
位置を表すメタ文字列
特定の位置を表すメタ文字を以下に示す
^ 文字列の先頭にマッチ $ 文字列の最後にマッチ \A 文字列の先頭にマッチ \b 単語境界にマッチ \B 単語境界以外にマッチ \G 開始位置にマッチ \z 行の最後にマッチ \Z 文字列の最後にマッチ
\B は文字にマッチするメタ文字列だが \b と対比するためここに含めた
サンプル: 末尾にある数字を検索する
<?php if ( preg_match('/\d$/', "foobarbaz0123") ) { echo "最後の文字は数字である\n"; } else { echo "最後の文字は数字ではない\n"; }
文字クラス
文字クラスとは文字の集合で1文字を表すものである
任意の文字群の集合は [ ] で囲って表す
[abc] a, b, c のいずれか1文字にマッチ
任意の文字群以外の集合は [^ ] で囲って表す
[^abc] a, b, c 以外の1文字にマッチ
任意の文字群を範囲で指定するには - を使う
[a-z] a から z のいずれか1文字にマッチ
文字クラスには文字を表すメタ文字列も使用できる
\b は文字クラス中に限りバックスペース(0x08)を表す
特別な意味がある \ ^ - ] を集合に含めるにはバックスラッシュでエスケープする必要がある
ただし、先頭以外の ^ や先頭または最後の - はその必要がない
その他のメタ文字は文字クラス中で特別な意味を持たない
サンプル: 0xから始まる2桁の16進数を検索する
<?php if ( preg_match('/0x[0-9A-Fa-f]{2}/', "<0x5F>") ) { echo "16進数が含まれる\n"; } else { echo "16進数が含まれない\n"; }
分岐
複数のパターンで検索するには | による分岐を使用する
サンプル: .php か .pl を検索する
<?php if ( preg_match('/\.php|\.pl/', "public_html/app/index.php") ) { echo ".phpか.plが含まれる\n"; } else { echo ".phpも.plも含まれない\n"; }
グループ化
パターンをグループとして表すには () で囲う
foo(bar|baz) foobar か foobaz にマッチ
グループに対して量指定子を使用することができる
(foo){3} foofoofoo にマッチ
サンプル: 8桁の2進数の連続か確認する
<?php if ( preg_match('/\A([01]{8})+\Z/', "001111110001110100110011") ) { echo "8桁の2進数の連続である\n"; } else { echo "8桁の2進数の連続ではない\n"; }
修飾子
正規表現の振る舞いを変更する修飾子を以下に示す
i 大文字と小文字を区別しない m 複数行として扱う s 単一行として扱う x 正規表現のフリーフォーマットを許す e preg_replaceの第二引数がPHPのコードとして実行される A マッチを開始位置に限定する D $が文字列の最後のみにマッチするようになる、m修飾子がある場合この修飾子は無視される S 正規表現の最適化を行う U 最大量指定子と最小量指定子を入れ替える X Perl非互換なPCREの機能を有効にする、特別な意味が与えられていないバックスラッシュと任意の文字の組み合わせがエラーになる J 同じ名前の名前付きキャプチャを許す u 文字列をUTF-8として扱う
i修飾子に関しては説明の通りである
/foo/i foo, Foo, fOo, foO, FOo, fOO, FoO, FOO にマッチ
m修飾子は ^ が文字列の先頭に加えてLF(0x0A)の直後にもマッチするようになり
$ が文字列の最後に加えてLFの直前にもマッチするようになる
s修飾子は . がLFにもマッチするようになる
x修飾子はパターン中の空白や改行が無視され、#から改行までがコメントになる
ただしコメントにデリミタを含めることはできない
正規表現を使う際に注意することその2
D修飾子が出てきたのでついでに触れておくと
デフォルトで $ は文字列の最後、または最後の改行の直前にマッチする
なので /^[0-9]+$/ は 数字だけの文字列にマッチするが "0123\n" のように改行が含まれていてもマッチする
正規表現によるキャプチャ
グループ化したパターンはマッチに成功したあとで利用することができる(キャプチャまたは捕捉などと言う)
preg_matchの第三引数にはキャプチャを代入する変数を指定することができる
<?php if ( preg_match('/(\d+)/', "foo 123 bar", $matches) ) { echo $matches[1], PHP_EOL; // 123 }
キャプチャするグループは複数あっても構わない
左から順に1から始まる番号がふられ、ネストされた場合は外側が先にくる
また、キャプチャの有無に関係なく添字0にはマッチ全体が代入される
<?php if ( preg_match('/ ([^:\s]+) \s*? : \s*? ([^\s].*) /x', "title: hello regex", $matches) ) { echo $matches[0], PHP_EOL; // title: hello regex echo $matches[1], PHP_EOL; // title echo $matches[2], PHP_EOL; // hello regex }
最小量指定子
前述した量指定子は、いわゆる最大量指定子で、なるべく長くマッチしようとする
それに対し最小量指定子はなるべく短くマッチしようとする
例えば次のような文字列に
最大量指定子を使った次のパターンでは
/ < (.*) > /x
次のようなキャプチャになるが
foo>
最小量指定子を使った次のパターンでは
/ < (.*?) > /x
次のようなキャプチャになる
foo
最小量指定子を以下に示す
*? 0個以上の連続にマッチ +? 1個以上の連続にマッチ ?? 0個または1個の連続にマッチ {n,}? n個以上の連続にマッチ {n,m}? n個以上m個以下の連続にマッチ
後方参照
キャプチャを正規表現のなかで利用するには後方参照を使う
後方参照は \ とその後に対応するキャプチャの番号を並べて表す
/(foo)\1/ foofooにマッチ /(.)(.)(.)\3\2\1/ 左右対称の6文字(abccbaなど)にマッチ
PHPでは99個までキャプチャと後方参照が可能である
正規表現でマルチバイト文字を扱う
正規表現はマルチバイト文字だろうとなんだろうと1バイトを1文字として扱う
シングルバイト文字もマルチバイト文字も同じ1文字として扱うために u修飾子 が使用できる
もし正規表現か対象の文字列にマルチバイト文字が含まれる可能性がある場合は迷わずこの修飾子を使用するべきである
ただしエンコーディングはUTF-8でなくてはならない
u修飾子の効果は次の通り
. 1バイトではなく文字単位でマッチするようになる \w マルチバイト文字の一部がマッチするようになる \W マルチバイト文字の一部がにマッチしないようになる Unicode文字プロパティが使えるようになる(\p \P \X)詳しくはhttp://www.php.net/manual/ja/regexp.reference.unicode.php 量指定子でマルチバイト文字の連続を表せるようになる
文字コードの指定
16進数を使った書式は次の通り(Fは任意の16進数)
\xF \xFF \x{F*}
2桁までは {} で囲っても囲わなくても良い、3桁以上は囲わなければならない
8進数を使った書式は次の通り(7は任意の8進数)
\07 \77 \077 \777
ただし \10 から \99 は対応するキャプチャがないときに限る
ある場合は前述した後方参照になる
ここで指定する数値は u修飾子 の有無で意味が変わることに注意したい
u修飾子がないときは単にバイト列である
u修飾子があるときはUnicodeのコードポイントである
つまりUTF-8の "あ" を表すには
u修飾子がない場合
\x{E3}\x{81}\x{82}
u修飾子がある場合
\x{3042}
となる
ソートの比較
PHPでソート関数を試し書きしてるときに、ふと気になったのでPerlと速度を比べてみた
コードは以下
ActivePerl 5.12.2
use strict; use warnings; use Time::HiRes; sub str { my $str = ""; for (0..int(rand(10)+1)) { $str .= chr(65 + rand(25)%26); } return $str; } my @data = map{&str()} 1..3000; my @temp; my $t = Time::HiRes::time(); for (my $i = 0; $i < 10000; $i++) { @temp = sort {$a cmp $b} @data; } printf "%.2f\n", Time::HiRes::time() - $t;
PHP 5.3.10
<?php function str() { $str = ""; mt_srand(); $len = mt_rand(0, 10) + 1; for ($i = 0; $i < $len; $i++) { $str .= chr(65 + mt_rand(0, 25)%26); } return $str; } $data = array(); for ($i = 0; $i < 3000; $i++) $data[] = str(); $t = microtime(true); for ($i = 0; $i < 10000; $i++) { $temp = $data; usort($temp, function($a, $b) { return strcmp($a, $b); }); } printf("%.2f\n", microtime(true) - $t);
文字列3000個をユーザー定義で10000回ソートするのにかかる時間を測定した
なるべく似せて書いたつもりだけど、
配列の値が毎回ランダムなんで言語間の比較としては微妙かもしれない
何度か実行してみるとPerlは大体32秒前後であるのに対しPHPは260秒前後だった
JITとインタプリタだとやはり差がでるな、という感想
PHPの変数スコープ
forとかにつかった変数がそのあとも残るのがなんだか慣れない
<?php for ($i = 0; $i < 10; $i++) { echo $i, PHP_EOL; } unset($i);
こんな感じに書くのが主流なのかな、どうなのかな
PHPUnitをインストールしてみる
PHPで自動テストを行うにはPHPUnitというPEARモジュールを使うらしいので
インストールしてみることにした
私の場合はPEARモジュールをインストールすること自体初めてだったりする
PEARのインストール
まず以下のページからgo-pearをダウンロードしてくる
http://pear.php.net/manual/ja/installation.getting.php
ブラウザで表示してコピー、と書いてあるが私の場合は勝手にダウンロードされたのでそれを使った
必要なかったかもしれないがPHPのインストールディレクトリに保存してからコマンドプロンプトに
以下のコマンドをタイプする
php go-pear
終わってからpear versionとタイプして反応があったのでたぶん成功
PHPUnitのインストール
以下のページの通り
http://www.phpunit.de/manual/3.6/ja/installation.html
pear config-set auto_discover 1 pear install pear.phpunit.de/PHPUnit
終わってからphpunit --versionとタイプしてみるが無反応だったので調べてみたらphpunit.batが足りない様子
ググって最初にあったページをそのままに、以下のコマンドをタイプしたら解決した
pear install --alldeps --force phpunit/phpunit
今度はちゃんとphpunit --versionで反応がある
PHPUnitの書き方
参考にしたサイトはこちら
http://www.phpunit.de/manual/3.6/ja/index.html
<?php class Foo { protected $val; public function __construct($val = 0) { $this->val = $val; } public function add($num) { if(! is_int($num)) { throw new InvalidArgumentException("'add' only accepts integers."); } $this->val += $num; } public function get() { return $this->val; } public function puts() { echo $this->val, PHP_EOL; } }
<?php set_include_path(__DIR__ . './lib' . PATH_SEPARATOR . get_include_path()); require_once("Foo.php"); class FooTest extends PHPUnit_Framework_TestCase { protected $foo; public function setUp() { $this->foo = new Foo(); } /** * @test */ public function test1() { $this->foo->add(1); $this->assertTrue(1 === $this->foo->get()); } public function test2() { $this->foo->add(1); $this->foo->add(2); $this->assertSame(3, $this->foo->get()); } /** * @expectedException InvalidArgumentException * @expectedExceptionMessage 'add' only accepts integers. */ public function test3() { $this->foo->add("x"); } public function test4() { $this->foo->add(1); $this->expectOutputString("1" . PHP_EOL); $this->foo->puts(); } }
コマンド
phpunit FooTest
出力
PHPUnit 3.6.10 by Sebastian Bergmann. .... Time: 0 seconds, Memory: 2.75Mb OK (4 tests, 5 assertions)
2行目のset_include_pathはライブラリ検索用のパスを追加している
Perlでいうuse libみたいな感じ
テスト用のクラスはPHPUnit_Framework_TestCaseを継承して作る
setUpというメソッドは各テストの前にコールされる
他にも以下のようなものがある
tearDown // 各テストの後にコールされる setUpBeforeClass // テスト開始時にコールされる tearDownAfterClass // テスト終了時にコールされる
スクリプトの中にある@から始まるコメントは単なるコメントではなく
アノテーションというやつで、その直後に書いたメソッドの振る舞いを決めるもの
@testはテストメソッドを指定するものだけど、メソッド名がtestから始まる場合は
この指定なしにテストメソッドになる
@expectedExceptionは投げられるはずの例外のクラスを指定して
例外のテストを行うことができる
@expectedExceptionMessageは例外のメッセージに含まれる文字列を指定する
assertから始まるメソッドはアサーションというやつで
こいつらで値をチェックしていく
assertTrueメソッドは引数がtrueであれば成功する
PerlでいうTest::SimpleとかTest::Moreのok関数みたいな感じ
ただし勝手にキャストされることはないのでassertTrue(1)などは失敗する
本当にtrueを期待する場面に使ったほうがよさそう
逆にfalseを期待するassertFalseもある
assertSameは2つの引数を === で比較し真ならば成功する
<?php $this->assertSame(1, 1); // Successful $this->assertSame(1, "1"); // Failures $foo = new Foo; $alias = &$foo; $this->assertSame($foo, $alias); // Successful $this->assertSame($foo, new Foo); // Failures
逆に !== で比較するassertNotSameもある
expectOutputStringは出力のテストを行う
正規表現を使うexpectOutputRegexもある
次回の課題
とりあえずきちっとテストにこけるか試せばよかったな、と
もうすこし複雑な使い方についても試してみようか
編集中のファイルをHTTP経由で開く
編集中のWebページをブラウザでテストしたくなったとき、HTMLとかならそのままファイルを開けば大体OKなんだけど
PerlやPHPのスクリプトの場合http://localhost/foo/bar.cgiなどとブラウザに打ち込まなければならない
ちょろっと簡単なテストのたびにこれをやるのは結構めんどうだったりする
そこで、少しでも手間が省けるようにバッチファイルを作ってみた
@echo off set host=http:\\localhost\ set docloot=C:\Program Files (x86)\Apache Software Foundation\Apache2.2\htdocs set fpath=%~f1 set temp= set sep= set a= set b= if "%fpath%" == "" goto end :loop for /f "tokens=1* delims=\" %%A in ("%fpath%") do ( set a=%%A set b=%%B ) set fpath=%b% set temp=%temp%%sep%%a% set sep=\ if "%temp%" == "%docloot%" goto browse if "%fpath%" == "" goto end goto loop :browse start %host%%fpath% :end
hostとdoclootは環境にあわせて書きかえること
hostの最後には\が付くことと、doclootの最後には付かないことに注意
特定のブラウザで開きたい場合はstartのところをブラウザのパスに書きかえるといいと思う
あとはbrowse.batなど好きな名前でこいつを保存して、
テストしたいファイルをドラッグするだけでOK
browse.bat foo.cgi
とかでもいい
私の場合は最近Notepad++を使い始めたので
実行 > ファイル名を指定して実行
のところでこのファイルを選び、後ろに"$(FULL_CURRENT_PATH)"を追加して登録してやれば
編集中にAlt+Bとかで開けるようになる
C:\script\browse.bat "$(FULL_CURRENT_PATH)"
こんな感じ