連想配列
連想配列はindexが必ずしも整数である必要はなく、 疎なindexも扱えます。 連想配列のインデックスのことをkeyと呼び、その型をKeyTypeと呼びます。
連想配列は、 配列宣言の[]の中に KeyType を書いて宣言します:
int[char[]] b; // 文字の配列で添え字づけられた // int型の連想配列 b // KeyTypeはchar[] 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 != null) ...
関数型とvoidはKeyTypeにできません。
関数型とvoidは要素型にすることもできません。
クラスをKeyTypeとして使うには
クラスをKeyTypeとして使うこともできます。そのためには、 クラスの定義で、以下にあげる Object のメンバ関数をオーバーライドしてください:
- hash_t toHash()
- int opEquals(Object)
- int opCmp(Object)
hash_t は整数型へのaliasです
opCmp と opEquals への引数の型はそのクラス自身ではなく、 Object型とすることに注意が必要です。
例:
class Foo { int a, b; hash_t toHash() { return a + b; } int opEquals(Object o) { Foo f = cast(Foo) o; return f && a == foo.a && b == foo.b; } int opCmp(Object o) { Foo f = cast(Foo) o; if (!f) return -1; if (a == foo.a) return b - foo.b; return a - foo.a; } }
処理系は opEquals、 opCmp、あるいはその両方のいずれかを使用します。 実装する際は、オブジェクト同士が等しいかどうかの判定に関しては opEquals と opCmp の結果が矛盾しないよう注意してください。
構造体や共用体をKeyTypeとして使うには
KeyType が構造体型の場合、 構造体のバイナリ表現を使ったハッシュ計算や比較が デフォルトの方式として使われます。 カスタマイズしたい場合は、 以下の関数を構造体のメンバとして提供します:
hash_t toHash(); int opEquals(KeyType* s); int opCmp(KeyType* s);
例:
import std.string; struct MyString { string str; hash_t toHash() { hash_t hash; foreach (char c; s) hash = (hash * 9) + c; return hash; } bool opEquals(MyString* s) { return std.string.cmp(this.str, s.str) == 0; } int opCmp(MyString* s) { return std.string.cmp(this.str, s.str); } }
処理系は opEquals、 opCmp、あるいはその両方のいずれかを使用します。 実装する際は、オブジェクト同士が等しいかどうかの判定に関しては opEquals と opCmp の結果が矛盾しないよう注意してください。
プロパティ
連想配列のプロパティは:プロパティ | 説明 |
---|---|
.sizeof | 連想配列への参照のサイズを返します。 典型的には、8 です。 |
.length | 連想配列に格納された値の個数を返します。 動的配列とは異なり、読み取り専用です。 |
.keys | 連想配列のkeyからなる 動的配列を返します。 |
.values | 連想配列のvalueからなる 動的配列を返します。 |
.rehash | 連想配列をin-placeで再構成し、より効率的なアクセスを可能にします。 rehashは、例えば、 プログラムがシンボルテーブルのロードを終えて、 さあ高速に参照したい、というときに効果があります。 返値は、再構成された配列自身です。 |
連想配列の例: 単語数カウント
import std.file; // D のファイルI/O import std.stdio; int main (string[] args) { int word_total; int line_total; int char_total; int[char[]] dictionary; writefln(" lines words bytes file"); for (int i = 1; i < args.length; ++i) // プログラム引数 { char[] input; // 入力バッファ int w_cnt, l_cnt, c_cnt; // 単語, 行, 文字数 int inword; int wstart; // input[] へファイルを読み込み input = cast(char[])std.file.read(args[i]); foreach (j, char c; input) { if (c == '\n') ++l_cnt; if (c >= '0' && c <= '9') { } else if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') { if (!inword) { wstart = j; inword = 1; ++w_cnt; } } else if (inword) { char[] word = input[wstart .. j]; dictionary[word]++; // 単語数カウントを増やす inword = 0; } ++c_cnt; } if (inword) { char[] word = input[wstart .. input.length]; dictionary[word]++; } writefln("%8d%8d%8d %s", l_cnt, w_cnt, c_cnt, args[i]); line_total += l_cnt; word_total += w_cnt; char_total += c_cnt; } if (args.length > 2) { writef("-------------------------------------\n%8d%8d%8d total", line_total, word_total, char_total); } writefln("-------------------------------------"); foreach (word; dictionary.keys.sort) { writefln("%3d %s", dictionary[word], word); } return 0; }