HHeLiBeXの日記 正道編

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

mb_encode_mimeheader/mb_decode_mimeheaderする際には内部文字エンコーディングに注意

マニュアルをちゃんと読むと書いてあるのだが。

前者のmb_encode_mimeheader()は、以下のように書いてある。

パラメータ

str
 エンコードする文字列。 mb_internal_encoding() と同じエンコーディングにしなければいけません。

つまり、マルチバイト文字を扱う限り、mb_internal_encoding()による内部文字エンコーディングの設定が必須なのだ。

ついでに、後者のmb_decode_mimeheader()のマニュアルも見てみる。

返り値

内部文字エンコーディングでデコードされた文字列を返します。

「内部文字エンコーディング」と明記してある。

というわけで、ちょっと試してみるために、以下のようなシナリオに沿ったプログラムを書いてみる。

共通部品/処理

<?php

/*
 * コマンド「od -c」を模したダンプを出力する関数。
 */
function od($str) {
    for ($i = 0; $i < strlen($str); ++$i) {
        if ($i % 16 === 0) {
            if ($i > 0) {
                echo PHP_EOL;
            }
            printf("%08o", $i);
        }
        $s = substr($str, $i, 1);
        if (ctype_graph($s) || $s === " ") {
            printf(" %3s", $s);
        } else {
            printf(" %03o", ord($s));
        }
    }
    if ($i % 16 !== 15) {
        echo PHP_EOL;
    }
    printf("%08o\n", $i);
}
<?php

// ** このファイルはもちろん「UTF-8」で保存する **

// 初期設定
mb_internal_encoding("UTF-8");

// メールで送る文字列の件名
$str = "あいうえおかきくけこさしすせそたちつてとなにぬねの";

シナリオ1

ソースコードは以下の通り。

<?php

include("od.php");
include("init.php");

// ** このファイルはもちろん「UTF-8」で保存する **

// 文字列の文字エンコーディング変換
$convStr = mb_convert_encoding($str, "ISO-2022-JP", "UTF-8");

// エンコード処理
$encStr = mb_encode_mimeheader($convStr, "ISO-2022-JP");
echo "// エンコードされた文字列" . PHP_EOL;
var_dump($encStr);

echo PHP_EOL;

// デコード処理
$decStr = mb_decode_mimeheader($encStr);
echo "// デコードされた文字列" . PHP_EOL;
var_dump($decStr);
od($decStr);

実行結果。

// エンコードされた文字列
string(115) "=?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREJEYk?=
 =?ISO-2022-JP?B?SCRKJEskTCRNJE4bKEI=?="

// デコードされた文字列
string(68) "あいうえおかきくけこさしすせそたちつてH$J$K$L$M$N"
00000000 343 201 202 343 201 204 343 201 206 343 201 210 343 201 212 343
00000020 201 213 343 201 215 343 201 217 343 201 221 343 201 223 343 201
00000040 225 343 201 227 343 201 231 343 201 233 343 201 235 343 201 237
00000060 343 201 241 343 201 244 343 201 246   H   $   J   $   K   $   L
00000100   $   M   $   N
00000104

あれ、文字化けした。

多分、mb_decode_mimeheader()でデコードするときに以下のような処理をしているのだろうと思われる。

<?php
mb_internal_encoding("UTF-8");
var_dump(
    mb_convert_encoding(base64_decode("GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREJEYk"), mb_internal_encoding(), "ISO-2022-JP")
    . mb_convert_encoding(base64_decode("SCRKJEskTCRNJE4bKEI="), mb_internal_encoding(), "ISO-2022-JP"));
string(68) "あいうえおかきくけこさしすせそたちつてH$J$K$L$M$N"

同じ結果になった。

というわけで、これではダメ。

シナリオ2

ソースコードは以下の通り。

<?php

include("od.php");
include("init.php");

// ** このファイルはもちろん「UTF-8」で保存する **

// 文字列の文字エンコーディング変換
$convStr = mb_convert_encoding($str, "ISO-2022-JP", "UTF-8");

// 内部文字エンコーディングを変更
$origInternalEncoding = mb_internal_encoding();
mb_internal_encoding("ISO-2022-JP");

// エンコード処理
$encStr = mb_encode_mimeheader($convStr, "ISO-2022-JP");
echo "// エンコードされた文字列" . PHP_EOL;
var_dump($encStr);

// 内部文字エンコーディングを戻す
mb_internal_encoding($origInternalEncoding);

echo PHP_EOL;

// 内部文字エンコーディングを変更
mb_internal_encoding("ISO-2022-JP");

// デコード処理
$decStr = mb_decode_mimeheader($encStr);
echo "// デコードされた文字列" . PHP_EOL;
var_dump($decStr);
od($decStr);

// 内部文字エンコーディングを戻す
mb_internal_encoding($origInternalEncoding);

実行結果。

// エンコードされた文字列
string(123) "=?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREGyhC?=
 =?ISO-2022-JP?B?GyRCJEYkSCRKJEskTCRNJE4bKEI=?="

