PHPで$foo = ” || ‘foo’;とやると

$foo は 1 になる。
1 になるというか、真になってそれを出力すると 1 になる、といったほうがいいか。論理演算 || の結果は真偽値(この場合は真)で、echo で出力すると 1 になるということらしい。

もともとの話をすると、フォームからポストされたデータが空文字列だった場合にデフォルト値を設定しようとしたこと。こんな感じ:

$name = $_POST['name'] || 'no name';

上に書いたとおり、これだと $name を出力すると 1 になってしまう。

で、こう書けばいいようだ。

$name = $_POST['name'];
$name |= 'no name';

1行増えるのがめんどくさいな。

PHPで掲示板作った

つい先日 C++ の本を読み始めたばかりだけど、この3連休はにわかPHPerになっていた。
コトのはじめは、自宅サーバに掲示板を設置しようとしたこと。ググってみると無料で配布されている掲示板アプリはいろいろとあるんだけど、どうにも気に入ったものがない。メールやホームページのアドレスなんかいらないんだよ。文字の色を変える機能なんかもいらない。一方でメッセージにコメント(reply)を付けたい……といった希望に沿うものがなかなかなくて、おまけに 2つほど試したみたらきちんと動かない(設定のせいかもしれいけど)。
結局、それじゃちょっと自分で作ってみようか、ということになった。配布されている掲示板アプリは大抵が PHP だった(あとちょっとだけ Perl)ので、見様見真似で PHP で書いてみることにしたわけだ。
で、作ってみたのがこれ:

 cf. miniBBS – GitHub/takatoh

PHP についてほとんど何も知らないところから3日で作ったので、ホント基本的な機能しかない。メッセージがポストできて、そのメッセージにコメントが付けられるだけ。削除機能はなし。管理者用ログイン機能もなし。まあ、このへんは気が向いたらあとから実装するかも。

PHP についての印象だけど、良くも悪くも Web 用の言語だなあといったところ。もうちょっと具体的には:

  • 言語自体がテンプレートエンジンになってる感じ
  • 変数名にいちいち $ マークめんどくさい
  • 文末のセミコロンもめんどくさい
  • クラスのメンバ変数にアクセスするのにいちいち $this-> 付けなきゃいけないのめんどくさい
  • 文字列分割する関数の名前が explode ってなんだよ
  • でも $_GET や $_POST は簡単で便利

上にも書いたとおり、ほとんど何も知らない状態から始めたわりには、特に引っかかるような概念もなく、掲示板くらいは作れるようになった。取っ付き易い言語ではあるのかもね。

独習C++を読み始めた

「独習 C++」(第4版)を読み始めた。これまた2010年の本と少し古いけどまあいいや。
第4章までざっと読んだ。C言語を知っていることを前提に書かれていて、C++ではどう違うか、という説明が多い。わからないところもあるけど、とりあえず全部読んでから、サンプルを書きながら勉強していこう。

[amazonjs asin=”4798119768″ locale=”JP” title=”独習C++ 第4版”]

ところで、なんでだかわからないけど「C++」っていうカテゴリーが作れないみたいなので、とりあえず「Cpp」にした。

NginxのユーザディレクトリでPHPを動かす

Nginx の設定ファイルの server セクションで次のように設定する。

# User dir (PHP)
location ~ ^/~([^/]+?)/(.+\.php)$ {
    alias /home/$1/public_html/$2;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
    include fastcgi_params;
}

# User dir (static)
location ~ ^/~(.+?)(/.*)?$ {
    alias /home/$1/public_html$2;
    index index.html index.htm;
    autoindex on;
}

