[PHP]逆ポーランド記法への変換と計算

「1 * (2 + 3) * 4 – 5」のような数式が書かれた文字列を計算する場合、eval() を使ってしまうのが最も簡単ですが、状況によっては危険な関数を使われてしまうおそれがあるため利用できないことがあります。
自力で計算しなければならないときは一般的に「逆ポーランド記法(後置記法)」に変換してから計算を行うとプログラムが作りやすくなります。

例えば「1 + 2」を逆ポーランド記法で書くと「1 2 +」となります。「1 * (2 + 3) * 4 – 5」の場合は「1 2 3 + * 4 * 5 -」と書きます。
計算の仕方は、先頭から順に読み進め、算術演算子(+-*/)が現れるまで数字を1つずつ配列に格納していきます。
算術演算子が現れたら配列の後ろから2つ取り出し、その演算子で計算して置き換えます。「1 2 3 + *」であれば 1, 2, 3 を配列に格納し、後ろから 3 と 2 を取り出し、最初に現れた「+」で計算した結果で置き換えるとまずは「1 5 *」となります。再度配列の後ろから2つ(5, 1)取り出し、次に現れる演算子「*」で計算すると結果は 5 となります。詳細に関しては末尾に参考リンクを掲載しているのでそちらを読んで下さい。

こちらに変換を行うPHPプログラムを掲載しました。toRpn() で計算式の文字列を逆ポーランド記法の文字列に変換します。
逆ポーランド記法の計算式の計算結果を得るには calcRpn() を使います。

<?php
$expression = '1 * (2 + 3) * 4 - 5';

echo $rpn = toRpn($expression);
echo " = ";
echo calcRpn( $rpn );

function toRpn($expression) {
    $expression = preg_replace('/\s/', '', $expression);
    preg_match_all('/[0-9]+|\+|-|\*|\/|\(|\)/', $expression, $matches);
    $parts = $matches[0];
    
    $stack = [];
    $output = [];
    
    $priorities = [ '/' => 2, '*' => 2, '+' => 1, '-' => 1, '(' => -1, ')' => -1 ];
    
    foreach ($parts as $part) {
        if (is_numeric($part)) {
            $output[] = $part;
        } elseif ($part == '(') {
            $stack[] = $part;
        } elseif ($part == ')') {
            while (count($stack) > 0) {
                $end = end($stack);
                if ($end == '(') {
                    array_pop($stack);
                    break;
                } else {
                    $output[] = array_pop($stack);
                }
            }
        } else {
            if (!empty($stack)) {
                while (true) {
                    $end = end($stack);
                    if ($end && $priorities[$part] <= $priorities[$end]) {
                        $output []= array_pop($stack);
                    } else {
                        break;
                    }
                }
            }
            $stack[] = $part;
        }
    }
    
    while (count($stack) > 0) {
        $output[] = array_pop($stack);
    }
    
    return implode(' ', $output);
}

function calcRpn($expression) {
    $parts = preg_split('/\s/', $expression, -1, PREG_SPLIT_NO_EMPTY);
    $stack = [];

    foreach ($parts as $part) {
        if (is_numeric($part)) {
            $stack[] = $part;
        } else {
            $b = (float)array_pop($stack);
            $a = (float)array_pop($stack);
            
            switch ($part) {
                case "+":
                    $x = $a + $b;
                    break;
                case "-":
                    $x = $a - $b;
                    break;
                case "*":
                    $x = $a * $b;
                    break;
                case "/":
                    $x = $a / $b;
            }
			array_push($stack, $x);
        }
    }

    return $stack[0];
}
出力結果:
1 2 3 + * 4 * 5 - = 15

参考: さくらのナレッジ「日曜プログラミングで電卓を作ってみる

Symfony 3 のインストールと基本構成

この記事では Symfony 3.2 のインストールと重要なフォルダの階層、ルーティングの基本などについて説明します。

インストーラーの準備

Mac, Linux

下記のようにして Symfony のインストーラーをダウンロード、インストールします。

 $ sudo mkdir -p /usr/local/bin
 $ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
 $ sudo chmod a+x /usr/local/bin/symfony
 

プロジェクトの作成は次のようにします。my_project_name の部分はプロジェクトのフォルダ名になりますので好きな名前をつけて下さい。

$ symfony new my_project_name

Windows

Windows の場合次のようにして実行ファイルをダウンロードします。

php -r "readfile('https://symfony.com/installer');" > symfony

ダウンロードした symfony というファイルをプロジェクトのフォルダに移動し「php symfony」のようにして使います。

プロジェクトのルートフォルダに移動しプロジェクトを作成します。
my_project_name の部分はプロジェクトのフォルダ名になりますので好きな名前をつけて下さい。