// デコードされた文字列
string(56) "あいうえおかきくけこさしすせそたちつてとなにぬねの"
00000000 033   $   B   $   "   $   $   $   &   $   (   $   *   $   +   $
00000020   -   $   /   $   1   $   3   $   5   $   7   $   9   $   ;   $
00000040   =   $   ?   $   A   $   D   $   F   $   H   $   J   $   K   $
00000060   L   $   M   $   N 033   (   B
00000070

一見よさそうだが、ダンプを見ると、「ISO-2022-JP」になっている。システムの内部文字エンコーディングは「UTF-8」だったはずだ。

これもダメ。

シナリオ3

ソースコードは以下の通り。

<?php

include("od.php");
include("init.php");

// ** このファイルはもちろん「UTF-8」で保存する **

// 文字列の文字エンコーディング変換
$convStr = mb_convert_encoding($str, "ISO-2022-JP", "UTF-8");

// 内部文字エンコーディングを変更
$origInternalEncoding = mb_internal_encoding();
mb_internal_encoding("ISO-2022-JP");

// エンコード処理
$encStr = mb_encode_mimeheader($convStr, "ISO-2022-JP");
echo "// エンコードされた文字列" . PHP_EOL;
var_dump($encStr);

// 内部文字エンコーディングを戻す
mb_internal_encoding($origInternalEncoding);

echo PHP_EOL;

// デコード処理
$decStr = mb_decode_mimeheader($encStr);
echo "// デコードされた文字列" . PHP_EOL;
var_dump($decStr);
od($decStr);

実行結果。

// エンコードされた文字列
string(123) "=?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQrJC0kLyQxJDMkNSQ3JDkkOyQ9JD8kQSREGyhC?=
 =?ISO-2022-JP?B?GyRCJEYkSCRKJEskTCRNJE4bKEI=?="

// デコードされた文字列
string(75) "あいうえおかきくけこさしすせそたちつてとなにぬねの"
00000000 343 201 202 343 201 204 343 201 206 343 201 210 343 201 212 343
00000020 201 213 343 201 215 343 201 217 343 201 221 343 201 223 343 201
00000040 225 343 201 227 343 201 231 343 201 233 343 201 235 343 201 237
00000060 343 201 241 343 201 244 343 201 246 343 201 250 343 201 252 343
00000100 201 253 343 201 254 343 201 255 343 201 256
00000113

よさそうである。文字化けしていないし、ダンプを見ても「UTF-8」になっている。念のため確認で以下を実行してみた(コンソールの文字エンコーディングUTF-8)。

$ echo -n 'あいうえおかきくけこさしすせそたちつてとなにぬねの' | od -c
0000000 343 201 202 343 201 204 343 201 206 343 201 210 343 201 212 343
0000020 201 213 343 201 215 343 201 217 343 201 221 343 201 223 343 201
0000040 225 343 201 227 343 201 231 343 201 233 343 201 235 343 201 237
0000060 343 201 241 343 201 244 343 201 246 343 201 250 343 201 252 343
0000100 201 253 343 201 254 343 201 255 343 201 256
0000113
$ 

同じ結果だ。

まとめ

というわけで、まとめると、以下のようにして各関数を呼び出さないといけない。

各言語で正規表現「^」「$」「\A」「\z」を試してみる

ということで、あちこちから突っ込みが来ないことを祈りつつ(謎)、手元にある各言語でテストプログラムを書いてみたメモ。

入力は以下のような文字列。

abc
123
*+=

最大4つのパターンを試すが、すべてのパターンでマッチすると、

1234

のように出力される。逆に、マッチしないパターンや、そもそも存在しないマッチ方法の場合は「0」や「-」をそれぞれ出力する。

環境

手元にあるものということで、環境は以下のものに限定する。

Java

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    private static boolean matches(String str, String pattern, int flags) {
        Pattern p = Pattern.compile(pattern, flags);
        Matcher m = p.matcher(str);
        return m.matches();
    }

    public static void main(String[] args) {
        try (Reader in = new InputStreamReader(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            char[] buf = new char[1024];
            int len = in.read(buf);
            String s = new String(buf, 0, len);
//          s = s.trim();

            if (matches(s, "^[0-9]+$", 0)) {
                out.print("1");
            } else {
                out.print("0");
            }

            if (matches(s, "^[0-9]+$", Pattern.MULTILINE)) {
                out.print("2");
            } else {
                out.print("0");
            }

            if (matches(s, "\\A[0-9]+\\z", 0)) {
                out.print("3");
            } else {
                out.print("0");
            }

            if (matches(s, "\\A[0-9]+\\z", Pattern.MULTILINE)) {
                out.print("4");
            } else {
                out.print("0");
            }

            out.println();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

出力。

0000

どのパターンでもマッチしない。完全にマッチしないとダメなようだ。

C

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

int matches(const char* str, const char* pattern, int flags) {
    regex_t rb;
    if (regcomp(&rb, pattern, flags)) {
        perror(pattern);
        exit(1);
    }

    regmatch_t rm;
    int res;
    if (!regexec(&rb, str, 1, &rm, 0)) {
        res = 1;
    } else {
        res = 0;
    }

    regfree(&rb);

    return res;
}

int main(int argc, char** argv) {
    char str[1024];
    memset(str, '\0', sizeof(str));

    fread(str, sizeof(str), sizeof(char), stdin);
//  while (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r') {
//      str[strlen(str) - 1] = '\0';
//  }

    if (matches(str, "^[0-9]+$", REG_EXTENDED)) {
        printf("1");
    } else {
        printf("0");
    }

    // そもそも複数行モードが無い
    printf("-");

    // 文字列の先頭・末尾という正規表現が無い
    printf("-");

    // そもそも複数行モードが無い
    printf("-");

    printf("\n");

    return 0;
}

出力。

0---

まぁ、C言語は仕方がない。パターンが1つしかないので。

C++

#include <iostream>
#include <locale>
#include <string>
#include <boost/regex.hpp>

using namespace std;

bool matches(string str, const char* pattern, boost::match_flag_type flags) {
    boost::regex re(pattern);

    boost::smatch sm;
    return boost::regex_search(str, sm, re, flags);
}

int main(int argc, char** argv) {
    istreambuf_iterator<char> it(cin);
    istreambuf_iterator<char> last;
    string str(it, last);
//  str.erase(str.find_last_not_of("\r\n") + 1);

    if (matches(str, "^[0-9]+$", boost::regex_constants::match_single_line)) {
        cout << "1";
    } else {
        cout << "0";
    }

    if (matches(str, "^[0-9]+$", boost::regex_constants::match_default)) {
        cout << "2";
    } else {
        cout << "0";
    }

    if (matches(str, "\\A[0-9]+\\z", boost::regex_constants::match_single_line)) {
        cout << "3";
    } else {
        cout << "0";
    }

    if (matches(str, "\\A[0-9]+\\z", boost::regex_constants::match_default)) {
        cout << "4";
    } else {
        cout << "0";
    }

    cout << endl;

    return EXIT_SUCCESS;
}

出力。

0200

これがうわさに聞く、複数行モードで「^」「$」を使うと部分文字列にマッチするというものか。

最初のパターンでわざわざ「boost::regex_constants::match_single_line」をフラグに指定していることから分かるように、C++(Boost)のデフォルトは複数行モードのようだ。

PHP

<?php

$s = file_get_contents('php://stdin');
//$s = trim($s);

if (preg_match("/^[0-9]+$/", $s)) {
    echo '1';
} else {
    echo '0';
}

if (preg_match("/^[0-9]+$/m", $s)) {
    echo '2';
} else {
    echo '0';
}

if (preg_match("/\A[0-9]+\z/", $s)) {
    echo '3';
} else {
    echo '0';
}

if (preg_match("/\A[0-9]+\z/m", $s)) {
    echo '4';
} else {
    echo '0';
}

echo PHP_EOL;

出力。

0200

同様に、複数行モードだと「^」「$」を使うと部分文字列にマッチする。

Python 2 / 3

import sys
import re

s = sys.stdin.read()
#s = s.strip()

if re.search(r'^[0-9]+$', s):
    sys.stdout.write('1')
else:
    sys.stdout.write('0')

if re.search(r'^[0-9]+$', s, re.MULTILINE):
    sys.stdout.write('2')
else:
    sys.stdout.write('0')

if re.search(r'\A[0-9]+\Z', s):
    sys.stdout.write('3')
else:
    sys.stdout.write('0')

if re.search(r'\A[0-9]+\Z', s, re.MULTILINE):
    sys.stdout.write('4')
else:
    sys.stdout.write('0')

sys.stdout.write("\n")

出力。

0200

同様に、複数行モードだと「^」「$」を使うと部分文字列にマッチする。

なお、文字列の末尾を表す正規表現が「\z」ではなく「\Z」となることに注意。

Ruby

s = STDIN.read
#s.chomp!

# 単一行モードが無いので。
print "-"

if s.match(/^[0-9]+$/)
    print "2"
else
    print "0"
end

# 単一行モードが無いので。
print "-"

if s.match(/\A[0-9]+\z/)
    print "4"
else
    print "0"
end

print "\n"

出力。

-2-0

調べた限りでは複数行モードしかなかったので、複数行モードのみの出力。

確かに、「^」「$」で部分文字列にマッチする。

Perl

my $s;
{
    local $/ = undef;
    $s = <STDIN>;
}
#chomp($s);

if ($s =~ /^[0-9]+$/) {
    print '1';
} else {
    print '0';
}

if ($s =~ /^[0-9]+$/m) {
    print '2';
} else {
    print '0';
}

if ($s =~ /\A[0-9]+\z/) {
    print '3';
} else {
    print '0';
}

if ($s =~ /\A[0-9]+\z/m) {
    print '4';
} else {
    print '0';
}

print "\n";

出力。

0200

PHPと同様に、mフラグを付けてやると複数行モードで、「^」「$」を使用すると部分文字列にマッチする。

Go

package main

import (
    "fmt"
    "os"
    "regexp"
    "bufio"
    "io/ioutil"
//  "strings"
)

func main() {
    stdin := bufio.NewReader(os.Stdin)
    b, _ := ioutil.ReadAll(stdin)
    s := string(b)
//  s = strings.Trim(s, "\r\n")

    {
        m := regexp.MustCompile(`^[0-9]+$`)
        if m.MatchString(s) {
            fmt.Print("1")
        } else {
            fmt.Print("0")
        }
    }

    {
        m := regexp.MustCompile(`(?m)^[0-9]+$`)
        if m.MatchString(s) {
            fmt.Print("2")
        } else {
            fmt.Print("0")
        }
    }

    {
        m := regexp.MustCompile(`\A[0-9]+\z`)
        if m.MatchString(s) {
            fmt.Print("3")
        } else {
            fmt.Print("0")
        }
    }

    {
        m := regexp.MustCompile(`(?m)\A[0-9]+\z`)
        if m.MatchString(s) {
            fmt.Print("4")
        } else {
            fmt.Print("0")
        }
    }

    fmt.Println()
}

出力。

0200

PHPと同様に、mフラグを付けてやると複数行モードで、「^」「$」を使用すると部分文字列にマッチする。

まとめ

表にまとめると、以下のような感じか。マッチするケースに「○」、マッチしないケースに「×」を入れている。存在しないパターンは「-」としている。

  • (1) 単一行モードで、「^」「$」を使ったパターン
  • (2) 複数行モードで、「^」「$」を使ったパターン
  • (3) 単一行モードで、「\A」「\z」を使ったパターン
  • (4) 複数行モードで、「\A」「\z」を使ったパターン
(1) (2) (3) (4)
Java × × × ×
C ×
C++ × × ×
PHP × × ×
Python 2 / 3 × × ×
Ruby ×
Perl × × ×
Go × × ×

自分も「^」「$」をついつい使ってしまっていたので、気に留めておくことにしよう。

各言語で部分文字列を取得してみる

各言語で入力された文字列の部分文字列を取得するプログラムを書いてみたメモ。

要件は以下の通り。

  • 標準入力から、1行の文字列が与えられる
  • 入力文字列の部分文字列「[2, 4)」(つまり2~3文字目からなる文字列)を抽出
  • 標準出力に、抽出した文字列を出力

環境

手元にあるものということで、環境は以下のものに限定する。

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
    /**
     * サロゲートを考慮したsubstring
     */
    private static String substring(String s, int startIndex, int endIndex) {
        StringBuilder sb = new StringBuilder();

        if (startIndex < 0) {
            throw new StringIndexOutOfBoundsException(startIndex);
        }
        int cpCount = s.codePointCount(0, s.length());
        if (cpCount < endIndex) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - startIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }

        int idx = 0;
        for (int i = 0; i < s.length() && idx < endIndex; ++i) {
            char ch1 = s.charAt(i);
            if (startIndex <= idx && idx < endIndex) {
                sb.append(ch1);
            }
            if (Character.isSurrogate(ch1)) {
                char ch2 = s.charAt(++i);
                if (startIndex <= idx && idx < endIndex) {
                    sb.append(ch2);
                }
            }
            ++idx;
        }

        return sb.toString();
    }

    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String s = in.readLine();

            out.println(substring(s, 1, 3));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

サロゲートペアを考慮すると、Javaでは2つのchar値でサロゲートペアを表すことになるので、部分文字列を抽出する処理に一番手間がかかった。

C

#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <wchar.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    char str[1024];

    fgets(str, sizeof(str), stdin);
    while (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r') {
        str[strlen(str) - 1] = '\0';
    }

    wchar_t buf[1024];
    const char* p = str;
    mbsrtowcs(buf, &p, sizeof(buf), NULL);

    wchar_t wstr[3];
    memset(wstr, 0, sizeof(wstr));
    // 「2」は言うまでもなく、indexではなくlength
    wcsncpy(wstr, &buf[1], 2);
    fwprintf(stdout, L"%ls\n", wstr);

    return 0;
}

C++

#include <iostream>
#include <locale>
#include <string>
#include <boost/regex.hpp>

using namespace std;

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");
    wcout.imbue(locale("japanese"));

    wstring str;
    getline(wcin, str);

    // 「2」はindexではなくlengthであることに注意
    str = str.substr(1, 2);
    wcout << str << endl;

    return EXIT_SUCCESS;
}

PHP

<?php

$str = file_get_contents('php://stdin');
$str = preg_replace("/[\r\n]/", '', $str);

// 「2」はindexではなくlengthであることに注意
echo mb_substr($str, 1, 2, 'UTF-8') . PHP_EOL;

Python 2

import sys

s = sys.stdin.readline()
ustr = unicode(s, 'UTF-8')
ustr = ustr.replace('\n', '')
ustr = ustr.replace('\r', '')

print ustr[1:3].encode('UTF-8')

Python 3

import sys

b = sys.stdin.buffer.readline()
s = str(b, 'UTF-8')
s = s.replace('\n', '')
s = s.replace('\r', '')

print(s[1:3])

Ruby

str = STDIN.gets
str.chomp!()

# 「2」はindexではなくlengthであることに注意
print str[1, 2],"\n"

Perl

use Encode;

my $str = readline(STDIN);
chomp($str);

my $ustr = decode('UTF-8', $str);
# 「2」はindexではなくlengthであることに注意
print encode('UTF-8', substr($ustr, 1, 2)),"\n";

Go

package main

import (
    "fmt"
    "os"
    "io"
    "bufio"
)

func ReadLine(reader *bufio.Reader) (s string, err error) {
    prefix := false
    buf := make([]byte, 0)
    var line []byte
    for {
        line, prefix, err = reader.ReadLine()
        if err == io.EOF {
            return
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s = string(buf)
        return
    }
}

func main() {
    stdin := bufio.NewReader(os.Stdin)
    s, _ := ReadLine(stdin)

    runes := []rune(s)
    fmt.Println(string(runes[1:3]))
}

bash

#! /bin/bash

IFS= read s

echo "${s}" | sed -e 's/^.\(..\).*$/\1/g'

Awk

{
    gsub(/[\r\n]/, "");
    # 第3パラメータの「2」はindexではなくlengthであることに注意
    print substr($0, 2, 2);
}

各言語で指定したディレクトリ内のファイル一覧を取得してみる

各言語で指定したディレクトリ直下のファイル一覧を取得するプログラムを書いてみたメモ。

要件は以下の通り。

  • コマンドライン引数には、ディレクトリ名が1つ指定される
  • 指定されたディレクトリから直下にあるファイルのファイル名一覧を読む
    • ファイルの個数は高々256個とする
    • ディレクトリか通常ファイルしか存在しない
    • ディレクトリは除外する
    • いわゆる隠しファイル("."で始まるファイル名のファイル)は含める
  • 読み込んだファイル名一覧をファイル名の辞書順でソートする
  • ファイル名一覧を、指定されたディレクトリの下の「result/out.txt」に書き込む
    • 「result」ディレクトリはあらかじめ用意してあるので、存在チェック等は不要

環境

手元にあるものということで、環境は以下のものに限定する。

Java

import java.io.File;
import java.io.FileFilter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Main dirname");
            System.exit(1);
            return;
        }

        File dir = new File(args[0]);
        if (!dir.isDirectory()) {
            System.err.println(dir + ": No such directory");
            System.exit(1);
            return;
        }

        // ディレクトリからファイルの一覧を読み込み
        File[] files = dir.listFiles(new FileFilter() {
            public boolean accept(File file) {
                return file.isFile();
            }
        });

        // ファイル名の辞書順でソート
        List<String> filenames = new ArrayList<>();
        for (File file : files) {
            filenames.add(file.getName());
        }
        Collections.sort(filenames);

        // ファイル名一覧の出力
        try (PrintWriter out = new PrintWriter(new FileWriter(new File(dir + "/result/out.txt")))) {
            for (String filename : filenames) {
                out.println(filename);
            }
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

C

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <limits.h>

int isdir(const char* path) {
    struct stat st;
    if (stat(path, &st)) {
        return 0;
    }
    return ((st.st_mode & S_IFMT) == S_IFDIR);
}

int isfile(const char* path) {
    struct stat st;
    if (stat(path, &st)) {
        return 0;
    }
    return ((st.st_mode & S_IFMT) != S_IFDIR);
}

int cmp(const void* p1, const void* p2) {
    const char* str1 = (const char*)p1;
    const char* str2 = (const char*)p2;
    return strcmp(str1, str2);
}

int main(int argc, char** argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s dirname\n", argv[0]);
        exit(1);
    }

    char dir[PATH_MAX];
    strcpy(dir, argv[1]);
    if (!isdir(dir)) {
        fprintf(stderr, "%s: No such directory\n", dir);
        exit(1);
    }

    // ディレクトリからファイルの一覧を読み込み
    DIR* dp = opendir(dir);
    if (!dp) {
        perror(dir);
        exit(1);
    }
    char filenames[256][PATH_MAX];
    int ct = 0;
    struct dirent* entry;
    while ((entry = readdir(dp))) {
        char tmp[PATH_MAX];
        sprintf(tmp, "%s/%s", dir, entry->d_name);
        if (isfile(tmp)) {
            strcpy(filenames[ct], entry->d_name);
            ++ct;
        }
    }
    closedir(dp);

    // ファイル名の辞書順でソート
    qsort(filenames, ct, sizeof(char) * PATH_MAX, cmp);

    // ファイル名一覧の出力
    char outFile[PATH_MAX];
    sprintf(outFile, "%s/result/out.txt", dir);
    FILE* outFp = fopen(outFile, "w");
    if (!outFp) {
        perror(outFile);
        exit(1);
    }
    for (int i = 0; i < ct; ++i) {
        fprintf(outFp, "%s\n", filenames[i]);
    }
    fclose(outFp);

    return 0;
}

C++

#include <iostream>
#include <fstream>
#include <vector>
#include <boost/filesystem.hpp>

using namespace std;
using namespace boost::filesystem;

int main(int argc, char** argv) {
    if (argc != 2) {
        cerr << "Usage: " << argv[0] << " dirname" << endl;
        return EXIT_FAILURE;
    }

    path dir(argv[1]);
    if (!exists(dir) || !is_directory(dir)) {
        cerr << dir << ": No such directory" << endl;
        return EXIT_FAILURE;
    }

    // ディレクトリからファイルの一覧を読み込み
    vector<string> filenames;
    try {
        directory_iterator end;
        for (directory_iterator it(dir); it != end; ++it) {
            if (!is_directory(it->path())) {
                filenames.push_back(it->path().filename().string());
            }
        }
    } catch (const filesystem_error& e) {
        cerr << e.what() << endl;
    }

    // ファイル名の辞書順でソート
    sort(filenames.begin(), filenames.end());

    // ファイル名一覧の出力
    string outFile = dir.string() + "/result/out.txt";
    ofstream outFs(outFile, ios::binary);
    if (!outFs) {
        cerr << outFile << ": Cannot open file" << endl;
        exit(1);
    }
    for (string& filename : filenames) {
        outFs << filename << endl;
    }
    outFs.close();

    return EXIT_SUCCESS;
}

Boostに頼りました。コンパイルには「-lboost_filesystem -lboost_system」が必要。

PHP

<?php

if (count($argv) != 2) {
    file_put_contents("php://stderr", "Usage: {$argv[0]} dirname" . PHP_EOL);
    exit(1);
}
$dir = $argv[1];
if (!is_dir($dir)) {
    file_put_contents('php://stderr', "{$dir}: No such directory" . PHP_EOL);
    exit(1);
}

// ディレクトリからファイルの一覧を読み込み
$dp = opendir($dir);
if (!$dp) {
    file_put_contents('php://stderr', "{$dir}: Cannot open directory" . PHP_EOL);
    exit(1);
}
$filenames = array();
while (($f = readdir($dp))) {
    if (is_file("{$dir}/{$f}")) {
        $filenames[] = $f;
    }
}
closedir($dp);

// ファイル名の辞書順でソート
sort($filenames);

// ファイル名一覧の出力
$outFile = "{$dir}/result/out.txt";
$fp = fopen($outFile, 'w');
if (!$fp) {
    file_put_contents('php://stderr', "{$outFile}: Cannot open output file" . PHP_EOL);
    exit(1);
}
foreach ($filenames as $filename) {
    fprintf($fp, "%s\n", $filename);
}
fclose($fp);

Python 2

# -*- coding: utf-8 -*-
import sys
import os

if len(sys.argv) != 2:
        sys.stderr.write("Usage: " + sys.argv[0] + " dirname\n");
        exit(1)
dirPath = sys.argv[1];
if not os.path.isdir(dirPath):
        sys.stderr.write(dirPath + ": No such directory\n");
        exit(1)

# ディレクトリからファイルの一覧を読み込み
filenames = []
for f in os.listdir(dirPath):
        if os.path.isfile(dirPath + '/' + f):
                filenames.append(f)

# ファイル名の辞書順でソート
filenames.sort()

# ファイル名一覧の出力
outFile = dirPath + "/result/out.txt"
outFp = os.open(outFile, os.O_WRONLY | os.O_CREAT)
for f in filenames:
        os.write(outFp, f + "\n");
os.close(outFp)

Dir.globを使うという手もあるらしいのだが、試したら、隠しファイルを取得するために2回呼び出さないといけないことが分かったので、今回は正攻法で攻めた。

Python 3

import sys
import os

if len(sys.argv) != 2:
        sys.stderr.write("Usage: " + sys.argv[0] + " dirname\n");
        exit(1)
dirPath = sys.argv[1];
if not os.path.isdir(dirPath):
        sys.stderr.write(dirPath + ": No such directory\n");
        exit(1)

# ディレクトリからファイルの一覧を読み込み
filenames = []
for f in os.listdir(dirPath):
        if os.path.isfile(dirPath + '/' + f):
                filenames.append(f)

# ファイル名の辞書順でソート
filenames.sort()

# ファイル名一覧の出力
outFile = dirPath + "/result/out.txt"
outFp = os.open(outFile, os.O_WRONLY | os.O_CREAT)
for f in filenames:
        os.write(outFp, f.encode('utf-8') + b"\n");
os.close(outFp)

Dir.globを使うという手もあるらしいのだが、試したら、隠しファイルを取得するために2回呼び出さないといけないことが分かったので、今回は正攻法で攻めた。

Ruby

if ARGV.length != 1
    STDERR.puts('Usage: ' + __FILE__ + ' dirname')
    exit 1
end

dir = ARGV[0]
if !File.directory?(dir)
    STDERR.puts(dir + ': No such directory')
    exit 1
end

# ディレクトリからファイルの一覧を読み込み
filenames = []
Dir.foreach(dir).each do |filename|
    if File.file?(dir + '/' + filename)
        filenames.push(File.basename(filename))
    end
end

# ファイル名の辞書順でソート
filenames.sort!

# ファイル名一覧の出力
outFile = dir + '/result/out.txt'
outFp = File.open(outFile, mode = 'wb')
for filename in filenames
    outFp.puts(filename + "\n")
end
outFp.close()

Perl

if (@ARGV != 1) {
    print(STDERR 'Usage: ' . __FILE__ . " dirname\n");
    exit(1);
}
my $dir = $ARGV[0];
if (! -d $dir) {
    print(STDERR $dir . ": No such directory\n");
    exit(1);
}

# ディレクトリからファイルの一覧を読み込み
my $dp;
my $res = opendir($dp, $dir);
if (!$res) {
    print(STDERR $dir . ':' . $! . "\n");
    exit(1);
}
my @filenames = ();
my $ct = 0;
while (my $filename = readdir($dp)) {
    if (-f $dir . '/' . $filename) {
        $filenames[$ct++] = $filename;
    }
}
closedir($dp);

# ファイル名の辞書順でソート
@filenames = sort(@filenames);

# ファイル名一覧の出力
my $outFile = $dir . '/result/out.txt';
my $outFp;
my $res = open($outFp, '>', $outFile);
if (!$res) {
    print(STDERR $outFile . ':' . $! . "\n");
    exit(1);
}
for (my $i = 0; $i < @filenames; ++$i) {
    print($outFp $filenames[$i] . "\n");
}
close($outFp);

glob()を使う方法もあるらしいのだが、隠しファイルを取得するために2回呼び出さないといけない感じだったので、今回は正攻法で攻めた。

Go

package main

import (
    "os"
    "fmt"
    "io/ioutil"
    "sort"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "Usage: " + os.Args[0] + " dirname")
        os.Exit(1)
    }
    dir := os.Args[1]
    statInfo, _ := os.Stat(dir)
    if !statInfo.IsDir() {
        fmt.Fprintln(os.Stderr, dir + ": No such directory")
        os.Exit(1)
    }

    // ディレクトリからファイルの一覧を読み込み
    files, err := ioutil.ReadDir(dir)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    count := 0
    for _, file := range files {
        if (!file.IsDir()) {
            count += 1
        }
    }
    filenames := make([]string, count)
    i := 0
    for _, file := range files {
        if (!file.IsDir()) {
            filenames[i] = file.Name()
            i += 1
        }
    }

    // ファイル名の辞書順でソート
    sort.Strings(filenames)

    // ファイル名一覧の出力
    outFile := dir + "/result/out.txt"
    outFp, err := os.Create(outFile)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    for _, filename := range filenames {
        fmt.Fprintln(outFp, filename);
    }
}

