HHeLiBeXの日記 正道編

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

各言語でbase64エンコード/デコード

手元にある各言語で、base64エンコード/デコードをしてみたメモ。

要件は以下の通り。

  • 標準入力は以下の通り構成される
    • 1行目はASCII文字(空白文字を含む)からなる文字列が与えられる
    • 2行目はbase64エンコードされた文字列が与えられる
    • 3行目以降はMIME形式に従ってbase64エンコードされた文字列が与えられる
  • 入力として与えられる「base64エンコードされた文字列」をbase64デコードしても、得られるのはASCII文字(空白文字を含む)のみからなる文字列とする
  • 入力される各行の長さは不明とする(つまり上限設定なし)
  • 入力の不正チェックは不要(上記仕様に従った入力が必ず与えられるとしてよい)
  • 標準出力には以下を出力する
    • 入力の1行目をMIME形式に従ってbase64エンコードした結果の文字列(複数行にわたることがある)
    • 入力の1行目を改行無しでbase64エンコードした結果の文字列
    • 入力の2行目をbase64デコードした結果の文字列
    • 入力の3行目以降をbase64デコードした結果の文字列
    • 各出力の末尾には改行コードを入れること

環境

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

なお、以下の言語は自前実装しなきゃならなくて辛いので割愛。

入力

od -cした結果で示す。

0000000   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o   p
0000020   q   r   s   t   u   v   w   x   y   z   A   B   C   D   E   F
0000040   G   H   I   J   K   L   M   N   O   P   Q   R   S   T   U   V
0000060   W   X   Y   Z   0   1   2   3   4   5   6   7   8   9  \n   Y
0000100   W   J   j   Z   G   V   m   Z   2   h   p   a   m   t   s   b
0000120   W   5   v   c   H   F   y   c   3   R   1   d   n   d   4   e
0000140   X   p   B   Q   k   N   E   R   U   Z   H   S   E   l   K   S
0000160   0   x   N   T   k   9   Q   U   V   J   T   V   F   V   W   V
0000200   1   h   Z   W   j   A   x   M   j   M   0   N   T   Y   3   O
0000220   D   k   =  \n   Y   W   J   j   Z   G   V   m   Z   2   h   p
0000240   a   m   t   s   b   W   5   v   c   H   F   y   c   3   R   1
0000260   d   n   d   4   e   X   p   B   Q   k   N   E   R   U   Z   H
0000300   S   E   l   K   S   0   x   N   T   k   9   Q   U   V   J   T
0000320   V   F   V   W   V   1   h   Z   W   j   A   x   M   j   M   0
0000340  \r  \n   N   T   Y   3   O   D   k   =  \r  \n
0000354

人間に分かりやすく示すと大体以下のような感じ。

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODk=
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0
NTY3ODk=

ただし3行目以降の各行は「CR LF」で終わっていることに注意。

出力

これもod -cした結果で示すと以下のような感じ。

0000000   Y   W   J   j   Z   G   V   m   Z   2   h   p   a   m   t   s
0000020   b   W   5   v   c   H   F   y   c   3   R   1   d   n   d   4
0000040   e   X   p   B   Q   k   N   E   R   U   Z   H   S   E   l   K
0000060   S   0   x   N   T   k   9   Q   U   V   J   T   V   F   V   W
0000100   V   1   h   Z   W   j   A   x   M   j   M   0  \r  \n   N   T
0000120   Y   3   O   D   k   =  \r  \n   Y   W   J   j   Z   G   V   m
0000140   Z   2   h   p   a   m   t   s   b   W   5   v   c   H   F   y
0000160   c   3   R   1   d   n   d   4   e   X   p   B   Q   k   N   E
0000200   R   U   Z   H   S   E   l   K   S   0   x   N   T   k   9   Q
0000220   U   V   J   T   V   F   V   W   V   1   h   Z   W   j   A   x
0000240   M   j   M   0   N   T   Y   3   O   D   k   =  \n   a   b   c
0000260   d   e   f   g   h   i   j   k   l   m   n   o   p   q   r   s
0000300   t   u   v   w   x   y   z   A   B   C   D   E   F   G   H   I
0000320   J   K   L   M   N   O   P   Q   R   S   T   U   V   W   X   Y
0000340   Z   0   1   2   3   4   5   6   7   8   9  \n   a   b   c   d
0000360   e   f   g   h   i   j   k   l   m   n   o   p   q   r   s   t
0000400   u   v   w   x   y   z   A   B   C   D   E   F   G   H   I   J
0000420   K   L   M   N   O   P   Q   R   S   T   U   V   W   X   Y   Z
0000440   0   1   2   3   4   5   6   7   8   9  \n
0000453

人間に分かりやすく示すと大体以下のような感じ。

YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0
NTY3ODk=
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODk=
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

ただし、上の例でいうところの1~2行目の末尾は「CR LF」で終わっていることに注意。

Java