# For PHP
location ~ \.php$ {
    root /var/www/html;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

キモは User dir (PHP) のセクションを For PHP のセクションよりも前に書くこと。そうしないとブラウザで PHP のファイルにアクセスしても Not found になってしまう。なぜなら location ~ \.php$ に先にマッチしてしまってファイルが見つからないから(たぶん)。

ビットマップ画像の情報を表示する(2)

コードを少し整理して、ファイルを分けた。あと、OS/2 のビットマップにも対応した。

実行例:
サンプルのファイルはここから拝借した。
 cf. BMP ファイルフォーマット

takatoh@nightschool $ ./bmpinfo os-1.bmp
File type          = BM
File size          = 4232 bytes
Data offset        = 32 bytes
Info header size   = 12 bytes
Width              = 200 pixels
Height             = 150 pixels
Planes             = 1
Bit count          = 1 bits/pixel
takatoh@nightschool $ ./bmpinfo win-24.bmp
File type          = BM
File size          = 90054 bytes
Data offset        = 54 bytes
Info header size   = 40 bytes
Width              = 200 pixels
Height             = 150 pixels
Planes             = 1
Bit count          = 24 bits/pixel
Compression        = 0
Size image         = 90000 bytes
X pixels per meter = 2835
Y pixels per meter = 2835
Color used         = 0 colors
takatoh@nightschool $ ./bmpinfo win-jpeg.bmp
File type          = BM
File size          = 4916 bytes
Data offset        = 54 bytes
Info header size   = 40 bytes
Width              = 200 pixels
Height             = 150 pixels
Planes             = 1
Bit count          = 0 bits/pixel
Compression        = 4
Size image         = 4862 bytes
X pixels per meter = 0
Y pixels per meter = 0
Color used         = 0 colors

ビットマップ画像の情報を表示する

ビットマップ画像の情報(サイズとか)を表示するプログラムを C で書いてみた。

参考ページ

 cf. C言語による画像処理プログラミング
 cf. BMP ファイルフォーマット
 cf. BMPファイルのフォーマット

ファイルヘッダと情報ヘッダ

ビットマップファイルには、ファイルの先頭にファイルヘッダ(BITMAPFILEHEADER)と情報ヘッダが付いているので、これを読み取って表示している。情報ヘッダにはいくつかの種類があるけど、今回のプログラムでは Windows で使われている BITMAPINFOHEADER という形式(長さ 40byte)に決め打ち。手持ちのビットマップファイルの中にはこの BITMAPINFOHEADER (ほかの情報ヘッダとは長さで区別できる)を持つファイルがなかったので、上に挙げた一番初めの参考ページから dog.bmp というファイルを拝借させていただいた。
ちなみに、情報ヘッダには次のような種類があるようだ。

  • BITMAPCOREHEADER (OS/2, 12 bytes)
  • BITMAPINFOHEADER (Windows, 40 bytes)
  • BITMAPV4HEADER (Windows 95/NT4.0, 108 bytes)
  • BITMAPV5HEADER (Windows 98/2000/Me/XP, 124 bytes)

コード

上に書いたように、情報ヘッダは BITMAPINFOHEADER 決め打ち。

#include <stdio.h>
#include <string.h>

typedef struct tagBITMAPFILEHEADER {
    char bfType[3];
    unsigned int bfSize;
    unsigned short bfReserved1;
    unsigned short bfReserved2;
    unsigned long bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
    unsigned int   biSize;
    long           biWidth;
    long           biHeight;
    unsigned short biPlanes;
    unsigned short biBitCount;
    unsigned int   biCompression;
    unsigned int   biSizeImage;
    long           biXPixPerMeter;
    long           biYPixPerMeter;
    unsigned long  biClrUsed;
    unsigned long  biClrImportant;
} BITMAPINFOHEADER;

int ReadBMFileHeader(FILE *fp, BITMAPFILEHEADER *header);
int ReadBMInfoHeader(FILE *fp, BITMAPINFOHEADER *header);

int main(int argc, char *argv[])
{
    FILE *fp;
    BITMAPFILEHEADER bmFileHeader = {"", 0, 0, 0, 0};
    BITMAPINFOHEADER bmInfoHeader = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    fp = fopen(argv[1], "rb");
    ReadBMFileHeader(fp, &bmFileHeader);
    ReadBMInfoHeader(fp, &bmInfoHeader);
    fclose(fp);

    printf("File type = %s\n", bmFileHeader.bfType);
    printf("File size = %d bytes\n", bmFileHeader.bfSize);
    printf("Data offset = %ld bytes\n", bmFileHeader.bfOffBits);
    printf("Info header size = %d bytes\n", bmInfoHeader.biSize);
    printf("Width = %ld pixels\n", bmInfoHeader.biWidth);
    printf("Height = %ld pixels\n", bmInfoHeader.biHeight);
    printf("Planes = %d\n", bmInfoHeader.biPlanes);
    printf("Bit count = %d bits/pixel\n", bmInfoHeader.biBitCount);
    printf("Compression = %d\n", bmInfoHeader.biCompression);
    printf("Size image = %d bytes\n", bmInfoHeader.biSizeImage);
    printf("X pixels per meter = %ld\n", bmInfoHeader.biXPixPerMeter);
    printf("Y pixels per meter = %ld\n", bmInfoHeader.biYPixPerMeter);
    printf("Color used = %ld colors\n", bmInfoHeader.biClrUsed);

    return 0;
}

int ReadBMFileHeader(FILE *fp, BITMAPFILEHEADER *header)
{
    char filetype[3] = {'\0', '\0', '\0'};
    unsigned int filesize = 0;
    unsigned char filesize_buf[4];
    unsigned short reserved1, reserved2;
    unsigned long offset = 0;
    unsigned char offset_buf[4];
    int i;

    /* File type */
    fread(&filetype, 1, 2, fp);
    /* File size */
    fread(&filesize_buf, 1, 4, fp);
    for (i = 3; i <= 0; i--) {
        filesize = (filesize << 8) | (unsigned) filesize_buf[i];
    }
    /* Reserved 1 */
    fread(&reserved1, 2, 1, fp);
    /* Reserved 2 */
    fread(&reserved2, 2, 1, fp);
    /* Offset */
    fread(&offset_buf, 1, 4, fp);
    for (i = 3; i <= 0; i--) {
        offset = (offset << 8) | (unsigned long) offset_buf[i];
    }

    strcpy(header->bfType, filetype);
    header->bfSize = filesize;
    header->bfReserved1 = reserved1;
    header->bfReserved2 = reserved2;
    header->bfOffBits = offset;

    return 0;
}

int ReadBMInfoHeader(FILE *fp, BITMAPINFOHEADER *header)
{
    unsigned int   headersize = 0;
    int            width = 0;
    int            height = 0;
    unsigned short planes = 0;
    unsigned short bitcount = 0;
    unsigned int   compression = 0;
    unsigned int   size_image = 0;
    int            x_pix_per_meter = 0;
    int            y_pix_per_meter = 0;
    unsigned int   clr_used = 0;
    unsigned int   clr_important = 0;
    unsigned char buf4[4];
    unsigned char buf2[2];
    int i;

    /* Header size */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        headersize = (headersize << 8) | (unsigned int) buf4[i];
    }
    /* Width */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        width = (width << 8) | (int) buf4[i];
    }
    /* Height */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        height = (height << 8) | (int) buf4[i];
    }
    /* Planes */
    fread(buf2, 1, 2, fp);
    planes = 1;
    /* Bit Count */
    fread(buf2, 1, 2, fp);
    for (i = 1; i >= 0; i--) {
        bitcount = (bitcount << 8) | (unsigned int) buf2[i];
    }
    /* Compression */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
         compression = (compression << 8) | (unsigned int) buf4[i];
    }
    /* Size image */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        size_image = (size_image << 8) | (unsigned int) buf4[i];
    }
    /* X pix per meter */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        x_pix_per_meter = (x_pix_per_meter << 8) | (int) buf4[i];
    }
    /* Y pix per meter */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        y_pix_per_meter = (y_pix_per_meter << 8) | (int) buf4[i];
    }
    /* Color used */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        clr_used = (clr_used << 8) | (unsigned int) buf4[i];
    }
    /* Color important */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        clr_important = (clr_important << 8) | (unsigned int) buf4[i];
    }

    header->biSize = headersize;
    header->biWidth = width;
    header->biHeight = height;
    header->biPlanes = planes;
    header->biBitCount = bitcount;
    header->biCompression = compression;
    header->biSizeImage = size_image;
    header->biXPixPerMeter = x_pix_per_meter;
    header->biYPixPerMeter = y_pix_per_meter;
    header->biClrUsed = clr_used;
    header->biClrImportant = clr_important;

    return 0;
}