"container/list"を使ってもよかった気がするが、ソートが面倒だったので逃げた。

bash

#! /bin/bash

dir=$1
if [ -z "${dir}" ]; then
    echo "Usage: ${0} dirname"
    exit 1
fi
if [ ! -d "${dir}" ]; then
    echo "${dir}: No such directory"
    exit 1
fi

ls -1aF "${dir}" | grep -v '/$' | tr -d / | LANG=C sort > "${dir}/result/out.txt"

「LANG=C」しないと、日本語ファイル名のファイルを含む場合にソート順が期待通りにならない。

各言語での整数型の最大値と最小値

唐突に、各言語での整数型の最大値と最小値をまとめてみようと思ったメモ。

環境

手元にあるものということで、環境は以下のものに限定する。なお、32ビット環境は、このために急きょ作った。

以下、検証に使ったソースと、実行結果(32ビット環境と64ビット環境のそれぞれの実行結果のsdiff)と、補足事項を各言語ごとに記載していく。

どの言語においても、概ね以下の形で出力を出している。

  • 最大値を求めて出力
  • 最大値に+1して、循環することの確認
  • 最小値を求めて出力
  • 最小値に-1して、循環することの確認

Java

public class Main {
    public static void main(String[] args) {
        {
            byte a;
            a = Byte.MAX_VALUE;
            System.out.println("byte:max  = " + a);
            ++a;
            System.out.println("      +1  = " + a);

            a = Byte.MIN_VALUE;
            System.out.println("byte:min  = " + a);
            --a;
            System.out.println("      -1  = " + a);
        }
        {
            short a;
            a = Short.MAX_VALUE;
            System.out.println("short:max = " + a);
            ++a;
            System.out.println("       +1 = " + a);

            a = Short.MIN_VALUE;
            System.out.println("short:min = " + a);
            --a;
            System.out.println("       -1 = " + a);
        }
        {
            int a;
            a = Integer.MAX_VALUE;
            System.out.println("int:max   = " + a);
            ++a;
            System.out.println("     +1   = " + a);

            a = Integer.MIN_VALUE;
            System.out.println("int:min   = " + a);
            --a;
            System.out.println("     -1   = " + a);
        }
        {
            long a;
            a = Long.MAX_VALUE;
            System.out.println("long:max  = " + a);
            ++a;
            System.out.println("      +1  = " + a);

            a = Long.MIN_VALUE;
            System.out.println("long:min  = " + a);
            --a;
            System.out.println("      -1  = " + a);
        }
    }
}
===32bit===                                                     ===64bit===
byte:max  = 127                                                 byte:max  = 127
      +1  = -128                                                      +1  = -128