import java.io.IOException;
import java.io.PrintStream;
import java.util.Base64;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 今回はASCII文字しかない前提なので手抜きでScannerを使っている
        try (Scanner sc = new Scanner(System.in);
            PrintStream out = new PrintStream(System.out)
        ) {
            String line1 = sc.nextLine();

            // MIME format(encode)
            Base64.Encoder mimeEncoder = Base64.getMimeEncoder(/*76, "\n".getBytes()*/);
            out.write(mimeEncoder.encode(line1.getBytes()));
            out.print("\r\n"); // MimeEncoderでは最後の行にCR LFを付加しないので、その分を補う

            // no wrap format(encode)
            Base64.Encoder encoder = Base64.getEncoder();
            out.write(encoder.encode(line1.getBytes()));
            out.println();

            // no wrap format(decode)
            String line21 = sc.nextLine();
            Base64.Decoder decoder = Base64.getDecoder();
            out.write(decoder.decode(line21));
            out.println();

            // MIME format(decode)
            Base64.Decoder mimeDecoder = Base64.getMimeDecoder();
            StringBuilder sb = new StringBuilder();
            while (sc.hasNextLine()) {
                sb.append(sc.nextLine());
                sb.append("\r\n"); // わざとMIME形式を復元してみる
            }
            String line22 = sb.toString();
            out.write(mimeDecoder.decode(line22));
            out.println();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PHP

<?php

$lines = file('php://stdin');

// MIME format(encode)
$line1 = array_shift($lines);
$line1 = preg_replace("/[\r\n]/", "", $line1);
print chunk_split(base64_encode($line1)/*, 76, "\n"*/);

// no wrap format(encode)
print base64_encode($line1) . PHP_EOL;

// no wrap format(decode)
$line21 = array_shift($lines);
$line21 = preg_replace("/[\r\n]/", "", $line21);
print base64_decode($line21) . PHP_EOL;

// MIME format(decode)
$line22 = implode('', $lines);
$line22 = preg_replace("/[\r\n]/", "", $line22);
print base64_decode($line22) . PHP_EOL;

Python 2

import sys
import base64

line1 = sys.stdin.readline()
line1 = line1[0:len(line1) - 1] # remove LF
res1 = base64.b64encode(line1)

# MIME format(encode)
for i in range(0, len(res1), 76):
    print res1[i:i + 76] + "\r"

# no wrap format(encode)
print res1

# no wrap format(decode)
line21 = sys.stdin.readline()
print base64.b64decode(line21)

# MIME format(decode)
line22 = ''
while True:
    tmp = sys.stdin.readline()
    if tmp == '':
        break
    line22 = line22 + tmp
print base64.b64decode(line22)

Python 3

import sys
import base64

line1 = sys.stdin.buffer.readline()
line1 = line1[0:len(line1) - 1] # remove LF
res1 = base64.b64encode(line1)

# MIME format(encode)
for i in range(0, len(res1), 76):
    print(res1[i:i + 76].decode() + "\r")

# no wrap format(encode)
print(res1.decode())

# no wrap format(decode)
line21 = sys.stdin.buffer.readline()
print(base64.b64decode(line21).decode())

# MIME format(decode)
line22 = b''
while True:
    tmp = sys.stdin.buffer.readline()
    if tmp == b'':
        break
    line22 = line22 + tmp
print(base64.b64decode(line22).decode())

Ruby

require 'base64'

line1 = STDIN.gets
line1 = line1[0, line1.length() - 1]
res1 = Base64.strict_encode64(line1)

# MIME format(encode)
i = 0
while i < res1.length()
    print res1[i, 76],"\r\n"
    i += 76
end

# no wrap format(encode)
print res1,"\n"

# no wrap format(decode)
line21 = STDIN.gets
line21 = line21[0, line21.length() - 1]
print Base64.strict_decode64(line21),"\n"

# MIME format(decode)
line22 = ""
while tmp = STDIN.gets
    line22 = line22 + tmp
end
print Base64.decode64(line22),"\n"

Perl

use MIME::Base64;

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

# MIME format(encode)
print encode_base64($line1, "\r\n");

# no wrap format(encode)
print encode_base64($line1, ""),"\n";

# no wrap format(decode)
my $line21 = readline(STDIN);
chomp($line21);
print decode_base64($line21),"\n";

# MIME format(decode)
my $line22 = "";
while (my $tmp = readline(STDIN)) {
    $line22 = $line22 . $tmp;
}
print decode_base64($line22),"\n";

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "encoding/base64"
)

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

func main() {
    stdin := bufio.NewReader(os.Stdin)

    line1, err := ReadLine(stdin)
    if err == io.EOF {
        panic(err)
    }
    len1 := base64.StdEncoding.EncodedLen(len(line1))
    res1 := make([]byte, len1);
    base64.StdEncoding.Encode(res1, line1)

    // MIME format(encode)
    for i := 0; i < len(res1); i += 76 {
        end := i + 76
        if end > len(res1) {
            end = len(res1)
        }
        fmt.Println(string(res1[i:end]) + "\r")
    }

    // no wrap format(encode)
    fmt.Println(string(res1))

    // no wrap format(decode)
    line21, err := ReadLine(stdin)
    if err == io.EOF {
        panic(err)
    }
    // base64.StdEncoding.DecodedLenが返す値はあくまで最大値なので、
    // パディングがあったりすると、それよりも短くなることがあるので、
    // Decodeしたときの返り値で実際の長さを取得して切り詰めてやる必要がある。
    len21 := base64.StdEncoding.DecodedLen(len(line21))
    res21 := make([]byte, len21)
    resLen21, _ := base64.StdEncoding.Decode(res21, line21)
    fmt.Println(string(res21[0:resLen21]))

    // MIME format(decode)
    line22 := make([]byte, 0)
    for {
        tmp, err := ReadLine(stdin)
        if err == io.EOF {
            break
        }
        line22 = append(line22, tmp...)
    }
    len22 := base64.StdEncoding.DecodedLen(len(line22))
    res22 := make([]byte, len22)
    resLen22, _ := base64.StdEncoding.Decode(res22, line22)
    fmt.Println(string(res22[0:resLen22]))
}

bash

#! /bin/bash

IFS= read -r line1

# MIME format(encode)
#     base64コマンドは"\n"を付加するので、sedで"\r\n"に変えてやる
echo -n "${line1}" | base64 | sed -e 's/$/\r/'

# no wrap format(encode)
echo -n "${line1}" | base64 -w 0
echo

# no wrap format(decode)
IFS= read -r line21
echo -n "${line21}" | base64 -d
echo

# MIME format(decode)
while IFS=$'\r' read -r tmp ; do
    line22="${line22}${tmp}"
done
echo -n "${line22}" | base64 -d
echo
  • man base64

各言語での1行読み込み時に得られる文字列の違い

手元にある各言語で、「1行」を読み込んだ時の文字列の違い、とりわけ改行コードの扱いについて調べてみたメモ。

環境

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

繰り返すが、環境は「CentOS 7」。ということで、改行コードは「LF」(一般に"\n"で表されることが多い)が標準となっている環境。

入力ファイル

od -cした結果で示す。

0000000   h   o   g   e  \r  \n   f   o   o  \r   b   a   r  \n   e   n
0000020   d
0000021

改行コードとして一般的に知られている3つのパターンを含み、かつファイルの末尾には改行コードが無いというファイルである。

  • "hoge"の後には、Windows系のOSで標準とされる改行コード「CR LF」
  • "foo"の後には、昔の(?)MacOS系のOSで標準とされる改行コード「CR」
  • "bar"の後には、UNIX/Linux系のOSで標準とされる改行コード「LF」
  • "end"の後には、いきなりEOF

