Author Archives: PHP-Archive

[PHP]料金表のようなものをテキストだけで表現する

Facebook にシェア
Delicious にシェア
LINEで送る
Pocket

商品名と価格、氏名と年齢などのように左右に分かれる表を PHP を使って出力する例です。
スペースを使って文字を埋めているので等幅フォントを使えば数値部分だけ右寄せしたように見せることができます。
指定した文字数を超えた場合は自動的に改行されるのでテキストメールのように1行の長さが決まっている場合に便利だと思います。
引数は (文字列1, 文字列2, 左の文字数, 右の文字数, 区切り文字) となっています。

たまねぎ        400円
牛肉          1,200円
魚沼産コシヒ  2,780円
カリ 4kg             
プチトマト      300円
<?php
function textTable($text1, $text2, $length1, $length2, $spacing=' '){
    
    $result = '';
    $text1 = breakString($text1, $length1);
    $text2 = breakString($text2, $length2);
    $lines1 = preg_split('/\R/', $text1);
    $lines2 = preg_split('/\R/', $text2);
    $lineCount1 = count($lines1);
    $lineCount2 = count($lines2);

    $maxLines = max($lineCount1, $lineCount2);
    for($i = 0; $i < $maxLines; $i++){
        $line1 = isset($lines1[$i]) ? $lines1[$i] : '';
        $line2 = isset($lines2[$i]) ? $lines2[$i] : '';
        $pad1 = str_repeat(' ', $length1 - mb_strwidth($line1));
        $pad2 = str_repeat(' ', $length2 - mb_strwidth($line2));

        $result .= $line1 . $pad1 . $spacing;
        $result .= $pad2 . $line2;

        $result .= PHP_EOL;
    }
    
    return $result;
}

function breakString($text, $length, $eol=PHP_EOL, $encoding=null){
    if( ! $encoding) $encoding = mb_internal_encoding();
    $textLen = mb_strlen($text, $encoding);
    $chars = [];
    $width = 0;
    $result = '';
    for($i = 0; $i < $textLen; $i++){
        $char = mb_substr($text, $i, 1, $encoding);
        
        if($width + mb_strwidth($char, $encoding) > $length){
            $result .= $eol . $char; 
            $width = mb_strwidth($char, $encoding);
        } else {
            $result .= $char;
            $width += mb_strwidth($char, $encoding);
        }
    }
    
    return $result;
}

$items = [
    ['たまねぎ', '400円'],
    ['牛肉', '1,200円'],
    ['魚沼産コシヒカリ 4kg', '2,780円'],
    ['プチトマト', '300円']
];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <pre style="font-family: 'Osaka-mono', 'Osaka-等幅', 'MS ゴシック', monospace;"><?php
        foreach($items as $item){
            $result = textTable($item[0], $item[1], 12, 8);
            echo htmlspecialchars($result, ENT_QUOTES, 'UTF-8');
        }
    ?></pre>
</body>
</html>

Posted in PHP | Leave a comment

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

Facebook にシェア
Delicious にシェア
LINEで送る
Pocket

「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

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

Posted in PHP | Tagged | Leave a comment

[JS]画像ファイルアップロード前にプレビューを表示する

Facebook にシェア
Delicious にシェア
LINEで送る
Pocket

HTML 5 で追加された FileReader を利用すると JavaScript からローカルファイルの一部にアクセスすることができるようになります。一般的にはアップロードするファイルを事前にプレビューするなどの用途でよく使われます。
下記サンプルはファイル選択時に画像プレビューを表示するプログラムです。
古いブラウザでは FileReader が利用できないためその場合は何もしない点に注意して下さい。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Preview Demo</title>
    <script>
    document.addEventListener('DOMContentLoaded', function(){
        // アップロードボタン
        var fileSelector = '.image-upload';
        
        // プレビュー画像のクラス名
        var previewSelector = '.preview';

        // プレビューするファイルタイプ
        var fileTypes = [
            'image/jpeg', 'image/jpg', 'image/png',
            'image/gif', 'image/bmp'
        ];

        if( typeof FileReader == 'undefined' ){
            return;
        }

        var reader = new FileReader();

        reader.addEventListener('load', function(event) {
            preview.setAttribute('src', event.target.result);
        });

        var fileInputs = document.querySelectorAll(fileSelector);

        for(var i = 0; i < fileInputs.length; i++){
            var fileInput = fileInputs[i];
            var input = fileInput.querySelector('input');
            var preview = fileInput.querySelector(previewSelector);

            if(!preview) return;
            preview.setAttribute('data-fallback-src', preview.getAttribute('src'));

            input.addEventListener('change', function(){
                if(input.files && input.files[0] && fileTypes.indexOf(input.files[0].type) >= 0) {
                    reader.readAsDataURL(input.files[0]);
                } else {
                    preview.setAttribute('src', preview.getAttribute('data-fallback-src'));
                }
            });
        }
    });
    </script>
    <style>
    .image-upload .preview {
        display: block;
        width: 100%;
        max-width: 100px;
        height: auto;
    }
    </style>
</head>
<body>
    <div class="image-upload">
        <img class="preview" src="noimage.jpg" alt="Preview">
        <input name="image" type="file">
    </div>
</body>
</html>

Posted in JavaScript | Tagged | Leave a comment