HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

いろいろなクラスのiteratorのまとめ

C++の勉強をしていると、いろんなところにiteratorが出てきて面倒なので、先に主に使うクラスのiteraatorをまとめておく。

ソースコード

#include <iostream>
#include <string>
#include <set>
#include <map>
#include <list>
#include <vector>
#include <deque>
#include <array>

int main() {
    std::cout << "string" << std::endl;
    {
        std::string s = "hello";
        std::string::iterator i = s.begin();
        while (i != s.end()) {
            std::cout << "    " << *i++ << std::endl;
        }
    }

    std::cout << "array" << std::endl;
    {
        std::array<int, 5> a = { 1, 3, 4, 2, 5 };
        std::array<int, 5>::iterator i = a.begin();
        while (i != a.end()) {
            std::cout << "    " << *i++ << std::endl;
        }
    }

    std::cout << "set" << std::endl;
    {
        std::set<int> s = { 1, 3, 4, 2, 5 };
        std::set<int>::iterator i = s.begin();
        while (i != s.end()) {
            std::cout << "    " << *i++ << std::endl;
        }
    }

    std::cout << "map" << std::endl;
    {
        std::map<int,std::string> m;
        m.insert(std::make_pair(3, "world"));
        m.insert(std::make_pair(1, "hello"));
        m.insert(std::make_pair(4, "!!"));
        m.insert(std::make_pair(2, ","));
        std::map<int,std::string>::iterator i = m.begin();
        while (i != m.end()) {
            std::cout << "    " << i->first << "=" << i->second << std::endl;
            ++i;
        }
    }

    std::cout << "list" << std::endl;
    {
        std::list<int> l = { 1, 3, 4, 2, 5 };
        std::list<int>::iterator i = l.begin();
        while (i != l.end()) {
            std::cout << "    " << *i++ << std::endl;
        }
    }

    std::cout << "vector" << std::endl;
    {
        std::vector<int> v = { 1, 3, 4, 2, 5 };
        std::vector<int>::iterator i = v.begin();
        while (i != v.end()) {
            std::cout << "    " << *i++ << std::endl;
        }
    }

    std::cout << "deque" << std::endl;
    {
        std::deque<int> q = { 1, 3, 4, 2, 5 };
        std::deque<int>::iterator i = q.begin();
        while (i != q.end()) {
            std::cout << "    " << *i++ << std::endl;
        }
    }

    return EXIT_SUCCESS;
}

実行結果

string
    h
    e
    l
    l
    o
array
    1
    3
    4
    2
    5
set
    1
    2
    3
    4
    5
map
    1=hello
    2=,
    3=world
    4=!!
list
    1
    3
    4
    2
    5
vector
    1
    3
    4
    2
    5
deque
    1
    3
    4
    2
    5

まぁ、オチとしては、全て

auto i = obj.begin();

で済んでしまうというところか。

vectorクラスまとめ

C++でよく使われるvectorクラスだが、メソッドがよく分からなかったので、サンプルを全部詰め込んでみた。

ソースコード

#include <array>
#include <iostream>
#include <vector>