プログラムでの処理

各言語で書くプログラムは、入力の末端に到達したと判定されるまで「1行」を読み込み、先頭にスラッシュ('/')を付加して「1行」の区切りが分かるようにしたうえでそのまま出力。その際に追加の改行コードが出力されないような方法で出力する。

出力パターン

答えを先に言ってしまう形になるが、試したケースで計6パターンの出力が得られた。 以下のような感じである。

  • パターン1:いわゆる改行コードはすべて認識し、得られる文字列からは取り除かれる
0000000   /   h   o   g   e   /   f   o   o   /   b   a   r   /   e   n
0000020   d
0000021
  • パターン2:「LF」が改行コードと認識され、かつ得られる文字列に改行コードが含まれる
0000000   /   h   o   g   e  \r  \n   /   f   o   o  \r   b   a   r  \n
0000020   /   e   n   d
0000024
  • パターン3:「LF」が改行コードと認識され、得られる文字列からは取り除かれる
0000000   /   h   o   g   e  \r   /   f   o   o  \r   b   a   r   /   e
0000020   n   d
0000022
  • パターン4:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれる
0000000   /   h   o   g   e   /   f   o   o  \r   b   a   r   /   e   n
0000020   d
0000021
  • パターン5:「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
0000000   /   h   o   g   e  \r   /   f   o   o  \r   b   a   r
0000016
  • パターン6:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
0000000   /   h   o   g   e   /   f   o   o  \r   b   a   r
0000015

Java

念のため、BufferedReader版とScanner版の両方を試した。

BufferedReader版

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 buf;
            while ((buf = in.readLine()) != null) {
                out.print('/');
                out.print(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Scanner版

import java.io.PrintWriter;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            while (in.hasNextLine()) {
                String buf = in.nextLine();
                out.print('/');
                out.print(buf);
            }
        }
    }
}

C

#include <stdio.h>

int main(int argc, char** argv) {
    char buf[1024];

    while (fgets(buf, sizeof(buf), stdin)) {
        printf("/");
        printf("%s", buf);
    }

    return 0;
}

C++

念のため、cin.getlineとstringヘッダのgetlineの両方を試した。

cin.getline

#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    char buf[1024];

    while (cin.getline(buf, sizeof(buf))) {
        cout << '/';
        cout << buf;
    }

    return EXIT_SUCCESS;
}

stringヘッダのgetline

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char** argv) {
    string buf;

    while (getline(cin, buf)) {
        cout << '/';
        cout << buf;
    }

    return EXIT_SUCCESS;
}

PHP

<?php

$lines = file('php://stdin');

foreach ($lines as $line) {
    echo '/' . $line;
}

Python 2

import sys

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    sys.stdout.write('/')
    sys.stdout.write(line)

print文を使うと、末尾に勝手に改行コードが挿入されるので、sys.stdout.writeを使う。

Python 3

import sys

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    sys.stdout.write('/')
    sys.stdout.write(line)

print文を使うと、末尾に勝手に改行コードが挿入されるので、sys.stdout.writeを使う。

Ruby

while line = STDIN.gets
    print '/'
    print line
end

Perl

while (my $line = readline(STDIN)) {
    print '/';
    print $line;
}

Go

package main

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

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

func main() {
    stdin := bufio.NewReader(os.Stdin)

    for {
        line, err := ReadLine(stdin)
        if err == io.EOF {
            break
        }

        fmt.Print("/")
        fmt.Print(line)
    }
}

bash

環境変数IFSの値によって挙動が変わるだろうということで、3パターン試した。

IFS=$'\n'

#! /bin/bash

while IFS=$'\n' read line ; do
    echo -n /
    echo -n ${line}
done

IFS=$'\r'

#! /bin/bash

while IFS=$'\r' read line ; do
    echo -n /
    echo -n ${line}
done

IFS=

#! /bin/bash

while IFS= read line ; do
    echo -n /
    echo -n ${line}
done

Awk

{
    printf("/");
    printf("%s", $0);
}

sed

言語なのかという突っ込みがありそうだが、行処理なら任せろというツールの一つであるので外せないということで。

s|^|/|

行頭にスラッシュを挿入するという単純な正規表現

まとめると‥

  • パターン1:いわゆる改行コードはすべて認識し、得られる文字列からは取り除かれる
  • パターン2:「LF」が改行コードと認識され、かつ得られる文字列に改行コードが含まれる
  • パターン3:「LF」が改行コードと認識され、得られる文字列からは取り除かれる
  • パターン4:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれる
  • パターン5:「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
  • パターン6:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
言語 1 2 3 4 5 6
Java(BufferedReader) O
Java(Scanner) O
C O
C++(cin.getline) O
C++(getline) O
PHP O
Python 2 O
Python 3 O
Ruby O
Perl O
Go O
bash(IFS=$'\n') O
bash(IFS=$'\r') O
bash(IFS=) O
Awk O
sed O

bashで「end」が出力されないというのは予想外だった。

bashを除くと、Go言語がちょっと特殊。

各言語でMap

手元にある各言語で、Map構造を使うプログラムを書いてみようと思ったメモ。

Map構造と言っても、「連想配列」がその言語にあればそれを使う。

要件は以下の通り。

  • 標準入力は、以下のデータから構成される
    • 1行目に、空白区切りのキーワードがいくつか入力される
    • 2行目以降は、1行に空白区切りの文字列が2つ書かれた行が入力される
  • 2行目以降の2つの文字列をkey-valueとしてmapに放り込む
  • 不正入力のチェックは不要とする
  • 標準出力には、1行目で指定されたキーワードに対応する値を改行区切りで出力する

環境

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

なお、そもそもが「使ってみようシリーズ」だったことを思い出し、今回はC言語bashにはお休みしてもらうことにした。

入力ファイルの例

  • 001.txt
One Six Two Five
Zero 000
One 111
Two 222
Three 333
Four 444
Five 555
Six 666
Seven 777
Eight 888
Nine 999
Eight 8
Five 5
Four 4
Nine 9
One 1
Seven 7
Six 6
Three 3
Two 2
Zero 0

期待される出力の例

  • 001.txt
1
6
2
5

Java

Javaでは、入力の処理を2パターン書いてみた。