byte:min  = -128                                                byte:min  = -128
      -1  = 127                                                       -1  = 127
short:max = 32767                                               short:max = 32767
       +1 = -32768                                                     +1 = -32768
short:min = -32768                                              short:min = -32768
       -1 = 32767                                                      -1 = 32767
int:max   = 2147483647                                          int:max   = 2147483647
     +1   = -2147483648                                              +1   = -2147483648
int:min   = -2147483648                                         int:min   = -2147483648
     -1   = 2147483647                                               -1   = 2147483647
long:max  = 9223372036854775807                                 long:max  = 9223372036854775807
      +1  = -9223372036854775808                                      +1  = -9223372036854775808
long:min  = -9223372036854775808                                long:min  = -9223372036854775808
      -1  = 9223372036854775807                                       -1  = 9223372036854775807

さすがにJavaは、環境によって最大値や最小値が変わることはない。

C

#include <stdio.h>
#include <limits.h>

int main(int argc, char** argv) {
    {
        short a;
        a = SHRT_MAX;
        printf("short:max       = %d\n", a);
        ++a;
        printf("             +1 = %d\n", a);

        a = SHRT_MIN;
        printf("short:min       = %d\n", a);
        --a;
        printf("             -1 = %d\n", a);
    }

    {
        unsigned short a;
        a = USHRT_MAX;
        printf("ushort:max      = %u\n", a);
        ++a;
        printf("             +1 = %u\n", a);

        a = 0;
        printf("ushort:min      = %u\n", a);
        --a;
        printf("             -1 = %u\n", a);
    }

    {
        int a;
        a = INT_MAX;
        printf("int:max         = %d\n", a);
        ++a;
        printf("             +1 = %d\n", a);

        a = INT_MIN;
        printf("int:min         = %d\n", a);
        --a;
        printf("             -1 = %d\n", a);
    }

    {
        unsigned int a;
        a = UINT_MAX;
        printf("uint:max        = %u\n", a);
        ++a;
        printf("             +1 = %u\n", a);

        a = 0;
        printf("uint:min        = %u\n", a);
        --a;
        printf("             -1 = %u\n", a);
    }

    {
        long a;
        a = LONG_MAX;
        printf("long:max        = %ld\n", a);
        ++a;
        printf("             +1 = %ld\n", a);

        a = LONG_MIN;
        printf("long:min        = %ld\n", a);
        --a;
        printf("             -1 = %ld\n", a);
    }

    {
        unsigned long a;
        a = ULONG_MAX;
        printf("ulong:max       = %lu\n", a);
        ++a;
        printf("             +1 = %lu\n", a);

        a = 0;
        printf("ulong:min       = %lu\n", a);
        --a;
        printf("             -1 = %lu\n", a);
    }

    {
        long long a;
        a = LLONG_MAX;
        printf("long long:max   = %lld\n", a);
        ++a;
        printf("             +1 = %lld\n", a);

        a = LLONG_MIN;
        printf("long long:min   = %lld\n", a);
        --a;
        printf("             -1 = %lld\n", a);
    }

    {
        unsigned long long a;
        a = ULLONG_MAX;
        printf("ulong long:max  = %llu\n", a);
        ++a;
        printf("             +1 = %llu\n", a);

        a = 0;
        printf("ulong long:min  = %llu\n", a);
        --a;
        printf("             -1 = %llu\n", a);
    }

    return 0;
}
===32bit===                                                     ===64bit===
short:max       = 32767                                         short:max       = 32767
             +1 = -32768                                                     +1 = -32768