std::vector<int>& init() {
    static std::vector<int> v = {1, 99, 4};
    return v;
}
int dump(const std::vector<int>& v) {
    std::cout << "  elem = {";
    for (int x : v) {
        std::cout << " " << x;
    }
    std::cout << " } ";

    std::cout << "( size = " << v.size();

    std::cout << ", max_size = " << v.max_size();

    std::cout << ", capacity = " << v.capacity();

    std::cout << ", empty = " << v.empty();

    std::cout << " )" << std::endl;
}
void t1() {
    std::vector<int> v1 = init();
    const std::vector<int>& cv1 = v1;

    std::cout << "initial" << std::endl;
    dump(v1);
    
    std::cout << "begin(), end()" << std::endl;
    for (decltype(v1)::iterator i = v1.begin(); i != v1.end(); ++i) {
        std::cout << " " << *i;
    }
    std::cout << std::endl;
    for (decltype(v1)::const_iterator i = cv1.begin(); i != cv1.end(); ++i) {
        std::cout << " " << *i;
    }
    std::cout << std::endl;
    
    std::cout << "rbegin(), rend()" << std::endl;
    for (decltype(v1)::reverse_iterator i = v1.rbegin(); i != v1.rend(); ++i) {
        std::cout << " " << *i;
    }
    std::cout << std::endl;
    for (decltype(v1)::const_reverse_iterator i = cv1.rbegin(); i != cv1.rend(); ++i) {
        std::cout << " " << *i;
    }
    std::cout << std::endl;
}
void t2() {
    const std::vector<int> v1 = init();

    std::cout << "initial" << std::endl;
    dump(v1);
    
    std::cout << "cbegin(), cend()" << std::endl;
    for (decltype(v1)::const_iterator i = v1.cbegin(); i != v1.cend(); ++i) {
        std::cout << " " << *i;
    }
    std::cout << std::endl;
    
    std::cout << "crbegin(), crend()" << std::endl;
    for (decltype(v1)::const_reverse_iterator i = v1.crbegin(); i != v1.crend(); ++i) {
        std::cout << " " << *i;
    }
    std::cout << std::endl;
}
void t3() {
    std::vector<int> v1 = init();

    std::cout << "initial" << std::endl;
    dump(v1);

    std::cout << "v1.resize(5)" << std::endl;
    v1.resize(5);
    dump(v1);

    std::cout << "v1.resize(2)" << std::endl;
    v1.resize(2);
    dump(v1);

    std::cout << "v1.reserve(10)" << std::endl;
    v1.reserve(10);
    dump(v1);

    std::cout << "v1.shrink_to_fit()" << std::endl;
    v1.shrink_to_fit();
    dump(v1);
}
void t4() {
    std::vector<int> v1 = init();

    std::cout << "initial" << std::endl;
    dump(v1);

    std::cout << "v1[0] = 3" << std::endl;
    v1[0] = 3;
    dump(v1);

    std::cout << "v1.at(1)" << std::endl;
    std::cout << "  " << v1.at(1) << std::endl;

    std::cout << "v1.data()" << std::endl;
    int* p = v1.data();
    std::cout << "  *p = " << *p << std::endl;
    std::cout << "  *(++p) = " << *(++p) << std::endl;

    std::cout << "v1.front()" << std::endl;
    int& f = v1.front();
    std::cout << "  f = " << f << std::endl;

    std::cout << "v1.back()" << std::endl;
    int& b = v1.back();
    std::cout << "  b = " << b << std::endl;
}
void t5() {
    std::vector<int> v1 = init();

    std::cout << "initial" << std::endl;
    dump(v1);

    std::cout << "v1.push_back(999)" << std::endl;
    v1.push_back(999);
    dump(v1);

    std::cout << "v1.pop_back()" << std::endl;
    v1.pop_back();
    dump(v1);

    std::cout << "v1.insert(v1.begin() + 1, 9999)" << std::endl;
    v1.insert(v1.begin() + 1, 9999);
    dump(v1);

    std::cout << "v1.erase(v1.begin() + 2)" << std::endl;
    v1.erase(v1.begin() + 2);
    dump(v1);

    std::cout << "v1.clear()" << std::endl;
    v1.clear();
    dump(v1);

    std::cout << "v1.assign({ 4, 3, 2, 1 })" << std::endl;
    const std::array<int, 4> a = { 4, 3, 2, 1 };
    v1.assign(a.begin(), a.end());
    dump(v1);
}
void t6() {
    std::vector<int> v1;
    std::cout << "v1.emplace_back(5)" << std::endl;
    v1.emplace_back(5);
    dump(v1);

    std::vector<int> v2;
    std::cout << "v2.emplace(v2.begin(), 5)" << std::endl;
    v2.emplace(v2.begin(), 5);
    dump(v2);
}
void t7() {
    std::vector<int> v1 = { 1, 2, 3 };
    std::vector<int> v2 = { 4, 5, 6 };
    std::cout << "v1.swap(v2)" << std::endl;
    dump(v1);
    dump(v2);
    v1.swap(v2);
    dump(v1);
    dump(v2);
}
int main() {
    t1();

    t2();

    t3();

    t4();

    t5();

    t6();

    t7();

    return EXIT_SUCCESS;
}

実行結果