BufferedReader版

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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 buf;

            buf = in.readLine();
            List<String> keywords = Arrays.asList(buf.split(" "));

            Map<String, String> map = new HashMap<>();

            while ((buf = in.readLine()) != null) {
                String[] tokens = buf.split(" ");
                map.put(tokens[0], tokens[1]);
            }

            for (String keyword : keywords) {
                if (map.containsKey(keyword)) {
                    out.println(map.get(keyword));
                } else {
                    out.println("");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Scanner版

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf = in.nextLine();
            List<String> keywords = Arrays.asList(buf.split(" "));

            Map<String, String> map = new HashMap<>();

            while (in.hasNext()) {
                String key = in.next();
                String value = in.next();
                map.put(key, value);
            }

            for (String keyword : keywords) {
                if (map.containsKey(keyword)) {
                    out.println(map.get(keyword));
                } else {
                    out.println("");
                }
            }
        }
    }
}

C++

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <sstream>

using namespace std;

int main(int argc, char** argv) {
    // 1行目を読み込んで、空白区切りでリストに放り込む
    string line;
    getline(cin, line);

    stringstream ss(line);

    vector<string> keywords;
    string keyword;
    while (getline(ss, keyword, ' ')) {
        keywords.push_back(keyword);
    }

    // 2行目以降を読み込んで、マップに放り込む
    map<string, string> map;
    string key;
    string value;
    while (cin >> key >> value) {
        map[key] = value;
    }

    // 指定されたキーワードに対応する値を出力する
    for (vector<string>::iterator it = keywords.begin(); it != keywords.end(); ++it) {
        cout << map[*it] << endl;
    }

    return EXIT_SUCCESS;
}

PHP

<?php

$lines = file('php://stdin');

$keywords = split(' ', trim($lines[0]));
array_shift($lines);

$map = array();
foreach ($lines as $line) {
    $line = trim($line);
    $tmp = split(' ', $line);
    $map[$tmp[0]] = $tmp[1];
}

foreach ($keywords as $keyword) {
    if (array_key_exists($keyword, $map)) {
        printf("%s\n", $map[$keyword]);
    } else {
        printf("\n");
    }
}

Python 2

import sys

line = sys.stdin.readline()
keywords = line.split()

map = {}

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    tmp = line.split()
    map[tmp[0]] = tmp[1]

for keyword in keywords:
    if keyword in map:
        print map[keyword]
    else:
        print ""

Python 3

import sys

line = sys.stdin.readline()
keywords = line.split()

map = {}

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    tmp = line.split()
    map[tmp[0]] = tmp[1]

for keyword in keywords:
    if keyword in map:
        print(map[keyword])
    else:
        print("")

Ruby

line = STDIN.gets
keywords = line.split()

map = {}

while line = STDIN.gets
    tmp = line.split()
    map[tmp[0]] = tmp[1]
end

for keyword in keywords
    print map[keyword],"\n"
end

Perl

my $line = readline(STDIN);
chomp($line);
my @keywords = split(/ /, $line);

my %map = ();
while ($line = readline(STDIN)) {
    chomp($line);
    my @tmp = split(/ /, $line);
    $map{@tmp[0]} = @tmp[1];
}

foreach my $keyword(@keywords) {
    print "$map{$keyword}\n";
}

Go

package main

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

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

func main() {
    stdin := bufio.NewReader(os.Stdin)

    line, err := ReadLine(stdin)
    if err == io.EOF {
        panic(err)
    }
    keywords := strings.Split(line, " ")

    aMap := map[string]string{}
    for {
        line, err := ReadLine(stdin)
        if err == io.EOF {
            break
        }
        tmp := strings.Split(line, " ")
        aMap[tmp[0]] = tmp[1]
    }

    for i := 0; i < len(keywords); i += 1 {
        value, exists := aMap[keywords[i]]
        if exists {
            fmt.Println(value)
        } else {
            fmt.Println()
        }
    }
}

ついに、指定されたReaderから1行を読み込む関数を自作した‥

各言語で日時文字列の解析

手元にある各言語で、標準入力から日時文字列を読み込んで、標準出力にUNIX TIME値を吐き出すプログラムを書いてみようと思ったメモ。

要件は以下の通り。

  • 入力される日時文字列は1つのみ
    • 不正入力のチェックは不要とする
  • OS等のタイムゾーンJST
  • 対応するUNIX TIME値を出力
    • 例えば、「2017/12/31 12:34:56 +0900」が入力されたら「1514723696」を出力

環境

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

入力ファイルの例

  • 001.txt
2017/12/31 12:34:56 +0900

期待される出力の例

  • 001.txt
1514723696

Java

import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Scanner;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String str = in.nextLine();

            DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss ZZZZZ");
            Date date = df.parse(str);

            out.println(date.getTime() / 1000);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

C

#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>

int main(int argc, char** argv) {
    char str[32];
    fgets(str, sizeof(str) - 1, stdin);

    struct tm timeinfo;
    strptime(str, "%Y/%m/%d %H:%M:%S %z", &timeinfo);
    long second = mktime(&timeinfo);

    printf("%d\n", second);

    return 0;
}

strptimeを使ったソースを警告も出さないようにコンパイルするためには、マクロ定数を定義してやる必要がある。

上記を見ると、%zについては、「glibc での注意」として書いてあるが、「サポートさせようとしているが、多くの場合、tmフィールドは変更されない」らしい、つまり無視される、と。 実際、「+0000」を与えて試してみたら、見事に無視されて、tmフィールドのタイムゾーン情報には"JST"が入っていた・・・

C++

#include <ctime>
#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    char str[32];
    cin.getline(str, sizeof(str));

    struct tm timeinfo;
    strptime(str, "%Y/%m/%d %H:%M:%S %z", &timeinfo);
    long second = mktime(&timeinfo);

    cout << second << endl;

    return EXIT_SUCCESS;
}

上記を見ると、%zについては、「glibc での注意」として書いてあるが、「サポートさせようとしているが、多くの場合、tmフィールドは変更されない」らしい、つまり無視される、と。 実際、「+0000」を与えて試してみたら、見事に無視されて、tmフィールドのタイムゾーン情報には"JST"が入っていた・・・

PHP

