Perlの代替として(4)

連想配列


int[char [] ] totalTime;

と宣言すると、keyが文字列の連想配列になる。
もちろん、普通に代入できる。


totalTime[str] = 5;

keyがあるかを調べるには次のようにする。


if(str in totalTime) {
...

列挙は次のようにする。


foreach(str; totalTime.keys) {
...

値なら、


foreach(str; totalTime.values) {
...

だいたいPerlと同じように使える。
ただ、この辺には闇があって、たとえば、


char [] str = "a";
char [] str2 = str ~ str; // aa
int[char [] ] a;
a[str2] = 2;
str2[1] = 'b';
foreach(key; a.keys) {
dout.writeLine(key); // ab
dout.writefln(a[key]); // Error: ArrayBoundsError
}

連想配列内ではkeyの実体は持たないってことか。
Dでは、基本的にこういう書き換えはやってはいけないらしい。
書き換えるなら、コピーをしてそちらを書き換える。



正規表現

おなじみの、


/^[a-z]/

みたいな構文は使えなくて、
RegExpオブジェクトをふつうに作って使う。


auto re = new RegExp("([a-z]+) ([0-9]+)");
auto ary = re.match(line);

auto は、型を自動的に判別できるときに使える。


RegExp re = new RegExp("([a-z]+) ([0-9]+)");
char [][] ary = re.match(line);

と同じ。楽だから使うという感じ。
上を1行にまとめると、


auto ary = RegExp("([a-z]+) ([0-9]+)").match(line);

こういうのはダメらしい。


RegExp re = new RegExp("(\w+) (\d+)");

追記
こう書く。


RegExp re = new RegExp("(\\w+) (\\d+)");

matchはJavaScriptと同じで、文字列の配列が返ってくる。
最初の要素はマッチした全体、その後が()で括った部分が返ってくる。
ary[1] => "a"、ary[2] => "100000000"、といった具合。
マッチしなかった場合、長さ0の配列が返る。


結局、書きあがったコードは、


import std.stream;
import std.cstream;
import std.string;
import std.regexp;
import std.conv;
import std.c.stdlib;

void main(char [][] arg) {
int[char[] ] totalTime;
int prevTime = -1;
char [] prevWord;

if(arg.length != 2) {
dout.writeLine("usage : time file.");
exit(1);
}

File inp = new File(arg[1], FileMode.In);
foreach(char [] line ; inp) {
auto ary = RegExp("([a-z]+) ([0-9]+)").match(line);
if(ary) {
char [] word = ary[1];
int now = toInt(ary[2]);
if(prevTime >= 0) {
auto rangeName = prevWord ~ "->" ~ word;
totalTime[rangeName] += now - prevTime;
}
prevWord = word.dup;
prevTime = now;
}
}
inp.close();

foreach(range; totalTime.keys.sort) {
dout.writefln("%s %.3f", range, totalTime[range] / 1000.0);
}
}