short:min       = -32768                                        short:min       = -32768
             -1 = 32767                                                      -1 = 32767
ushort:max      = 65535                                         ushort:max      = 65535
             +1 = 0                                                          +1 = 0
ushort:min      = 0                                             ushort:min      = 0
             -1 = 65535                                                      -1 = 65535
int:max         = 2147483647                                    int:max         = 2147483647
             +1 = -2147483648                                                +1 = -2147483648
int:min         = -2147483648                                   int:min         = -2147483648
             -1 = 2147483647                                                 -1 = 2147483647
uint:max        = 4294967295                                    uint:max        = 4294967295
             +1 = 0                                                          +1 = 0
uint:min        = 0                                             uint:min        = 0
             -1 = 4294967295                                                 -1 = 4294967295
long:max        = 2147483647                                  | long:max        = 9223372036854775807
             +1 = -2147483648                                 |              +1 = -9223372036854775808
long:min        = -2147483648                                 | long:min        = -9223372036854775808
             -1 = 2147483647                                  |              -1 = 9223372036854775807
ulong:max       = 4294967295                                  | ulong:max       = 18446744073709551615
             +1 = 0                                                          +1 = 0
ulong:min       = 0                                             ulong:min       = 0
             -1 = 4294967295                                  |              -1 = 18446744073709551615
long long:max   = 9223372036854775807                           long long:max   = 9223372036854775807
             +1 = -9223372036854775808                                       +1 = -9223372036854775808
long long:min   = -9223372036854775808                          long long:min   = -9223372036854775808
             -1 = 9223372036854775807                                        -1 = 9223372036854775807
ulong long:max  = 18446744073709551615                          ulong long:max  = 18446744073709551615
             +1 = 0                                                          +1 = 0
ulong long:min  = 0                                             ulong long:min  = 0
             -1 = 18446744073709551615                                       -1 = 18446744073709551615

違いが出たのはlong/unsigned longの部分。32ビット環境ではintと同じで、64ビット環境ではlong longと同じ。

C++

#include <iostream>
#include <limits>

using namespace std;

int main(int argc, char** argv) {
    {
        short a;
        a = numeric_limits<short>::max();
        cout << "short:max      = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<short>::min();
        cout << "short:min      = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        unsigned short a;
        a = numeric_limits<unsigned short>::max();
        cout << "ushort:max     = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<unsigned short>::min();
        cout << "ushort:min     = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        int a;
        a = numeric_limits<int>::max();
        cout << "int:max        = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<int>::min();
        cout << "int:min        = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        unsigned int a;
        a = numeric_limits<unsigned int>::max();
        cout << "uint:max       = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<unsigned int>::min();
        cout << "uint:min       = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        long a;
        a = numeric_limits<long>::max();
        cout << "long:max       = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<long>::min();
        cout << "long:min       = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        unsigned long a;
        a = numeric_limits<unsigned long>::max();
        cout << "ulong:max      = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<unsigned long>::min();
        cout << "ulong:min      = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        long long a;
        a = numeric_limits<long long>::max();
        cout << "long long:max  = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<long long>::min();
        cout << "long long:min  = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    {
        unsigned long long a;
        a = numeric_limits<unsigned long long>::max();
        cout << "ulong long:max = " << a << endl;
        ++a;
        cout << "            +1 = " << a << endl;

        a = numeric_limits<unsigned long long>::min();
        cout << "ulong long:min = " << a << endl;
        --a;
        cout << "            -1 = " << a << endl;
    }

    return EXIT_SUCCESS;
}
===32bit===                                                     ===64bit===
short:max      = 32767                                          short:max      = 32767
            +1 = -32768                                                     +1 = -32768
short:min      = -32768                                         short:min      = -32768
            -1 = 32767                                                      -1 = 32767