(2017/12/07)DateTimeクラス版を書いてみたので追記。大して変わらないけど(ぉ

strtotime()版

<?php

$lines = file('php://stdin');
$str = $lines[0];

date_default_timezone_set("Asia/Tokyo");
$second = strtotime($str);

printf("%d\n", $second);

DateTimeクラス版

<?php

$lines = file('php://stdin');
$str = $lines[0];

date_default_timezone_set("Asia/Tokyo");
$dt = new DateTime($str);
$second = $dt->getTimestamp();

printf("%d\n", $second);

Python 2

import sys
import re
from datetime import datetime
import time

str = sys.stdin.readline()
str = re.sub(r' [+][0-9][0-9][0-9][0-9][\r]*\n', "", str)

dt = datetime.strptime(str, "%Y/%m/%d %H:%M:%S")
second = int(time.mktime(dt.timetuple()))

print second

最初、%zを入れたら、ValueError: 'z' is a bad directive in format '%Y/%m/%d %H:%M:%S %z'って怒られた‥

次に、%zを外したら、ValueError: unconverted data remains: +0900って怒られた‥

なので、時差を指定している部分を正規表現による文字列置換で削除‥当然、JST扱いされるので、「+0000」を与えたテストケースは通らない‥

Python 3

import sys
import re
from datetime import datetime
import time

str = sys.stdin.readline()
str = re.sub(r'[\r]*\n', "", str)

dt = datetime.strptime(str, "%Y/%m/%d %H:%M:%S %z")
second = int(dt.timestamp())

print(second)

Ruby

require 'time'

str = STDIN.gets

second = Time.parse(str).to_i

print second,"\n"

Perl

DateTime::Format::Strptime版

use DateTime::Format::Strptime;

my $str = readline(STDIN);

my $strp = DateTime::Format::Strptime->new(
    pattern => "%Y/%m/%d %H:%M:%S %z"
);
my $second = $strp->parse_datetime($str)->epoch;

print $second,"\n";

これをやるには、「yum install perl-DateTime-Format-Strptime」をしておく必要がある。

Time::Piece版

use Time::Piece;

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

my $dt = Time::Piece->strptime($str, "%Y/%m/%d %H:%M:%S %z");
my $second = $dt->epoch;

print $second,"\n";

これをやるには、「yum install perl-Time-Piece」をしておく必要がある。

Go

package main

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

func main() {
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    str := ""
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        str = string(buf)
        break
    }

    t, err := time.Parse("2006/01/02 15:04:05 -0700", str)
    if err != nil {
        panic(err)
    }

    fmt.Println(t.Unix())
}

Go言語の日時のフォーマット指定子の仕様は何度見ても謎だ‥

bash

#! /bin/bash

IFS=$'\n' read str

date -d "${str}" +"%s"
  • man date(ぉ

各言語でdate format

手元にある各言語で、標準入力からUNIX TIME値を読み込んで、標準出力にフォーマットされた日時文字列を吐き出すプログラムを書いてみようと思ったメモ。

要件は以下の通り。

  • 入力されるUNIX TIME値は1つのみ
    • 不正入力のチェックは不要とする
  • タイムゾーンJST
  • 対応する日時文字列を出力
    • 例えば、「1514723696」が入力されたら「2017/12/31 12:34:56 +0900」を出力

環境

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

入力ファイルの例

  • 001.txt
1514723696

期待される出力の例

  • 001.txt
2017/12/31 12:34:56 +0900

Java

import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            long millisecond = in.nextLong() * 1000L;

            DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss ZZZZZ");
            String str = df.format(millisecond);

            out.println(str);
        }
    }
}

C

#include <stdio.h>
#include <time.h>

int main(int argc, char** argv) {
    time_t second;
    scanf("%ld", &second);

    char str[32];
    struct tm* timeinfo;
    timeinfo = localtime(&second);
    strftime(str, sizeof(str), "%Y/%m/%d %H:%M:%S %z", timeinfo);

    printf("%s\n", str);

    return 0;
}

C++

#include <ctime>
#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    time_t second;

    cin >> second;

    char str[32];
    struct tm* timeinfo;
    timeinfo = localtime(&second);
    strftime(str, sizeof(str), "%Y/%m/%d %H:%M:%S %z", timeinfo);

    cout << str << endl;

    return EXIT_SUCCESS;
}

PHP

(2017/12/07) DateTimeクラスを使ったバージョンも書いてみたので追記。大して変わらないけど(ぉ

date()版

<?php

$lines = file('php://stdin');
$second = (int)$lines[0];

date_default_timezone_set("Asia/Tokyo");
printf("%s\n", date("Y/m/d H:i:s O", $second));

DateTimeクラス版

<?php

$lines = file('php://stdin');
$second = (int)$lines[0];

date_default_timezone_set("Asia/Tokyo");
$dt = new DateTime();
$dt->setTimestamp($second);
printf("%s\n", $dt->format("Y/m/d H:i:s O"));

Python 2

import sys
import pytz
from datetime import datetime

line = sys.stdin.readline()
second = int(line)

dt = datetime.fromtimestamp(second)
dt = pytz.timezone('Asia/Tokyo').localize(dt)
str = dt.strftime("%Y/%m/%d %H:%M:%S %z")

print str

これをやるには、「yum pytz」でpytzモジュールをインストールしておく必要がある。

Python 3

import sys
import pytz
from datetime import datetime

line = sys.stdin.readline()
second = int(line)

dt = datetime.fromtimestamp(second)
dt = pytz.timezone('Asia/Tokyo').localize(dt)
str = dt.strftime("%Y/%m/%d %H:%M:%S %z")

print(str)

これをやるには、(自前ビルドしたPythonなので)「pip3 install pytz」でpytzモジュールをインストールしておく必要がある。

Ruby

line = STDIN.gets
second = line.to_i

t = Time.at(second)
str = t.strftime("%Y/%m/%d %H:%M:%S %z")

print str,"\n"

Perl

use POSIX 'strftime';

my $line = readline(STDIN);
my $second = $line + 0;

my $str = strftime "%Y/%m/%d %H:%M:%S %z", localtime($second);

print $str,"\n";

Go

package main

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

func main() {
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    second := int64(0)
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s := string(buf)
        tmpSec, err2 := strconv.Atoi(s)
        if err2 != nil {
            panic(err2)
        }
        second = int64(tmpSec)
        break
    }

    t := time.Unix(second, 0)

    fmt.Println(t.Format("2006/01/02 15:04:05 -0700"))
}