C:¥> cd projects
C:¥projects¥> php symfony new my_project_name

Composer を使う

PHP のバージョンが古かったり、Symfony のインストーラーが使えない場合は Composer を使ってインストールします。

composer create-project symfony/framework-standard-edition my_project_name

インストールされる主要コンポーネント

Symfony 3 にはデータベースまわりを扱う Doctrine ORM、メール送信のための Swift Mailer、テンプレートエンジンの Twig などが予めインストールされています。

主要ファイル構成

各種設定ファイルは app/config にまとめられています。
設定は YAML 形式のファイルで保存されており、ロケールやメールサーバー情報、データベースの接続情報などはここで編集します。

コントローラーは src/AppBundle/Controller にあります。
ホームページを表示するための DefaultController.php が見本として用意されています。

テンプレートファイルは app/Resources/views にあります。
テンプレートエンジンには Twig が使われています。

パブリックフォルダの名前は web です。独自ドメインもこのフォルダに割当てます。
画像や CSS、JS などのアセットはここに配置します。

ルーティング

ルーティングの指定方法は app/config/routing.yml で変更できますが、デフォルトではコントローラーでのアノテーション(コメント)が利用されます。

	/**
     * @Route("/api/posts/{id}")
     * @Method({"GET","HEAD"})
     */
    public function showAction($id)
    {
        //
    }

それぞれのビューコントローラーのメソッドの上にアノテーションとしてルーティングを記述します。
@Route にはパスを定義します {id} のように括弧で囲むと引数として受け取れるようになります。
@Method は HTTP メソッドを指定するために使います。デフォルトでは全てのメソッドが受け入れられる状態です。
@Method(“PUT”) のようにすると PUT メソッドのみに反応するようにできます。複数のメソッドに対応する場合 {“GET”,”HEAD”} のように波括弧を用いて GET か HEAD のときにみに限定できます。
コントローラーメソッドの命名は indexAction(), showAction(), editAction() のように 役割 + Action() の形にします。

パスの書式を指定したり、ルートに名前を付ける場合は次のようにします。

/**
 * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"})
 */

name はルートの名前です。アプリケーション全体でユニークな名前にして下さい。
名前をつけておくと URL の出力が簡単になります。
requirements は正規表現を使ったルールです。
上記の例では {page} が数字であることが条件となっています。

デバッグ

web/app_dev.php にアクセスするとデバッグモードになります。
IPアドレスによる制限がかかっているため、Vagrant を使用している場合などでアクセス元が 127.0.0.1 でない場合 app_dev.php 内の in_array() に許可する IP を追加します。

!(in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])

ビューのレンダリング

コントローラーで直接レスポンスを出力するには次のようにします。

return new Response( "Hello, World!" );

Twig テンプレートを使ってレンダリングする場合はこのようになります。

return $this->render('default/index.html.twig', ['key' => 'value']);

Twig の詳細に関しては割愛しますが render() の第1引数にテンプレートファイル名を指定し、第2引数にテンプレートに渡す変数とその内容を連想配列の形で記述します。配列のキー名がそのまま変数名として渡されます。

[PHP]クラスメソッドをキャメルケース、スネークケースのどちらでも呼び出せるようにする

PHP のクラスメソッドは基本的にキャメルケース(camelCase)で命名されることが多いのですが、プロジェクトによっては単語をアンダーバーで区切るスネークケース(snake_case)が使われることもあります。
どちらにするかは自由ですが、プロジェクト全体で統一するのが望ましいとされています。

自分で作ったクラスをほかのプロジェクトでも再利用する場合、必要に応じて命名記法を書き換える事がありますが、マジックメソッドの __call(), __callStatic() を利用すると存在しない存在しない名前のメソッドが呼び出されようとしたときの処理を定義できるため、キャメルケース、スネークケースのどちらで呼び出されても同じメソッドが実行される仕組みを作ることができます。

<?php
class Example
{
    public function __call($name, $args) {
        $name = str_replace('_', '', ucwords($name, '_'));
	    $name[0] = strtolower($name[0]);
        return call_user_func_array([$this, $name], $args);
    }

    public static function __callStatic($name, $args) {
        $name = str_replace('_', '', ucwords($name, '_'));
	    $name[0] = strtolower($name[0]);
        return call_user_func_array([self::class, $name], $args);
    }

    public function exampleMethod($str) {
        return "hello, " . $str;
    }

    static public function staticMethod($str){
        return "hello, " . $str;
    }
}

メソッドの呼び出し

$example = new Example();

echo $example->exampleMethod("World");
echo $example->example_method("World");

echo Example::staticMethod("World");
echo Example::static_method("World");
実行結果: Hello, World