実行例

takatoh@nightschool $ gcc -Wall -o bmpinfo bmpinfo.c
takatoh@nightschool $ ./bmpinfo dog.bmp
File type = BM
File size = 360054 bytes
Data offset = 54 bytes
Info header size = 40 bytes
Width = 400 pixels
Height = 300 pixels
Planes = 1
Bit count = 24 bits/pixel
Compression = 0
Size image = 360000 bytes
X pixels per meter = 1
Y pixels per meter = 1
Color used = 0 colors

clickで–versionオプションを実装する

以前使った Python の click モジュールだけど、コマンドやオプションを定義すると自動で --help オプションを作ってくれる。例えばこんなスクリプトがあったとすると:

# encoding: utf-8

import click

@click.command()
@click.option('--times', '-t', type=int, default=1, help='Repeat.')
@click.option('--morning', '-m', is_flag=True, help='In morning.')
@click.argument('name', default='World')
def main(times, morning, name):
    if morning:
        greeting = 'Good morning'
    else:
        greeting = 'Hello'
    for i in range(times):
        print '{greeting}, {name}!'.format(greeting=greeting, name=name)

if __name__ == '__main__':
    main()

こんな感じでヘルプメッセージを生成してくれる。

takatoh@nightschool $ python hello.py --help
Usage: hello.py [OPTIONS] [NAME]