initial
  elem = { 1 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
begin(), end()
 1 99 4
 1 99 4
rbegin(), rend()
 4 99 1
 4 99 1
initial
  elem = { 1 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
cbegin(), cend()
 1 99 4
crbegin(), crend()
 4 99 1
initial
  elem = { 1 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
v1.resize(5)
  elem = { 1 99 4 0 0 } ( size = 5, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.resize(2)
  elem = { 1 99 } ( size = 2, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.reserve(10)
  elem = { 1 99 } ( size = 2, max_size = 4611686018427387903, capacity = 10, empty = 0 )
v1.shrink_to_fit()
  elem = { 1 99 } ( size = 2, max_size = 4611686018427387903, capacity = 2, empty = 0 )
initial
  elem = { 1 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
v1[0] = 3
  elem = { 3 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
v1.at(1)
  99
v1.data()
  *p = 3
  *(++p) = 99
v1.front()
  f = 3
v1.back()
  b = 4
initial
  elem = { 1 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
v1.push_back(999)
  elem = { 1 99 4 999 } ( size = 4, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.pop_back()
  elem = { 1 99 4 } ( size = 3, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.insert(v1.begin() + 1, 9999)
  elem = { 1 9999 99 4 } ( size = 4, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.erase(v1.begin() + 2)
  elem = { 1 9999 4 } ( size = 3, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.clear()
  elem = { } ( size = 0, max_size = 4611686018427387903, capacity = 6, empty = 1 )
v1.assign({ 4, 3, 2, 1 })
  elem = { 4 3 2 1 } ( size = 4, max_size = 4611686018427387903, capacity = 6, empty = 0 )
v1.emplace_back(5)
  elem = { 5 } ( size = 1, max_size = 4611686018427387903, capacity = 1, empty = 0 )
v2.emplace(v2.begin(), 5)
  elem = { 5 } ( size = 1, max_size = 4611686018427387903, capacity = 1, empty = 0 )
v1.swap(v2)
  elem = { 1 2 3 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
  elem = { 4 5 6 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
  elem = { 4 5 6 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )
  elem = { 1 2 3 } ( size = 3, max_size = 4611686018427387903, capacity = 3, empty = 0 )

参考

PHPでPOSTする

大昔に書いた記事にウソがあったので、それを訂正しつつ、PHPでPOSTするためのコードを何通りか書いてみるテスト。

下準備

以下のようなAPIを用意しておく。HTMLコードを吐き出す仕様になっているのは気にしない。

<dl>
<?php foreach ($_POST as $key => $value): ?>
    <dt><?php echo htmlspecialchars($key); ?></dt>
    <dd><?php echo htmlspecialchars($value); ?></dd>
<?php endforeach ?>
</dl>
<hr />
<dl>
<?php foreach ($_FILES as $key => $file): ?>
    <dt><?php echo htmlspecialchars($key); ?></dt>
    <dd>
        name: <?php echo htmlspecialchars($file['name']); ?>
        <br />
        type: <?php echo htmlspecialchars($file['type']); ?>
        <br />
        size: <?php echo htmlspecialchars($file['size']); ?>
        <br />
        error: <?php echo htmlspecialchars($file['error']); ?>
        <br />
        tmp_name: <?php echo htmlspecialchars($file['tmp_name']); ?>
        <br />
        <pre><?php var_dump(htmlspecialchars(file_get_contents($file['tmp_name']))); ?></pre>
    </dd>
<?php endforeach ?>
</dl>

本題

以前に書いたコードの焼き直しみたいなものだが。

<?php

$protocol = 'http';
$host = 'localhost';
$port = 80;
$path = '/post-api.php';
$apiUrl = "{$protocol}://{$host}:{$port}/{$path}";

$params = array(
    'title' => 'POST test',
    'content' => 'sample text',
);
$files = array(
    'my_file' => 'sample.txt'
);

function buildRequestData($params, $files = null) {
    $data = '';
    if (!empty($files) && is_array($files)) {
        $boundaryStr = '________' . time() . '________';
        $contentType = "Content-Type: multipart/form-data; boundary={$boundaryStr}";
        foreach ($params as $key => $value) {
            $data .= "--{$boundaryStr}\r\n";
            $data .= "Content-Disposition: form-data; name={$key}\r\n";
            $data .= "\r\n";
            $data .= "{$value}\r\n";
        }
        foreach ($files as $key => $file) {
            $filename = basename($file);
            $data .= "--{$boundaryStr}\r\n";
            $data .= "Content-Disposition: form-data; name={$key}; filename=\"{$filename}\"\r\n";
            $data .= "Content-Type: application/octet-stream\r\n";
            $data .= "\r\n";
            $data .= file_get_contents($file) . "\r\n";
        }
        $data .= "--{$boundaryStr}--\r\n";
    } else {
        $contentType = 'Content-Type: application/x-www-form-urlencoded';
        $data .= http_build_query($params);
    }
    $headers = array(
        $contentType,
        'Content-Length: ' . strlen($data),
    );
    $requestData = array(
        'method' => 'POST',
        'content' => $data,
        'header' => implode("\r\n", $headers),
    );
    return $requestData;
}

function test_file_get_contents($url, $params, $files = null) {
    printf("=== %s ===\n", __FUNCTION__);
    $context = stream_context_create(array('http' => buildRequestData($params, $files)));
    var_dump(file_get_contents($url, false, $context));
}
function test_fopen($url, $params, $files = null) {
    printf("=== %s ===\n", __FUNCTION__);
    $context = stream_context_create(array('http' => buildRequestData($params, $files)));
    $sock = fopen($url, 'rb', false, $context);
    if ($sock) {
        while (($s = fgets($sock))) {
            $tmp .= $s;
        }
        var_dump($tmp);
        fclose($sock);
    }
}
function test_fsockopen($host, $port, $path, $params, $files = null) {
    printf("=== %s ===\n", __FUNCTION__);
    $sock = fsockopen($host, $port);
    if ($sock) {
        $requestData = buildRequestData($params, $files);

        fputs($sock, "{$requestData['method']} {$path} HTTP/1.1\r\n");
        fputs($sock, "Host: {$host}\r\n");
        fputs($sock, "{$requestData['header']}\r\n");
        fputs($sock, "\r\n");
        fputs($sock, "{$requestData['content']}");

        $len = 0;
        while (strlen(trim($s = fgets($sock)))) {
            if (preg_match('/^Content-Length: /', $s)) {
                $len = (int)preg_replace('/^Content-Length: /', '', $s);
            }
            echo "[H] {$s}";
        }
        $s = fread($sock, $len);
        var_dump($s);
        fclose($sock);
    }
}
function test_stream_socket_client($protocol, $host, $port, $path, $params, $files = null) {
    printf("=== %s ===\n", __FUNCTION__);
    $remoteSocket = "{$protocol}://{$host}:{$port}";
    $sock = stream_socket_client($remoteSocket);
    if ($sock) {
        $requestData = buildRequestData($params, $files);

        fputs($sock, "{$requestData['method']} {$path} HTTP/1.1\r\n");
        fputs($sock, "Host: {$host}\r\n");
        fputs($sock, "{$requestData['header']}\r\n");
        fputs($sock, "\r\n");
        fputs($sock, "{$requestData['content']}");

        $len = 0;
        while (strlen(trim($s = fgets($sock)))) {
            if (preg_match('/^Content-Length: /', $s)) {
                $len = (int)preg_replace('/^Content-Length: /', '', $s);
            }
            echo "[H] {$s}";
        }
        $s = fread($sock, $len);
        var_dump($s);
        fclose($sock);
    }
}

test_file_get_contents($apiUrl, $params);
test_file_get_contents($apiUrl, $params, $files);
echo "\n";
test_fopen($apiUrl, $params);
test_fopen($apiUrl, $params, $files);
echo "\n";
test_fsockopen($host, $port, $path, $params);
test_fsockopen($host, $port, $path, $params, $files);
echo "\n";
test_stream_socket_client('tcp', $host, $port, $path, $params);
test_stream_socket_client('tcp', $host, $port, $path, $params, $files);

実行結果は以下のような感じ。

=== test_file_get_contents ===
string(105) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
</dl>
"
=== test_file_get_contents ===
string(326) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
    <dt>my_file</dt>
    <dd>
        name: sample.txt        <br />
        type: application/octet-stream      <br />
        size: 21        <br />
        error: 0        <br />
        tmp_name: /tmp/phpTb7Eyx        <br />
        <pre>string(21) "SAMPLE TEXT CONTENTS
"
</pre>
    </dd>
</dl>
"

=== test_fopen ===
string(105) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
</dl>
"
=== test_fopen ===
string(326) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
    <dt>my_file</dt>
    <dd>
        name: sample.txt        <br />
        type: application/octet-stream      <br />
        size: 21        <br />
        error: 0        <br />
        tmp_name: /tmp/php2AD6yA        <br />
        <pre>string(21) "SAMPLE TEXT CONTENTS
"
</pre>
    </dd>
</dl>
"

=== test_fsockopen ===
[H] HTTP/1.1 200 OK
[H] Date: Sun, 30 May 2021 11:54:49 GMT
[H] Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/5.6.40
[H] X-Powered-By: PHP/5.6.40
[H] Content-Length: 105
[H] Content-Type: text/html; charset=UTF-8
string(105) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
</dl>
"
=== test_fsockopen ===
[H] HTTP/1.1 200 OK
[H] Date: Sun, 30 May 2021 11:54:49 GMT
[H] Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/5.6.40
[H] X-Powered-By: PHP/5.6.40
[H] Content-Length: 326
[H] Content-Type: text/html; charset=UTF-8
string(326) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
    <dt>my_file</dt>
    <dd>
        name: sample.txt        <br />
        type: application/octet-stream      <br />
        size: 21        <br />
        error: 0        <br />
        tmp_name: /tmp/phpssgUsE        <br />
        <pre>string(21) "SAMPLE TEXT CONTENTS
"
</pre>
    </dd>
</dl>
"

=== test_stream_socket_client ===
[H] HTTP/1.1 200 OK
[H] Date: Sun, 30 May 2021 11:54:49 GMT
[H] Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/5.6.40
[H] X-Powered-By: PHP/5.6.40
[H] Content-Length: 105
[H] Content-Type: text/html; charset=UTF-8
string(105) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
</dl>
"
=== test_stream_socket_client ===
[H] HTTP/1.1 200 OK
[H] Date: Sun, 30 May 2021 11:54:49 GMT
[H] Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/5.6.40
[H] X-Powered-By: PHP/5.6.40
[H] Content-Length: 326
[H] Content-Type: text/html; charset=UTF-8
string(326) "<dl>
    <dt>title</dt>
    <dd>POST test</dd>
    <dt>content</dt>
    <dd>sample text</dd>
</dl>
<hr />
<dl>
    <dt>my_file</dt>
    <dd>
        name: sample.txt        <br />
        type: application/octet-stream      <br />
        size: 21        <br />
        error: 0        <br />
        tmp_name: /tmp/phpPF5VVZ        <br />
        <pre>string(21) "SAMPLE TEXT CONTENTS
"
</pre>
    </dd>
</dl>
"

「プログラマ脳を鍛える数学パズル」補完計画:進数変換(2)

以下でC言語版を書いた。

hhelibex.hatenablog.jp

それのC++版。せっかくなのでstringを使って書いてみた。

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

/*
 * 与えられた文字列を逆順にする。
 *
 * str 文字列が格納された配列
 */
string reverse(string src) {
    string dest(src.rbegin(), src.rend());
    return dest;
}

/*
 * 与えられた文字セットを使って、整数値の進数変換をして文字列にする。
 *
 * digits 使用する文字セット
 * src 変換元の整数値
 * dest 変換結果を格納する文字配列
 */
string dec(string digits, int src) {
    int base = digits.length();

    int i = 0;

    char buf[128];
    // 下位の桁から順にbufに格納する
    do {
        int r = src % base;
        buf[i++] = digits[r];
        src /= base;
    } while (src > 0);
    buf[i] = '\0';

    string dest(buf);
    // 上位が先頭になるように文字列を逆順にする
    dest = reverse(dest);
    return dest;
}

/*
 * 10進⇒2進変換
 */
string toBin(int src) {
    return dec("01", src);
}

/*
 * 10進⇒8進変換
 */
string toOct(int src) {
    return dec("01234567", src);
}

/*
 * 10進⇒16進変換
 */
string toHex(int src) {
    return dec("0123456789ABCDEF", src);
}

int main(int argc, char** argv) {
    string dBin;
    string dOct;
    string dHex;
    int sample[] = { 0, 1, 2, 5, 8, 123456789, 0x7fffffff };

    for (int i = 0; i < sizeof(sample) / sizeof(int); ++i) {
        dBin = toBin(sample[i]);
        dOct = toOct(sample[i]);
        dHex = toHex(sample[i]);
        cout << sample[i] << endl;
        cout << "  => " << dBin << "(2)" << endl;
        cout << "  => " << dOct << "(8)" << endl;
        cout << "  => " << dHex << "(16)" << endl;
    }
    return EXIT_SUCCESS;
}

実行結果。

0
  => 0(2)
  => 0(8)
  => 0(16)
1
  => 1(2)
  => 1(8)
  => 1(16)
2
  => 10(2)
  => 2(8)
  => 2(16)
5
  => 101(2)
  => 5(8)
  => 5(16)
8
  => 1000(2)
  => 10(8)
  => 8(16)
123456789
  => 111010110111100110100010101(2)
  => 726746425(8)
  => 75BCD15(16)
2147483647
  => 1111111111111111111111111111111(2)
  => 17777777777(8)
  => 7FFFFFFF(16)

「プログラマ脳を鍛える数学パズル」補完計画:進数変換(1)

本業が開店休業状態でリハビリしなきゃなぁということで6年前の本だけど掘り返してみた。

この本の1問目で「C言語には2進表記や8進表記に一発で変換する方法はない」とあったので、リハビリがてら書いてみた。といっても大したものではないけど。

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

/*
 * 与えられた文字列を逆順にする。
 *
 * str 文字列が格納された配列
 */
void reverse(char* str) {
    for (int i = 0, j = strlen(str) - 1; i < j; ++i, --j) {
        char ch = str[i];
        str[i] = str[j];
        str[j] = ch;
    }
}

/*
 * 与えられた文字セットを使って、整数値の進数変換をして文字列にする。
 *
 * digits 使用する文字セット
 * src 変換元の整数値
 * dest 変換結果を格納する文字配列
 */
void dec(char* digits, int src, char* dest) {
    int base = strlen(digits);

    int i = 0;

    // 下位の桁から順にdestに格納する
    do {
        int r = src % base;
        dest[i++] = digits[r];
        src /= base;
    } while (src > 0);
    dest[i] = '\0';

    // 上位が先頭になるように文字列を逆順にする
    reverse(dest);
}

/*
 * 10進⇒2進変換
 */
void toBin(int src, char* dest) {
    dec("01", src, dest);
}

/*
 * 10進⇒8進変換
 */
void toOct(int src, char* dest) {
    dec("01234567", src, dest);
}

/*
 * 10進⇒16進変換
 */
void toHex(int src, char* dest) {
    dec("0123456789ABCDEF", src, dest);
}

int main(int argc, char** argv) {
    char dBin[128];
    char dOct[128];
    char dHex[128];
    int sample[] = { 0, 1, 2, 5, 8, 123456789, 0x7fffffff };

    for (int i = 0; i < sizeof(sample) / sizeof(int); ++i) {
        toBin(sample[i], dBin);
        toOct(sample[i], dOct);
        toHex(sample[i], dHex);
        fprintf(stdout, "%d\n", sample[i]);
        fprintf(stdout, "  => %s(2)\n", dBin);
        fprintf(stdout, "  => %s(8)\n", dOct);
        fprintf(stdout, "  => %s(16)\n", dHex);
    }
    return 0;
}

実行結果。

0
  => 0(2)
  => 0(8)
  => 0(16)
1
  => 1(2)
  => 1(8)
  => 1(16)
2
  => 10(2)
  => 2(8)
  => 2(16)
5
  => 101(2)
  => 5(8)
  => 5(16)
8
  => 1000(2)
  => 10(8)
  => 8(16)
123456789
  => 111010110111100110100010101(2)
  => 726746425(8)
  => 75BCD15(16)
2147483647
  => 1111111111111111111111111111111(2)
  => 17777777777(8)
  => 7FFFFFFF(16)

ひと仕事おしまい。

カーソルの使用方法

もうZend Framework 1(ZF1)の話はいいやって感じだけど一応メモ。

ZF1を使ってDBへのアクセスを実現するには、通常はZend_Db_Table_Abstractクラスを継承したモデルクラスを作って実現すると思うじゃないですか。

<?php

require_once 'Zend/Db/Table/Abstract.php';

class Model_Hoge extends Zend_Db_Table_Abstract {
    protected $_name = 'hoge';
}

で、例えばデータの取得は以下のような感じ。

$query = $hoge->select();
$rows = $hoge->fetchAll($query);
$res= array();
while ($rows->valid()) {
    $row = $rows->current();
    $res[$row->id] = $row->name;
    $rows->next();
}

でも、これだと、Zend_Db_Table_Rowset_Abstractが返された瞬間に全レコードのデータがメモリに載るので、件数が多かったりするとメモリアロケーションエラーが発生する。

そんなわけで「Zend Framework カーソル」なんて検索すると以下のページがヒットしたりするわけなんだが。

Statement?DB Adapter? なんか嫌な予感がした。

先のモデルクラスをベースにすると、以下のように書かないと、大量データの処理の際には使い物にならないという。

$query = $hoge->select();
$stmt = $hoge->getAdapter()->query($query->__toString());
$res = array();
while (($row = $stmt->fetch()) !== false) {
    $res[$row["id"]] = $row["name"];
}

もう、Zend_Db_Table_Rowset_AbstractやZend_Db_Table_Row_Abstractの恩恵一切なし・・・

ついでだったので、INSERTがクソ遅いという問題についても調べてみた。

元のコードはこんな感じ。

$start = time();
for ($i = 10000; $i < 11000; ++$i) {
    $data = array(
        "id" => $i,
        "name" => "hhelibex-{$i}",
    );
    $hoge->insert($data);
}
echo('insert time:' . (time() - $start));
$start = time();
try {
    for ($i = 10000; $i < 11000; ++$i) {
        $data = array(
            "name" => "HHeLiBeX-{$i}",
        );
        $where = array(
            "id = ?" => $i,
        );
        $hoge->update($data, $where);
    }
} catch (Exception $e) {
    echo '<pre>' . $e->__toString() . '</pre>';
}
echo('update time:' . (time() - $start));
$start = time();
try {
    for ($i = 10000; $i < 11000; ++$i) {
        $where = array(
            "id = ?" => $i,
        );
        $hoge->delete($where);
    }
} catch (Exception $e) {
    echo '<pre>' . $e->__toString() . '</pre>';
}
echo('delete time:' . (time() - $start));

これを実行すると、環境にもよるが(だいぶ前のLet's Note上のCentOS 7なので)、

insert time:40
update time:41
delete time:37

で、こんなふうに書き換えてみた。

<?php

require_once 'Zend/Db/Table/Abstract.php';

class Model_Hoge extends Zend_Db_Table_Abstract {
        protected $_name = 'hoge';
        public function getTableName() {
                return $this->_name;
        }
}

$start = time();
try {
    for ($i = 10000; $i < 11000; ++$i) {
        $data = array(
            "id" => $i,
            "name" => "hhelibex-{$i}",
        );
        $hoge->getAdapter()->insert($hoge->getTableName(), $data);
    }
} catch (Exception $e) {
    echo '<pre>' . $e->__toString() . '</pre>';
}
echo('insert time:' . (time() - $start));
$start = time();
try {
    for ($i = 10000; $i < 11000; ++$i) {
        $data = array(
            "name" => "HHeLiBeX-{$i}",
        );
        $where = array(
            "id = ?" => $i,
        );
        $hoge->getAdapter()->update($hoge->getTableName(), $data, $where);
    }
} catch (Exception $e) {
    echo '<pre>' . $e->__toString() . '</pre>';
}
echo('update time:' . (time() - $start));
$start = time();
try {
    for ($i = 10000; $i < 11000; ++$i) {
        $where = array(
            "id = ?" => $i,
        );
        $hoge->getAdapter()->delete($hoge->getTableName(), $where);
    }
} catch (Exception $e) {
    echo '<pre>' . $e->__toString() . '</pre>';
}
echo('delete time:' . (time() - $start));

結果は。

insert time:16
update time:16
delete time:14

半分以下にはなった。

Zend Framework 1+PostgreSQL

ある時、Zend Framework 1系+PostgreSQLで組まれたシステムのPostgreSQLバージョンアップを行う機会があったのだけど、PostgreSQL 12以降には上げられないことが判明して、PostgreSQL 11で妥協したことがあった。 CentOS 6のサポート期限も近づいているし、レガシーシステムの面倒を見ているとこういう場面にちょくちょく出くわす。

そこで、各バージョンのZend FrameworkPostgreSQLの組み合わせでの挙動確認を行ってみたメモ・・と思ったが、検証環境の構築を書くのが面倒になったので結論だけ(ぉ。

ちなみに、PostgreSQLについては以下で書いているので参考までに。

hhelibex.hatenablog.jp

Zend Framework 1からPostgreSQL 12以上に接続すると以下のようなエラーが発生する。

exception 'PDOException' with message 'SQLSTATE[42703]: Undefined column: 7 ERROR:  列d.adsrcは存在しません
LINE 9:                 d.adsrc AS default_value,
                        ^' in /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Statement/Pdo.php:228
Stack trace:
#0 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Statement/Pdo.php(228): PDOStatement->execute(Array)
#1 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Statement.php(303): Zend_Db_Statement_Pdo->_execute(Array)
#2 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Adapter/Abstract.php(480): Zend_Db_Statement->execute(Array)
#3 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Adapter/Pdo/Abstract.php(238): Zend_Db_Adapter_Abstract->query('SELECT\n        ...', Array)
#4 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Adapter/Pdo/Pgsql.php(177): Zend_Db_Adapter_Pdo_Abstract->query('SELECT\n        ...')
#5 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(836): Zend_Db_Adapter_Pdo_Pgsql->describeTable('hoge', NULL)
#6 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(875): Zend_Db_Table_Abstract->_setupMetadata()
#7 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(983): Zend_Db_Table_Abstract->_setupPrimaryKey()
#8 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Select.php(100): Zend_Db_Table_Abstract->info()
#9 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Select.php(78): Zend_Db_Table_Select->setTable(Object(Model_Hoge))
#10 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(1019): Zend_Db_Table_Select->__construct(Object(Model_Hoge))
#11 /var/www/html/test/2020-1018-01/v1-1/Application/controllers/IndexController.php(15): Zend_Db_Table_Abstract->select()
#12 /var/www/html/test/2020-1018-01/v1-1/Zend/Controller/Action.php(516): IndexController->indexAction()
#13 /var/www/html/test/2020-1018-01/v1-1/Zend/Controller/Dispatcher/Standard.php(308): Zend_Controller_Action->dispatch('indexAction')
#14 /var/www/html/test/2020-1018-01/v1-1/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))
#15 /var/www/html/test/2020-1018-01/v1-1/index.php(7): Zend_Controller_Front->dispatch()
#16 {main}

Next exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE[42703]: Undefined column: 7 ERROR:  列d.adsrcは存在しません
LINE 9:                 d.adsrc AS default_value,
                        ^, query was: SELECT
                a.attnum,
                n.nspname,
                c.relname,
                a.attname AS colname,
                t.typname AS type,
                a.atttypmod,
                FORMAT_TYPE(a.atttypid, a.atttypmod) AS complete_type,
                d.adsrc AS default_value,
                a.attnotnull AS notnull,
                a.attlen AS length,
                co.contype,
                ARRAY_TO_STRING(co.conkey, ',') AS conkey
            FROM pg_attribute AS a
                JOIN pg_class AS c ON a.attrelid = c.oid
                JOIN pg_namespace AS n ON c.relnamespace = n.oid
                JOIN pg_type AS t ON a.atttypid = t.oid
                LEFT OUTER JOIN pg_constraint AS co ON (co.conrelid = c.oid
                    AND a.attnum = ANY(co.conkey) AND co.contype = 'p')
                LEFT OUTER JOIN pg_attrdef AS d ON d.adrelid = c.oid AND d.adnum = a.attnum
            WHERE a.attnum > 0 AND c.relname = 'hoge' ORDER BY a.attnum' in /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Statement/Pdo.php:235
Stack trace:
#0 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Statement.php(303): Zend_Db_Statement_Pdo->_execute(Array)
#1 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Adapter/Abstract.php(480): Zend_Db_Statement->execute(Array)
#2 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Adapter/Pdo/Abstract.php(238): Zend_Db_Adapter_Abstract->query('SELECT\n        ...', Array)
#3 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Adapter/Pdo/Pgsql.php(177): Zend_Db_Adapter_Pdo_Abstract->query('SELECT\n        ...')
#4 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(836): Zend_Db_Adapter_Pdo_Pgsql->describeTable('hoge', NULL)
#5 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(875): Zend_Db_Table_Abstract->_setupMetadata()
#6 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(983): Zend_Db_Table_Abstract->_setupPrimaryKey()
#7 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Select.php(100): Zend_Db_Table_Abstract->info()
#8 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Select.php(78): Zend_Db_Table_Select->setTable(Object(Model_Hoge))
#9 /var/www/html/test/2020-1018-01/v1-1/Zend/Db/Table/Abstract.php(1019): Zend_Db_Table_Select->__construct(Object(Model_Hoge))
#10 /var/www/html/test/2020-1018-01/v1-1/Application/controllers/IndexController.php(15): Zend_Db_Table_Abstract->select()
#11 /var/www/html/test/2020-1018-01/v1-1/Zend/Controller/Action.php(516): IndexController->indexAction()
#12 /var/www/html/test/2020-1018-01/v1-1/Zend/Controller/Dispatcher/Standard.php(308): Zend_Controller_Action->dispatch('indexAction')
#13 /var/www/html/test/2020-1018-01/v1-1/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))
#14 /var/www/html/test/2020-1018-01/v1-1/index.php(7): Zend_Controller_Front->dispatch()
#15 {main}

ここで出てくる「d.adsrc」とは「pg_attrdef.adsrc」のことなので、調べてみる。

$ ( v=11 ; /usr/pgsql-${v}/bin/psql -U postgres -p $((5432+v)) -c '\d pg_attrdef' )
                テーブル "pg_catalog.pg_attrdef"
   列    |      型      | 照合順序 | Null 値を許容 | デフォルト
---------+--------------+----------+---------------+------------
adrelid | oid          |          | not null      |
adnum   | smallint     |          | not null      |
adbin   | pg_node_tree |          |               |
adsrc   | text         |          |               |
インデックス:
    "pg_attrdef_adrelid_adnum_index" UNIQUE, btree (adrelid, adnum)
    "pg_attrdef_oid_index" UNIQUE, btree (oid)
$ ( v=12 ; /usr/pgsql-${v}/bin/psql -U postgres -p $((5432+v)) -c '\d pg_attrdef' )
                テーブル"pg_catalog.pg_attrdef"
   列    |      型      | 照合順序 | Null 値を許容 | デフォルト
---------+--------------+----------+---------------+------------
oid     | oid          |          | not null      |
adrelid | oid          |          | not null      |
adnum   | smallint     |          | not null      |
adbin   | pg_node_tree | C        | not null      |
インデックス:
    "pg_attrdef_adrelid_adnum_index" UNIQUE, btree (adrelid, adnum)
    "pg_attrdef_oid_index" UNIQUE, btree (oid)

確かに「adsrc」カラムがv12から無くなっている。

ちなみに、Zend Framework 2ではPostgreSQL 13まで問題なく動作した。

実際に確かめてみたいという方は、以下のリソースをダウンロードしてごにょごにょすれば(いい加減)。

Zend Framework本体は以下からダウンロードする。

また、いろいろと面倒なので、v2の方はZend Skeleton Applicationに頼る。