Go言語の日時のフォーマット指定子の仕様は謎だ‥

bash

#! /bin/bash

IFS=$'\n' read line
second=$(echo ${line} | sed -e 's/^\([+-]*[0-9][0-9]*\).*/\1/')
second=$((second+0))

date -d "@${second}" +"%Y/%m/%d %H:%M:%S %z"
  • man date(ぉ

各言語でQueue

手元にある各言語で、標準入力からの数値列をQueueに放り込んで、入力と同順になるように標準出力に吐き出すプログラムを書いてみようと思ったメモ。

標準入力から入力される数値列の要件は以下の通り。

  • 1行に1つの数値が書かれている
    • 不正入力のチェックは不要とする
  • いくつの数値が入力されるかは不明(ただし、メモリがあふれることが無い程度に手加減する)
  • 入力される数値は符号付32ビット整数とする

入力をそのまま標準出力に放り出せばいいじゃないかという突っ込みもあるだろうが、Queue構造を扱ってみようというのが今回の目的。

環境

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

入力ファイルの例

  • 001.txt
0
1
-1
256
-256
32768
-32768
2147483647
-2147483648

期待される出力の例

  • 001.txt
0
1
-1
256
-256
32768
-32768
2147483647
-2147483648

Java

import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            Queue<Integer> queue = new ArrayDeque<>();

            while (in.hasNextInt()) {
                queue.offer(in.nextInt());
            }

            while (queue.peek() != null) {
                out.println(queue.poll());
            }
        }
    }
}

Javaには、Queueとして使えるjava.util.Queueの実装クラスがあるので、それを使って簡単に書ける。

C

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

typedef struct {
    int capacity;
    int lastIndex;
    int curIndex;
    int* elemData;
} IntQueue;

IntQueue* IntQueue_new(int capacity) {
    IntQueue* queue = (IntQueue*)calloc(sizeof(IntQueue), 1);
    queue->elemData = (int*)calloc(sizeof(int), capacity);
    queue->capacity = capacity;
    queue->lastIndex = 0;
    queue->curIndex = 0;
    return queue;
}
void IntQueue_destroy(IntQueue* queue) {
    free(queue->elemData);
    free(queue);
}
int IntQueue_isEmpty(IntQueue* queue) {
    return queue->curIndex >= queue->lastIndex;
}
void IntQueue_push(IntQueue* queue, int elem) {
    // 先頭の方の使わなくなった領域をつぶして再利用。
    if (queue->lastIndex + 1 >= queue->capacity && queue->curIndex > 0) {
        if (queue->lastIndex - queue->curIndex > 0) {
            memmove(queue->elemData, &(queue->elemData[queue->curIndex]), sizeof(int) * (queue->lastIndex - queue->curIndex));
        }
        queue->lastIndex -= queue->curIndex;
        queue->curIndex = 0;
    }
    // それでも領域が足りない場合は増やす。
    if (queue->lastIndex + 1 >= queue->capacity) {
        int newCapacity = queue->capacity * 2;
        int* newElemData = (int*)calloc(sizeof(int), newCapacity);
        memcpy(newElemData, &(queue->elemData[queue->curIndex]), sizeof(int) * (queue->lastIndex - queue->curIndex));
        free(queue->elemData);
        queue->elemData = newElemData;
        queue->capacity = newCapacity;
        queue->lastIndex -= queue->curIndex;
        queue->curIndex = 0;
    }

    queue->elemData[(queue->lastIndex)++] = elem;
}
int IntQueue_pop(IntQueue* queue) {
    return queue->elemData[(queue->curIndex)++];
}

int main(int argc, char** argv) {
    IntQueue* queue = IntQueue_new(256);

    int num;
    while (scanf("%ld", &num) == 1) {
        IntQueue_push(queue, num);
    }

    while (!IntQueue_isEmpty(queue)) {
        printf("%d\n", IntQueue_pop(queue));
    }

    IntQueue_destroy(queue);

    return 0;
}

C言語は当然自前実装。

C++

#include <algorithm>
#include <iostream>
#include <queue>

using namespace std;

int main(int argc, char** argv) {
    queue<int> queue;
    int num;

    while (cin >> num) {
        queue.push(num);
    }

    while (!queue.empty()) {
        cout << queue.front() << endl;
        queue.pop();
    }

    return EXIT_SUCCESS;
}

C++にはqueueがあるので、それを使って簡単に書ける。

PHP

<?php

class Queue {
    private $elemData = array();
    public function isEmpty() {
        return count($this->elemData) === 0;
    }
    public function push($value) {
        array_push($this->elemData, $value);
    }
    public function pop() {
        return array_shift($this->elemData);
    }
}

$lines = file('php://stdin');

$queue = new Queue();

foreach ($lines as $line) {
    $queue->push((int)$line);
}

while (!$queue->isEmpty()) {
    printf("%d\n", $queue->pop());
}

PHPには標準ではQueueクラスが無いので書いてみた。単なる配列のラッパー。

ただ、65536個の数値を食わせてみたら、array_shiftで時間を食っているのか、返ってくるのに3分強かかったので、大量データに対しては実用的ではないかも。array_shiftを使わずに、先頭のインデックスを持っておいてインクリメントしていく方がいいのかも。

Python 2

(2017/11/18追記) よくよく調べてみたら、Queueモジュールがあったので、その実装を追記。

自前実装版

import sys

class Queue:
    elemData = []
    def push(self, value):
        self.elemData.append(value)
    def pop(self):
        return self.elemData.pop(0)
    def isEmpty(self):
        return len(self.elemData) == 0

queue = Queue()
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    queue.push(int(line))

while queue.isEmpty() != True:
    print queue.pop()

Python 2ではQueueクラスが見当たらなかったので書いてみた。単なる配列のラッパー。

Queueモジュール版

import sys
import Queue

aQueue = Queue.Queue()
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    aQueue.put(int(line))

while aQueue.empty() != True:
    print aQueue.get()

Python 3

(2017/11/18追記) よくよく調べてみたら、queueモジュールがあったので、その実装を追記。

自前実装版

import sys