ushort:max     = 65535                                          ushort:max     = 65535
            +1 = 0                                                          +1 = 0
ushort:min     = 0                                              ushort:min     = 0
            -1 = 65535                                                      -1 = 65535
int:max        = 2147483647                                     int:max        = 2147483647
            +1 = -2147483648                                                +1 = -2147483648
int:min        = -2147483648                                    int:min        = -2147483648
            -1 = 2147483647                                                 -1 = 2147483647
uint:max       = 4294967295                                     uint:max       = 4294967295
            +1 = 0                                                          +1 = 0
uint:min       = 0                                              uint:min       = 0
            -1 = 4294967295                                                 -1 = 4294967295
long:max       = 2147483647                                   | long:max       = 9223372036854775807
            +1 = -2147483648                                  |             +1 = -9223372036854775808
long:min       = -2147483648                                  | long:min       = -9223372036854775808
            -1 = 2147483647                                   |             -1 = 9223372036854775807
ulong:max      = 4294967295                                   | ulong:max      = 18446744073709551615
            +1 = 0                                                          +1 = 0
ulong:min      = 0                                              ulong:min      = 0
            -1 = 4294967295                                   |             -1 = 18446744073709551615
long long:max  = 9223372036854775807                            long long:max  = 9223372036854775807
            +1 = -9223372036854775808                                       +1 = -9223372036854775808
long long:min  = -9223372036854775808                           long long:min  = -9223372036854775808
            -1 = 9223372036854775807                                        -1 = 9223372036854775807
ulong long:max = 18446744073709551615                           ulong long:max = 18446744073709551615
            +1 = 0                                                          +1 = 0
ulong long:min = 0                                              ulong long:min = 0
            -1 = 18446744073709551615                                       -1 = 18446744073709551615

違いが出たのはlong/unsigned longの部分。32ビット環境ではintと同じで、64ビット環境ではlong longと同じ。

PHP

<?php

$a = 1;
while (($a << 1) + 1 > $a) {
    $a <<= 1;
    $a += 1;
}
echo "int:max = " . $a . PHP_EOL;
echo "        = ";
var_dump($a);
++$a;
echo "     +1 = " . $a . PHP_EOL;
echo "        = ";
var_dump($a);

$a = -1;
while (($a << 1) < $a) {
    $a <<= 1;
}
echo "int:min = " . $a . PHP_EOL;
echo "        = ";
var_dump($a);
--$a;
echo "     -1 = " . $a . PHP_EOL;
echo "        = ";
var_dump($a);
===32bit===                                                     ===64bit===
int:max = 2147483647                                          | int:max = 9223372036854775807
        = int(2147483647)                                     |         = int(9223372036854775807)
     +1 = 2147483648                                          |      +1 = 9.2233720368548E+18
        = float(2147483648)                                   |         = float(9.2233720368548E+18)
int:min = -2147483648                                         | int:min = -9223372036854775808
        = int(-2147483648)                                    |         = int(-9223372036854775808)
     -1 = -2147483649                                         |      -1 = -9.2233720368548E+18
        = float(-2147483649)                                  |         = float(-9.2233720368548E+18)

最大値、最小値を表す定数が無いので、計算によって求めている。

最初だまされたのは、32ビット環境で最大値に+1、最小値に-1したときに、一見するとint型に収まっているように見えたこと。 var_dump()すると、float型に変わっていることが分かる。

Python 2

a = 1
ct = 0
while ct < 128 and (a << 1) + 1 > a:
    a <<= 1
    a += 1
    ct += 1
print "long:max? = ",a
print "          = ",type(a)
a += 1
print "       +1 = ",a
print "          = ",type(a)

a = -1
ct = 0
while ct < 128 and (a << 1) < a:
    a <<= 1
    ct += 1
print "long:min? = ",a
print "          = ",type(a)
a -= 1
print "       -1 = ",a
print "          = ",type(a)
===32bit===                                                     ===64bit===
long:max? =  680564733841876926926749214863536422911            long:max? =  680564733841876926926749214863536422911
          =  <type 'long'>                                                =  <type 'long'>
       +1 =  680564733841876926926749214863536422912                   +1 =  680564733841876926926749214863536422912
          =  <type 'long'>                                                =  <type 'long'>
long:min? =  -340282366920938463463374607431768211456           long:min? =  -340282366920938463463374607431768211456
          =  <type 'long'>                                                =  <type 'long'>
       -1 =  -340282366920938463463374607431768211457                  -1 =  -340282366920938463463374607431768211457
          =  <type 'long'>                                                =  <type 'long'>

最大値や最小値という概念が無いことを知っていたので、128ビットまで計算したところで打ち切っている。 計算で出てきた数値に+1、-1してもまだ余地があることが分かる。

Python 3

a = 1
ct = 0
while ct < 128 and (a << 1) + 1 > a:
    a <<= 1
    a += 1
    ct += 1
print("ing:max? = ",a)
print("         = ",type(a))
a += 1
print("      +1 = ",a)
print("         = ",type(a))

a = -1
ct = 0
while ct < 128 and (a << 1) < a:
    a <<= 1
    ct += 1
print("int:min? = ",a)
print("         = ",type(a))
a -= 1
print("      -1 = ",a)
print("         = ",type(a))
===32bit===                                                     ===64bit===
ing:max? =  680564733841876926926749214863536422911             ing:max? =  680564733841876926926749214863536422911
         =  <class 'int'>                                                =  <class 'int'>
      +1 =  680564733841876926926749214863536422912                   +1 =  680564733841876926926749214863536422912
         =  <class 'int'>                                                =  <class 'int'>
int:min? =  -340282366920938463463374607431768211456            int:min? =  -340282366920938463463374607431768211456
         =  <class 'int'>                                                =  <class 'int'>
      -1 =  -340282366920938463463374607431768211457                  -1 =  -340282366920938463463374607431768211457
         =  <class 'int'>                                                =  <class 'int'>

こちらはPython 2の場合と同じ。

Ruby

a = 1
ct = 0
while ct < 128 && (a << 1) + 1 > a
    a <<= 1
    a += 1
    ct += 1
end
print "int:max? = ",a,"\n"
a += 1
print "      +1 = ",a,"\n"

a = -1
ct = 0
while ct < 128 && (a << 1) < a
    a <<= 1
    ct += 1
end
print "int:min? = ",a,"\n"
a -= 1
print "      -1 = ",a,"\n"
===32bit===                                                     ===64bit===
int:max? = 680564733841876926926749214863536422911              int:max? = 680564733841876926926749214863536422911
      +1 = 680564733841876926926749214863536422912                    +1 = 680564733841876926926749214863536422912
int:min? = -340282366920938463463374607431768211456             int:min? = -340282366920938463463374607431768211456
      -1 = -340282366920938463463374607431768211457                   -1 = -340282366920938463463374607431768211457

Rubyも最大値や最小値が無いことを知っていたので、Pythonと同じく128ビットで打ち切っている。

Perl

# 参考:http://d.hatena.ne.jp/sardine/20131026
my $a = ~0;
print "int:max = ",$a,"\n";
++$a;
print "     +1 = ",$a,"\n";

my $a = -(~0 >> 1) - 1;
print "int:min = ",$a,"\n";
--$a;
print "     -1 = ",$a,"\n";
===32bit===                                                     ===64bit===
int:max = 4294967295                                          | int:max = 18446744073709551615
     +1 = 4294967296                                          |      +1 = 1.84467440737096e+19
int:min = -2147483648                                         | int:min = -9223372036854775808
     -1 = -2147483649                                         |      -1 = -9.22337203685478e+18

最初、ビット演算しても期待した結果が得られなくてはまっていた。ソースに書かれた参考サイトの情報が無ければ変な結果を得ていただろう。

32ビットの結果がちょっと変で、最大値/最小値を突き抜けて+1/-1できているように見える。これは何なんだろう・・・

(2017/12/08)様子が分かったので追記。

あるサイト(perl - check if a number is int or float - Stack Overflow)を参考に、変数のダンプ情報を出すようにしてみた。

use Devel::Peek;

# 参考:http://d.hatena.ne.jp/sardine/20131026
# 参考:https://stackoverflow.com/questions/4094036/check-if-a-number-is-int-or-float
my $a = ~0;
print "int:max = ",$a,"\n";
Dump($a);
print STDERR "\n";
++$a;
print "     +1 = ",$a,"\n";
Dump($a);
print STDERR "\n";

my $a = -(~0 >> 1) - 1;
print "int:min = ",$a,"\n";
Dump($a);
print STDERR "\n";
--$a;
print "     -1 = ",$a,"\n";
Dump($a);