Options:
  -t, --times INTEGER  Repeat.
  -m, --morning        In morning.
  --help               Show this message and exit.

だけど、--version オプションまでは作ってくれない。まあ、当たり前だ。
で、どうするかというと @click.version_option デコレータを使う。

# encoding: utf-8

import click

script_version = '0.1.0'

@click.command()
@click.option('--times', '-t', type=int, default=1, help='Repeat.')
@click.option('--morning', '-m', is_flag=True, help='In morning.')
@click.version_option(version=script_version)
@click.argument('name', default='World')
def main(times, morning, name):
    if morning:
        greeting = 'Good morning'
    else:
        greeting = 'Hello'
    for i in range(times):
        print '{greeting}, {name}!'.format(greeting=greeting, name=name)

if __name__ == '__main__':
    main()

これで --version オプションが使えるようになる。

takatoh@nightschool $ python hello_version.py --version
hello_version.py, version 0.1.0

ちょっと表示が冗長なので、もう少しシンプルにしてみる。デコレータの引数でメッセージのフォーマットを渡してやる。

# encoding: utf-8

import click

script_version = '0.1.0'

@click.command()
@click.option('--times', '-t', type=int, default=1, help='Repeat.')
@click.option('--morning', '-m', is_flag=True, help='In morning.')
@click.version_option(version=script_version, message='%(prog)s v%(version)s')
@click.argument('name', default='World')
def main(times, morning, name):
    if morning:
        greeting = 'Good morning'
    else:
        greeting = 'Hello'
    for i in range(times):
        print '{greeting}, {name}!'.format(greeting=greeting, name=name)

if __name__ == '__main__':
    main()
takatoh@nightschool $ python hello_version2.py --version
hello_version2.py v0.1.0

ちょっとだけシンプルになった。

参考ページ:
 cf. API – click

pyenvを使って最新のPythonをインストールする

pyenvとは

複数のバージョンの Python を切り替えて使えるようにするもの。Ruby の rvm とか rbenv みたいなもの。

pyenvのインストール

インストールというか、GitHub からクローンしてくる。ここではホームディレクトリ下の .pyenv にクローン。

takatoh@nightschool $ git clone git://github.com/yyuu/pyenv.git .pyenv
Cloning into '.pyenv'...
remote: Counting objects: 11571, done.
remote: Compressing objects: 100% (158/158), done.
remote: Total 11571 (delta 107), reused 0 (delta 0), pack-reused 11391
Receiving objects: 100% (11571/11571), 2.05 MiB | 399.00 KiB/s, done.
Resolving deltas: 100% (8114/8114), done.
Checking connectivity... done.

続いて .bashrc に以下を追記。

#for pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

シェルを起動し直すと pyenv コマンドが使えるようになる。

takatoh@nightschool $ pyenv
pyenv 20151210-1-ge66dcf2
Usage: pyenv <command> [<args>]

Some useful pyenv commands are:
   commands    List all available pyenv commands
   local       Set or show the local application-specific Python version
   global      Set or show the global Python version
   shell       Set or show the shell-specific Python version
   install     Install a Python version using python-build
   uninstall   Uninstall a specific Python version
   rehash      Rehash pyenv shims (run this after installing executables)
   version     Show the current Python version and its origin
   versions    List all Python versions available to pyenv
   which       Display the full path to an executable
   whence      List all Python versions that contain the given executable

