連想配列
連想配列はindexが必ずしも整数である必要はなく、 疎なindexも扱えます。 連想配列のインデックスのことを key と呼び、その型を KeyTypeと呼びます。
連想配列は、 配列宣言の[]の中に KeyType を書いて宣言します:
int[string] b; // 文字の配列で添え字づけられた
// int型の連想配列 b
// KeyType は string
b["hello"] = 3; // key "hello" に値 3 を関連付ける
func(b["hello"]); // 3 を func() へ引数として渡す
連想配列のkeyは、 remove 関数によって取り除くことができます:
b.remove("hello");
InExpression によって、値がある連想配列のkeyであればその要素へのポインタ、 そうでなければ null が得られます:
int* p;
p = ("hello" in b);
if (p !is (B null))
...
関数型とvoidは KeyType にできません。
関数型とvoidは要素型にすることもできません。
クラスをKeyTypeとして使うには
クラスを KeyTypeとして使うこともできます。 そのためには、 クラスの定義で、以下にあげる Object のメンバ関数をオーバーライドしてください:
- hash_t toHash()
- bool opEquals(Object)
- int opCmp(Object)
hash_t は整数型へのaliasです。
opCmp と opEquals への引数の型はそのクラス自身ではなく、 Object 型とすることに注意が必要です。
例:
class Foo {
int a, b;
hash_t toHash() { return a + b; }
bool opEquals(Object o)
{ Foo foo = cast(Foo) o;
return foo && a == foo.a && b == foo.b;
}
int opCmp(Object o)
{ Foo foo = cast(Foo) o;
if (!foo)
return -1;
if (a == foo.a)
return b - foo.b;
return a - foo.a;
}
}
処理系は opEquals か opCmp、あるいはその両方のいずれかを使用します。 実装する際は、オブジェクト同士が等しいかどうかの判定に関しては opEquals と opCmp の結果が矛盾しないよう注意してください。
構造体や共用体をKeyTypeとして使うには
KeyType が構造体型の場合、 構造体のバイナリ表現を使ったハッシュ計算や比較が デフォルトの方式として使われます。 カスタマイズしたい場合は、 以下の関数を構造体のメンバとして提供します:
const hash_t toHash();
const bool opEquals(ref const KeyType s);
const int opCmp(ref const KeyType s);
例:
import std.string;
struct MyString {
string str;
const hash_t toHash()
{ hash_t hash;
foreach (char c; str)
hash = (hash * 9) + c;
return hash;
}
const bool opEquals(ref const MyString s)
{
return std.string.cmp(this.str, s.str) == 0;
}
const int opCmp(ref const MyString s)
{
return std.string.cmp(this.str, s.str);
}
}
処理系は opEquals か opCmp、あるいはその両方のいずれかを使用します。 実装する際は、オブジェクト同士が等しいかどうかの判定に関しては opEquals と opCmp の結果が矛盾しないよう注意してください。
プロパティ
連想配列のプロパティは以下の通りです:プロパティ | 説明 |
---|---|
.sizeof | 連想配列への参照のサイズを返します。 32bitでは4で64bitでは8です。 |
.length | 連想配列に格納された値の個数を返します。 動的配列とは異なり、読み取り専用です。 |
.keys | 連想配列のkeyからなる 動的配列を返します。 |
.values | 連想配列のvalueからなる 動的配列を返します。 |
.rehash | 連想配列をin-placeで再構成し、より効率的なアクセスを可能にします。 rehashは、例えば、 プログラムがシンボルテーブルのロードを終えて、 さあ高速に参照したい、というときに効果があります。 返値は、再構成された配列自身です。 |
.byKey() | ForeachStatement によって連想配列のkey全体をループするための delegateを返します。 |
.byValue() | ForeachStatement によって連想配列のvalue全体をループするための delegateを返します。 |
.get(Key key, lazy Value defaultValue) | key を連想配列から検索し、存在すれば対応する value を返し、 存在しなければ defaultValue を評価し値を返します。 |
連想配列の例: 単語数カウント
例では簡単のために、ファイルがASCIIでエンコードされLFで改行されているものとします。 汎用に書くには、dchar c をループに使い、 std.uni の関数を利用します。
import std.file; // D のファイル I/O
import std.stdio;
import std.ascii;
void main (string[] args) {
ulong totalWords, totalLines, totalChars;
ulong[string] dictionary;
writefln(" lines words bytes file");
foreach (arg; args[1 .. $]) // 先頭以外の引数それぞれについて
{
ulong wordCount, lineCount, charCount;
foreach(line; File(arg).byLine())
{
bool inWord;
size_t wordStart;
void tryFinishWord(size_t wordEnd)
{
if (inWord)
{
auto word = line[wordStart .. wordEnd];
++dictionary[word.idup]; // 単語数をカウントアップ
inWord = false;
}
}
foreach (i, char c; line)
{
if (std.ascii.isDigit(c))
{ // c は数字 (0..9)
}
else if (std.ascii.isAlpha(c))
{ // c はASCII文字 (A..Z, a..z)
if (!inWord)
{
wordStart = i;
inWord = true;
++wordCount;
}
}
else
tryFinishWord(i);
++charCount;
}
tryFinishWord(line.length);
++lineCount;
}
writefln("%8s%8s%8s %s", lineCount, wordCount, charCount, arg);
totalWords += wordCount;
totalLines += lineCount;
totalChars += charCount;
}
if (args.length > 2)
{
writefln("-------------------------------------\n%8s%8s%8s total",
totalLines, totalWords, totalChars);
}
writeln("-------------------------------------");
foreach (word; dictionary.keys.sort)
{
writefln("%3s %s", dictionary[word], word);
}
}