すると、以下のような出力が得られる。

===32bit===                                                     ===64bit===
int:max = 4294967295                                          | int:max = 18446744073709551615
SV = IV(0x8139d84) at 0x8139d88                               | SV = IV(0x106a778) at 0x106a788
  REFCNT = 1                                                      REFCNT = 1
  FLAGS = (PADMY,IOK,pIOK,IsUV)                                   FLAGS = (PADMY,IOK,pIOK,IsUV)
  UV = 4294967295                                             |   UV = 18446744073709551615

     +1 = 4294967296                                          |      +1 = 1.84467440737096e+19
SV = PVNV(0x811e9e0) at 0x8139d88                             | SV = PVNV(0x104cfe0) at 0x106a788
  REFCNT = 1                                                      REFCNT = 1
  FLAGS = (PADMY,NOK,POK,pNOK,pPOK)                               FLAGS = (PADMY,NOK,POK,pNOK,pPOK)
  IV = 0                                                          IV = 0
  NV = 4294967296                                             |   NV = 1.84467440737096e+19
  PV = 0x81417a0 "4294967296"\0                               |   PV = 0x106d530 "1.84467440737096e+19"\0
  CUR = 10                                                    |   CUR = 20
  LEN = 36                                                    |   LEN = 40

int:min = -2147483648                                         | int:min = -9223372036854775808
SV = IV(0x8139e14) at 0x8139e18                               | SV = IV(0x106a940) at 0x106a950
  REFCNT = 1                                                      REFCNT = 1
  FLAGS = (PADMY,IOK,pIOK)                                        FLAGS = (PADMY,IOK,pIOK)
  IV = -2147483648                                            |   IV = -9223372036854775808

     -1 = -2147483649                                         |      -1 = -9.22337203685478e+18
SV = PVNV(0x811e9f4) at 0x8139e18                             | SV = PVNV(0x104d000) at 0x106a950
  REFCNT = 1                                                      REFCNT = 1
  FLAGS = (PADMY,NOK,POK,pNOK,pPOK)                               FLAGS = (PADMY,NOK,POK,pNOK,pPOK)
  IV = -2147483648                                            |   IV = -9223372036854775808
  NV = -2147483649                                            |   NV = -9.22337203685478e+18
  PV = 0x8145810 "-2147483649"\0                              |   PV = 0x106d820 "-9.22337203685478e+18"\0
  CUR = 11                                                    |   CUR = 21
  LEN = 36                                                    |   LEN = 40

これを見ると、最大値/最小値に+1/-1した場合はfloatに自動変換されていることが分かる。これですっきり。

Go

package main

import (
    "fmt"
)

func main() {
    var a = 0
    var ct = 0

    a = 1
    ct = 0
    for ct < 128 && (a << 1) + 1 > a {
        a <<= 1
        a += 1
        ct += 1
    }
    fmt.Printf("int:max = %d\n", a)
    a += 1
    fmt.Printf("     +1 = %d\n", a)

    a = -1
    ct = 0
    for ct < 128 && (a << 1) < a {
        a <<= 1
        ct += 1
    }
    fmt.Printf("int:min = %d\n", a)
    a -= 1
    fmt.Printf("     -1 = %d\n", a)
}
===32bit===                                                     ===64bit===
int:max = 2147483647                                          | int:max = 9223372036854775807
     +1 = -2147483648                                         |      +1 = -9223372036854775808
int:min = -2147483648                                         | int:min = -9223372036854775808
     -1 = 2147483647                                          |      -1 = 9223372036854775807

Go言語は32ビット/64ビットの影響を受けるのだなとちょっと意外だった。

bash

#! /bin/bash

a=1
while [ $(((a << 1) + 1)) -gt ${a} ]; do
    a=$(((a << 1) + 1))
done
echo "int:max = ${a}"
a=$((a + 1))
echo "     +1 = ${a}"

a=-1
while [ $((a << 1)) -lt ${a} ]; do
    a=$((a << 1))
done
echo "int:min = ${a}"
a=$((a - 1))
echo "     -1 = ${a}"
===32bit===                                                     ===64bit===
int:max = 9223372036854775807                                   int:max = 9223372036854775807
     +1 = -9223372036854775808                                       +1 = -9223372036854775808
int:min = -9223372036854775808                                  int:min = -9223372036854775808
     -1 = 9223372036854775807                                        -1 = 9223372036854775807

逆に、シェルスクリプトは32ビット/64ビットの影響を受けると思っていたので意外だった。

まとめ

一覧表にしてみる。

32bit max 32bit min 64bit max 64bit min
Java byte 127 -128 127 -128
short 32767 -32768 32767 -32768
int 2147483647 -2147483648 2147483647 -2147483648
long 9223372036854775807 -9223372036854775808 9223372036854775807 -9223372036854775808
C short 32767 -32768 32767 -32768
unsigned short 65535 0 65535 0
int 2147483647 -2147483648 2147483647 -2147483648
unsigned int 4294967295 0 4294967295 0
long 2147483647 -2147483648 9223372036854775807 -9223372036854775808
unsigned long 4294967295 0 18446744073709551615 0
long long 9223372036854775807 -9223372036854775808 9223372036854775807 -9223372036854775808
unsigned long long 18446744073709551615 0 18446744073709551615 0
C++ short 32767 -32768 32767 -32768
unsigned short 65535 0 65535 0
int 2147483647 -2147483648 2147483647 -2147483648
unsigned int 4294967295 0 4294967295 0
long 2147483647 -2147483648 9223372036854775807 -9223372036854775808
unsigned long 4294967295 0 18446744073709551615 0
long long 9223372036854775807 -9223372036854775808 9223372036854775807 -9223372036854775808
unsigned long long 18446744073709551615 0 18446744073709551615 0
PHP int 2147483647 -2147483648 9223372036854775807 -9223372036854775808
Python 2 long -∞ -∞
Python 3 ing -∞ -∞
Ruby int -∞ -∞
Perl int 4294967295 -2147483648 18446744073709551615 -9223372036854775808
Go int 2147483647 -2147483648 9223372036854775807 -9223372036854775808
bash int 9223372036854775807 -9223372036854775808 9223372036854775807 -9223372036854775808

各言語でUTF-8バイト列を文字列置換および文字列分割してみる

各言語でUTF-8のバイト列を読み込み、文字列置換と文字列分割をしてみたメモ。

要件は以下の通り。

  • 標準入力から、文字列が1行だけ入力される。
  • 標準出力に、以下の2つを改行区切りで出力する。
    • 文字列の各文字をすべて'.'で置き換えた文字列
    • 入力文字列の各文字を改行で区切ったもの
  • つまり、10文字の文字列が入力されたら、出力は11行になる

環境

手元にあるものということで、環境は以下のものに限定する。

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String s = in.readLine();

            // 文字列置換
            out.println(s.replaceAll(".", "."));

            // 文字列分割
            for (int i = 0; i < s.length(); ++i) {
                char ch1 = s.charAt(i);
                out.print(ch1);
                if (Character.isSurrogate(ch1)) {
                    ++i;
                    char ch2 = s.charAt(i);
                    out.print(ch2);
                }
                out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

C

#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <stdlib.h>
#include <regex.h>

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    char str[1024];

    fgets(str, sizeof(str), stdin);
    while (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r') {
        str[strlen(str) - 1] = '\0';
    }

    regex_t rb;
    if (regcomp(&rb, ".", REG_EXTENDED | REG_NEWLINE)) {
        perror("regcomp");
        return 1;
    }

    const char* p;
    regmatch_t rm;
    int err;
    int idx;

    // 文字列置換
    p = str;
    idx = 0;
    do {
        err = regexec(&rb, p + idx, 1, &rm, 0);
        if (!err) {
            if (rm.rm_so > 0) {
                char buf[1024];
                memset(buf, '\0', sizeof(buf));
                strncpy(buf, p + idx, rm.rm_so);
                fprintf(stdout, "%s", buf);
            }
            fprintf(stdout, ".");
            idx += rm.rm_eo;
        }
    } while (!err);
    fprintf(stdout, "%s\n", p + idx);

    // 文字列分割
    p = str;
    idx = 0;
    do {
        err = regexec(&rb, p + idx, 1, &rm, 0);
        if (!err) {
            char buf[1024];
            memset(buf, '\0', sizeof(buf));
            strncpy(buf, p + idx + rm.rm_so, rm.rm_eo - rm.rm_so);
            fprintf(stdout, "%s\n", buf);
            idx += rm.rm_eo;
        }
    } while (!err);

    regfree(&rb);

    return 0;
}