class Queue:
    elemData = []
    def push(self, value):
        self.elemData.append(value)
    def pop(self):
        return self.elemData.pop(0)
    def isEmpty(self):
        return len(self.elemData) == 0

queue = Queue()
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    queue.push(int(line))

while queue.isEmpty() != True:
    print(queue.pop())

Python 3ではQueueクラスが見当たらなかったので書いてみた。単なる配列のラッパー。

queueモジュール版

import sys
import queue

aQueue = queue.Queue()
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    aQueue.put(int(line))

while aQueue.empty() != True:
    print(aQueue.get())

Ruby

(2017/11/18追記) よくよく調べてみたら、Queueクラス(Thread::Queueのエイリアス)があったので、その実装を追記。

自前実装版

class Queue
    def initialize
        @elemData = []
    end
    def push(value)
        @elemData.push(value)
    end
    def pop
        @elemData.shift
    end
    def isEmpty
        return @elemData.size == 0
    end
end

queue = Queue.new
while line = STDIN.gets
    num = line.to_i
    queue.push(num)
end

while !queue.isEmpty
    print queue.pop(),"\n"
end

RubyではQueueクラスが見当たらなかったので書いてみた。単なる配列のラッパー。

Thread::Queue版

queue = Queue.new
while line = STDIN.gets
    num = line.to_i
    queue.push(num)
end

while !queue.empty?
    print queue.pop(),"\n"
end

Perl

Perlにはpush/shift関数があるということだが、なんか微妙なので、2パターン書いてみた。

push/shift関数バージョン

my @queue = ();

my $line;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    push @queue, $num;
}

while ($#queue + 1 > 0) {
    my $value = @queue[0];
    shift @queue;
    print "$value\n";
}

Queueモジュールバージョン

Queue.pm

package Queue;

sub new {
    my %self;
    my $class = shift;
    $self->{elemData} = ();
    $self->{cur} = 0;
    $self->{last} = 0;
    return bless $self, $class;
}
sub push {
    my $self = shift;
    my $value = shift;
    $self->{elemData}[$self->{last}] = $value;
    ++$self->{last};
}
sub front {
    my $self = shift;
    return $self->{elemData}[$self->{cur}];
}
sub pop {
    my $self = shift;
    delete $self->{elemData}[$self->{cur}];
    ++$self->{cur};
}
sub isEmpty {
    my $self = shift;
    return $self->{cur} >= $self->{last};
}

1;

Main.pl

require "Queue.pm";

my $queue = Queue->new;

my $line;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    $queue->push($num);
}

while (!$queue->isEmpty()) {
    my $value = $queue->front();
    $queue->pop();
    print "$value\n";
}

Go

listをキュー代わりに直接使うということを考えたが、せっかくなので、Queueパッケージを作ってみた。

src/queue/queue.go

package queue;

import (
    "container/list"
)

type Queue struct {
    elemData *list.List
}

func (s *Queue) Init() *Queue {
    s.elemData = list.New()
    return s
}

func New() *Queue {
    return new(Queue).Init()
}

func (s *Queue) Push(v interface{}) {
    s.elemData.PushBack(v)
}

func (s *Queue) Pop() interface{} {
    e := s.elemData.Front()
    s.elemData.Remove(e)
    return e.Value
}

func (s *Queue) IsEmpty() bool {
    return s.elemData.Len() == 0
}

Main.go

package main

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

func main() {
    aQueue := queue.New()
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s := string(buf)
        num, err2 := strconv.Atoi(s)
        if err2 != nil {
            panic(err2)
        }
        buf = make([]byte, 0, 1024)

        aQueue.Push(num)
    }

    for !aQueue.IsEmpty() {
        fmt.Println(aQueue.Pop())
    }
}

実行するときに、GOPATH=$(pwd) go run Main.goってしてやらないと、カレントディレクトリのソースを参照してくれないのは注意点の一つ。

bash

#! /bin/bash

queue=()

while IFS=$'\n' read line ; do
    num=$(echo ${line} | sed -e 's/^\([+-]*[0-9][0-9]*\).*/\1/')
    num=$((num+0))
    queue=("${queue[@]}" ${num})
done

while [ ${#queue[@]} -gt 0 ]; do
    echo ${queue[0]}
    queue=("${queue[@]:1}")
done

あえて配列を使って書いたが、65536行のデータを食わせたら全然返ってこなかったので、大量のデータを処理する際には素直に他の言語で書いた方が良い。 あと、キューからの取り出しが面倒だったので、他の言語と違って、「末尾に追加」「先頭から削除」という形になっているので注意。

各言語でStack

手元にある各言語で、標準入力からの数値列をStackに放り込んで、入力とは逆順になるように標準出力に吐き出すプログラムを書いてみようと思ったメモ。

標準入力から入力される数値列の要件は以下の通り。

  • 1行に1つの数値が書かれている
    • 不正入力のチェックは不要とする
  • いくつの数値が入力されるかは不明(ただし、メモリがあふれることが無い程度に手加減する)
  • 入力される数値は符号付32ビット整数とする

前回と要件ややることは一緒だが、Stack構造を扱ってみようというのが今回の目的。

環境

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

入力ファイルの例

  • 001.txt
0
1
-1
256
-256
32768
-32768
2147483647
-2147483648

期待される出力の例

  • 001.txt
-2147483648
2147483647
-32768
32768
-256
256
-1
1
0

Java

import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            Deque<Integer> stack = new ArrayDeque<>();

            while (in.hasNextInt()) {
                stack.push(in.nextInt());
            }

            while (stack.peek() != null) {
                out.println(stack.pop());
            }
        }
    }
}

Javaには、Stackとして使えるjava.util.Dequeの実装クラスがあるので、それを使って簡単に書ける。

C

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

typedef struct {
    int capacity;
    int size;
    int* elemData;
} IntStack;