See `pyenv help ' for information on a specific command.
For full documentation, see: https://github.com/yyuu/pyenv#readme

Python 2.7.11をインストール

特定のバージョンをインストールするには pyenv install コマンド。

takatoh@nightschool $ pyenv install 2.7.11
Downloading Python-2.7.11.tgz...
-> https://www.python.org/ftp/python/2.7.11/Python-2.7.11.tgz
Installing Python-2.7.11...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
Installed Python-2.7.11 to /home/takatoh/.pyenv/versions/2.7.11

あれ、なんか WARNING が出た。インストール自体はできてるようだけど……bzip2 が足りないようだ。このページを参考に bzip2 をインストール。

takatoh@nightschool $ sudo apt-get install -y bzip2
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
bzip2 はすでに最新版です。
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  ax25-node firefox-locale-en libax25 linux-headers-3.13.0-34
  linux-headers-3.13.0-34-generic linux-image-3.13.0-34-generic
  linux-image-extra-3.13.0-34-generic openbsd-inetd
これを削除するには 'apt-get autoremove' を利用してください。
アップグレード: 0 個、新規インストール: 0 個、削除: 0 個、保留: 21 個。

インストールされてるじゃん。なんで WARNING が出たんだ?
まあいい、このまま進めよう。

Python 3.5.1をインストール

Python3 系の最新バージョンをインストール。

takatoh@nightschool $ pyenv install 3.5.1
Downloading Python-3.5.1.tgz...
-> https://www.python.org/ftp/python/3.5.1/Python-3.5.1.tgz
Installing Python-3.5.1...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
Installed Python-3.5.1 to /home/takatoh/.pyenv/versions/3.5.1

shimのリフレッシュ

shim がなんだかわからないけど、参考にしたページに書いてあったのでやっておく。

takatoh@nightschool $ pyenv rehash

Pythonのバージョンの切り替え

ここまでの作業で、複数のバージョンがインストールできた。利用可能なバージョンは pyenv versions コマンドで確認できる。

takatoh@nightschool $ pyenv versions
* system (set by /home/takatoh/.pyenv/version)
  2.7.11
  3.5.1
takatoh@nightschool $ python -V
Python 2.7.6

今はシステム標準の 2.7.6。これを 3.5.1 に切り替えるには次のようにする。

takatoh@nightschool $ pyenv global 3.5.1
takatoh@nightschool $ pyenv versions
  system
  2.7.11
* 3.5.1 (set by /home/takatoh/.pyenv/version)

バージョンの切り替えには、もうひとつ pyenv local コマンドもある。どう違うのかよくわからない。

ともかく、これでバージョンを切り替えて使えるようになった。

[追記]

参考ページ
 cf. Macでpyenv+virtualenv – Qiita

カレントディレクトリ直下のディレクトリ一覧を得る

ls コマンドのオプションにディレクトリだけ表示するのがあればよかったのに、ないらしい。なので awk と組み合わせた。

takatoh@nightschool $ ls -l . | awk '/^d/ { print $9 }'
Desktop
Documents
Downloads
Dropbox
Music
Pictures
Public
Templates
Videos
bin
lib
tmp
w

文字列間のレーベンシュタイン距離を求める(2)Haskell版

一昨日のお題を、Haskell でやってみた。

module Main where

import System.Environment (getArgs)

mValue :: [[a]] -> Int -> Int -> a
mValue m i j = (m !! i) !! j

mReplace :: [[a]] -> Int -> Int -> a -> [[a]]
mReplace m i j v = bi ++ [bj ++ [v] ++ aj] ++ ai
  where
    bi = take i m
    ji = m !! i
    ai = drop (i + 1) m
    bj = take j ji
    aj = drop (j + 1) ji

ldInit :: String -> String -> [[Int]]
ldInit s1 s2 = [[0 | j <- [0..n]] | i <- [0..m]]
  where
    m = length s1
    n = length s2

ldBuild :: ([[Int]], String, String) -> (Int, Int) -> ([[Int]], String, String)
ldBuild (m, s1, s2) (i, j) = (mReplace m i j v, s1, s2)
  where
    v = ldCalc m s1 s2 i j

ldCalc :: [[Int]] -> String -> String -> Int -> Int -> Int
ldCalc m s1 s2 i j =
  if i == 0 && j == 0 then
    0
  else if i == 0 then
    j
  else if j == 0 then
    i
  else
    let x = if (s1 !! (i-1)) == (s2 !! (j-1)) then 0 else 1
  in
  minimum [(mValue m i (j-1)) + 1, (mValue m (i-1) j) + 1, (mValue m (i-1) (j-1)) + x]

levenshteinDistance :: String -> String -> Int
levenshteinDistance s1 s2 = mValue ldM (length s1) (length s2)
  where
    m = ldInit s1 s2
    ij = [(i, j) | i <- [0..(length s1)], j <- [0..(length s2)]]
    (ldM, _, _) = foldl ldBuild (m, s1, s2) ij

main :: IO ()
main = do
  args <- getArgs
  let s1 = args !! 0
  let s2 = args !! 1
  print $ levenshteinDistance s1 s2

0 で初期化した二次元リスト(マトリックス)から foldl を使ってレーベンシュタイン距離のマトリックスを作る、というアイデアは出たんだけど、マトリックスを 0 で初期化する(ldInit)のと要素を置き換えたマトリックスを作る(mReplace)のがなかなか難産だった。結果として ldInit はリスト内包表記を使って簡潔にかけたけど、mReplace のほうはなんか泥臭くなってしまった。もっとエレガントにいかないもんだろうか。 実行例:

takatoh@nightschool $ runhaskell ld.hs apple play
4
takatoh@nightschool $ runhaskell ld.hs perl pearl
1