C++

#include <iostream>
#include <locale>
#include <string>

using namespace std;

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");
    wcout.imbue(locale("japanese"));

    wstring str;
    getline(wcin, str);

    // 文字列置換(ごまかし)
    //   regex_matchが完全マッチにしか対応してなくて使えないので。
    for (int i = 0; i < str.length(); ++i) {
        wcout << L".";
    }
    wcout << endl;

    // 文字列分割(ごまかし)
    //   regex_matchが完全マッチにしか対応してなくて使えないので。
    for (int i = 0; i < str.length(); ++i) {
        wcout << str[i] << endl;
    }

    return EXIT_SUCCESS;
}

(2017/12/05追記)

yum install boost-devel」してBoostのライブラリを使うようにしたらまともに動いてくれたので、そのソースコードを追記。コンパイル時に「-lboost_regex」が必要。

#include <iostream>
#include <locale>
#include <string>
#include <boost/regex.hpp>

using namespace std;

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");
    wcout.imbue(locale("japanese"));

    wstring str;
    getline(wcin, str);

    boost::wregex re(L".");

    // 文字列置換
    wcout << boost::regex_replace(str, re, L".") << endl;

    // 文字列分割
    boost::wsmatch sm;
    wstring::const_iterator start = str.begin();
    wstring::const_iterator end = str.end();
    int offset = 0;
    while (boost::regex_search(start + offset, end, sm, re)) {
        size_t idx = 0;
        for (int i = 0; i < sm.length(idx); ++i) {
            wcout << str[sm.position(idx) + offset + i];
        }
        wcout << endl;
        offset += sm.position(idx) + sm.length(idx);
    }

    return EXIT_SUCCESS;
}

PHP

<?php

$str = file_get_contents('php://stdin');
$str = trim($str);

mb_regex_encoding('UTF-8');

// 文字列置換
//   mb_xxx系ではereg版しかない
//   パターンの書き方がpreg系の関数と違うことに注意・・
echo mb_ereg_replace('.', '.', $str) . PHP_EOL;

// 文字列分割
//   mb_xxx系ではereg版しかない
//   パターンの書き方がpreg系の関数と違うことに注意・・
$tmp = $str;
do {
    mb_ereg_search_init($tmp, '.');
    $range = mb_ereg_search_pos();
    if ($range !== false) {
        echo substr($tmp, $range[0], $range[1]) . PHP_EOL;
        $tmp = substr($tmp, $range[1]);
    }
} while ($tmp !== false && $range !== false);

Python 2

# -*- coding: UTF-8 -*-
import sys
import re

s = sys.stdin.readline()
ustr = unicode(s, 'UTF-8')
ustr = ustr.replace('\n', '')
ustr = ustr.replace('\r', '')

# 文字列置換
print re.sub(r'.', '.', ustr)

# 文字列分割
for i in range(0, len(ustr)):
    print ustr[i].encode('UTF-8')

Python 3

# -*- coding: UTF-8 -*-
import sys
import re

b = sys.stdin.buffer.readline()
s = str(b, 'UTF-8')
s = s.replace('\n', '')
s = s.replace('\r', '')

# 文字列置換
print(re.sub(r'.', '.', s))

# 文字列分割
for i in range(0, len(s)):
    print(s[i])

Ruby

str = STDIN.gets
str.chomp!()

# 文字列置換
print str.gsub(/./, '.'),"\n"

# 文字列分割
for i in 0...str.size()
    print str[i],"\n"
end

Perl

use Encode;

my $str = readline(STDIN);
chomp($str);

# 文字列置換
my $ustr = decode('UTF-8', $str);
my $tmp = $ustr;
$tmp =~ s/././g;
print $tmp,"\n";

# 文字列分割
my $tmp = $ustr;
for (my $i = 0; $i < length($ustr); ++$i) {
    print encode('UTF-8', substr($tmp, $i, 1)),"\n";
}

Go

package main

import (
    "fmt"
    "os"
    "io"
    "bufio"
    "regexp"
)

func ReadLine(reader *bufio.Reader) (s string, err error) {
    prefix := false
    buf := make([]byte, 0)
    var line []byte
    for {
        line, prefix, err = reader.ReadLine()
        if err == io.EOF {
            return
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s = string(buf)
        return
    }
}

func main() {
    stdin := bufio.NewReader(os.Stdin)
    s, _ := ReadLine(stdin)

    ss := regexp.MustCompile(`.`).ReplaceAllString(s, ".")
    fmt.Println(ss)

    runes := []rune(s)
    for i := 0; i < len(runes); i += 1 {
        fmt.Println(string(runes[i]))
    }
}

bash

#! /bin/bash

IFS= read s

echo "${s}" | sed -e 's/././g'

echo -n "${s}" | sed -e 's/\(.\)/\1\n/g'

各言語でUTF-8バイト列からバイト数と文字数を取ってみる

各言語でUTF-8のバイト列を読み込み、バイト数とUnicodeでの文字数を取得してみたメモ。

要件は以下の通り。

  • 標準入力から、文字列が1行だけ入力される。
  • 標準出力に、以下の3つを改行区切りで出力する。
    • 文字列の総バイト数
    • 長さ
    • 入力文字列そのもの

環境

手元にあるものということで、環境は以下のものに限定する。

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String s = in.readLine();
            out.println(s.getBytes().length);
            out.println(s.codePointCount(0, s.length()));
            out.println(s);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Javaは、歴史的経緯から、サロゲートペアをcharで表すことができないので、文字数を知りたいときにString.length()を呼んではダメ。

C

#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <wchar.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    char str[1024];

    fgets(str, sizeof(str), stdin);
    while (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r') {
        str[strlen(str) - 1] = '\0';
    }

    fprintf(stdout, "%d\n", strlen(str));

    wchar_t buf[1024];
    const char* p = str;
    mbsrtowcs(buf, &p, sizeof(buf), NULL);
    fprintf(stdout, "%d\n", wcslen(buf));

    fprintf(stdout, "%s\n", str);

    return 0;
}

C++

#include <iostream>
#include <cwchar>
#include <clocale>
#include <string>
#include <cstring>

using namespace std;

int main(int argc, char** argv) {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    wstring str;
    getline(wcin, str);

    char cbuf[1024];
    wcstombs(cbuf, str.c_str(), sizeof(cbuf));
    wcout << strlen(cbuf) << endl;

    wcout << str.length() << endl;

    wcout << str << endl;

    return EXIT_SUCCESS;
}

PHP

<?php

$str = file_get_contents('php://stdin');
$str = trim($str);

echo strlen($str) . PHP_EOL;

echo mb_strlen($str, 'UTF-8') . PHP_EOL;

echo $str . PHP_EOL;

Python 2

import sys

s = sys.stdin.readline()
s = s.replace('\n', '')
s = s.replace('\r', '')

print len(s)

ustr = unicode(s, 'UTF-8')
print len(ustr)

print s

Python 3

import sys

b = sys.stdin.buffer.readline()
s = str(b, 'UTF-8')
s = s.replace('\n', '')
s = s.replace('\r', '')
b = bytes(s, 'UTF-8')

print(len(b))

print(len(s))

print(s)

Ruby

str = STDIN.gets
str.chomp!()

print str.bytes().size(),"\n"

print str.size(),"\n"

print str,"\n"

Perl

use Encode;

my $str = readline(STDIN);
chomp($str);

print length($str),"\n";

my $b = $str;
$b = decode('UTF-8', $b);
print length($b),"\n";

print $str,"\n";

Go

package main

import (
    "fmt"
    "os"
    "io"
    "bufio"
)

func ReadLine(reader *bufio.Reader) (s string, err error) {
    prefix := false
    buf := make([]byte, 0)
    var line []byte
    for {
        line, prefix, err = reader.ReadLine()
        if err == io.EOF {
            return
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s = string(buf)
        return
    }
}

func main() {
    stdin := bufio.NewReader(os.Stdin)
    s, _ := ReadLine(stdin)

    fmt.Println(len(s))

    runes := []rune(s)
    fmt.Println(len(runes))

    fmt.Println(s)
}

bash

#! /bin/bash

IFS= read s

echo -n "${s}" | wc -c

echo ${#s}

echo "${s}"

まさか、サロゲートペアを含む文字列の文字数をシェルスクリプトでちゃんと取れるとは思ってなかった。