IntStack* IntStack_new(int capacity) {
    IntStack* stack = (IntStack*)calloc(sizeof(IntStack), 1);
    stack->elemData = (int*)calloc(sizeof(int), capacity);
    stack->capacity = capacity;
    stack->size = 0;
    return stack;
}
void IntStack_destroy(IntStack* stack) {
    free(stack->elemData);
    free(stack);
}
int IntStack_isEmpty(IntStack* stack) {
    return stack->size == 0;
}
void IntStack_push(IntStack* stack, int elem) {
    if (stack->size + 1 > stack->capacity) {
        int newCapacity = stack->capacity * 2;
        int* newElemData = (int*)calloc(sizeof(int), newCapacity);
        memcpy(newElemData, stack->elemData, sizeof(int) * stack->size);
        free(stack->elemData);
        stack->elemData = newElemData;
        stack->capacity = newCapacity;
    }

    stack->elemData[(stack->size)++] = elem;
}
int IntStack_pop(IntStack* stack) {
    return stack->elemData[--(stack->size)];
}

int main(int argc, char** argv) {
    IntStack* stack = IntStack_new(256);

    int num;
    while (scanf("%ld", &num) == 1) {
        IntStack_push(stack, num);
    }

    while (!IntStack_isEmpty(stack)) {
        printf("%d\n", IntStack_pop(stack));
    }

    IntStack_destroy(stack);

    return 0;
}

C言語は当然自前実装。

C++

#include <algorithm>
#include <iostream>
#include <stack>

using namespace std;

int main(int argc, char** argv) {
    stack<int> stack;
    int num;

    while (cin >> num) {
        stack.push(num);
    }

    while (!stack.empty()) {
        cout << stack.top() << endl;
        stack.pop();
    }

    return EXIT_SUCCESS;
}

C++にはstackがあるので、それを使って簡単に書ける。

PHP

<?php

class Stack {
    private $elemData = array();
    public function isEmpty() {
        return count($this->elemData) === 0;
    }
    public function push($value) {
        array_push($this->elemData, $value);
    }
    public function pop() {
        $value = array_pop($this->elemData);
        return $value;
    }
}

$lines = file('php://stdin');

$stack = new Stack();

foreach ($lines as $line) {
    $stack->push((int)$line);
}

while (!$stack->isEmpty()) {
    printf("%d\n", $stack->pop());
}

PHPには標準ではStackクラスが無いので書いてみた。単なる配列のラッパー。

Python 2

import sys

class Stack:
    elemData = []
    def push(self, value):
        self.elemData.append(value)
    def pop(self):
        return self.elemData.pop()
    def isEmpty(self):
        return len(self.elemData) == 0

stack = Stack()
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    stack.push(int(line))

while stack.isEmpty() != True:
    print stack.pop()

Python 2ではStackクラスが見当たらなかったので書いてみた。単なる配列のラッパー。

Python 3

import sys

class Stack:
    elemData = []
    def push(self, value):
        self.elemData.append(value)
    def pop(self):
        return self.elemData.pop()
    def isEmpty(self):
        return len(self.elemData) == 0

stack = Stack()
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    stack.push(int(line))

while stack.isEmpty() != True:
    print(stack.pop())

Python 3ではStackクラスが見当たらなかったので書いてみた。単なる配列のラッパー。

Ruby

class Stack
    def initialize
        @elemData = []
    end
    def push(value)
        @elemData.push(value)
    end
    def pop
        @elemData.pop
    end
    def isEmpty
        return @elemData.size == 0
    end
end

stack = Stack.new
while line = STDIN.gets
    num = line.to_i
    stack.push(num)
end

while !stack.isEmpty
    print stack.pop(),"\n"
end

RubyではStackクラスが見当たらなかったので書いてみた。単なる配列のラッパー。

Perl

Perlにはpush/pop関数があるということだが、なんか微妙なので、2パターン書いてみた。

push/pop関数バージョン

my @stack = ();

my $line;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    push @stack, $num;
}

while ($#stack + 1 > 0) {
    my $value = @stack[$#stack];
    pop @stack;
    print "$value\n";
}

Stackモジュールバージョン

Stack.pm

package Stack;

sub new {
    my %self;
    my $class = shift;
    $self->{elemData} = ();
    $self->{size} = 0;
    return bless $self, $class;
}
sub push {
    my $self = shift;
    my $value = shift;
    $self->{elemData}[$self->{size}] = $value;
    ++$self->{size};
}
sub top {
    my $self = shift;
    return $self->{elemData}[$self->{size} - 1];
}
sub pop {
    my $self = shift;
    --$self->{size};
    delete $self->{elemData}[$self->{size}];
}
sub isEmpty {
    my $self = shift;
    return $self->{size} == 0;
}

1;

Main.pl

require "Stack.pm";

my $stack = Stack->new;

my $line;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    $stack->push($num);
}

while (!$stack->isEmpty()) {
    my $value = $stack->top();
    $stack->pop();
    print "$value\n";
}

Go

listをスタック代わりに直接使うということを考えたが、せっかくなので、Stackパッケージを作ってみた。

src/stack/stack.go

package stack;

import (
    "container/list"
)

type Stack struct {
    elemData *list.List
}

func (s *Stack) Init() *Stack {
    s.elemData = list.New()
    return s
}

func New() *Stack {
    return new(Stack).Init()
}

func (s *Stack) Push(v interface{}) {
    s.elemData.PushBack(v)
}

func (s *Stack) Pop() interface{} {
    e := s.elemData.Back()
    s.elemData.Remove(e)
    return e.Value
}

func (s *Stack) IsEmpty() bool {
    return s.elemData.Len() == 0
}

Main.go

package main

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

func main() {
    aStack := stack.New()
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s := string(buf)
        num, err2 := strconv.Atoi(s)
        if err2 != nil {
            panic(err2)
        }
        buf = make([]byte, 0, 1024)

        aStack.Push(num)
    }

    for !aStack.IsEmpty() {
        fmt.Println(aStack.Pop())
    }
}

実行するときに、GOPATH=$(pwd) go run Main.goってしてやらないと、カレントディレクトリのソースを参照してくれないのでちょっとハマった。

bash

#! /bin/bash

stack=()

while IFS=$'\n' read line ; do
    num=$(echo ${line} | sed -e 's/^\([+-]*[0-9][0-9]*\).*/\1/')
    num=$((num+0))
    stack=(${num} "${stack[@]}")
done

while [ ${#stack[@]} -gt 0 ]; do
    echo ${stack[0]}
    stack=("${stack[@]:1}")
done

あえて配列を使って書いたが、65536行のデータを食わせたら全然返ってこなかったので、大量のデータを処理する際には素直に他の言語で書いた方が良い。