英語版
このページの英語版を見る

連想配列

連想配列は、必ずしも整数ではないインデックスを持つ、 を持ち、まばらに入力することができる。連想配列のインデックスは のインデックスはキーと呼ばれ、その型はKeyTypeと呼ばれる。

連想配列は、配列宣言の。 を配列宣言の[ ] :

int[string] aa;   // 文字列キーでインデックス化されたintの連想配列
                  // 文字列キーでインデックス化される。
                  // KeyTypeはstringである。
aa["hello"] = 3;  // キー"hello"に関連付けられた値を3に設定する
int value = aa["hello"];  // キーから値を検索する
assert(value == 3);

連想配列のキー型も要素型も、関数型や。 関数型やvoid

実装定義: 組み込みの連想配列は、配列に挿入されたキーの順序を保持しない。 を保持しない。特に、 ループでは のループでは、要素が反復される順序は通常指定されない。foreach

リテラル

auto aa = [21u: "he", 38: "ho", 2: "hi"];
static assert(is(typeof(aa) == string[uint]));
assert(aa[2] == "hi");

連想配列リテラルを参照のこと。

キーを削除する

連想配列の特定のキーを削除するには remove 関数 "で削除できる:

aa.remove("hello");

remove(key) 与えられたキーが存在しない場合は何もしない。 を返す。 指定されたfalseキーが存在する場合は、それをAAから削除し、 を返す。 をAAから削除し、 を返す。 true

すべてのキーは、clear のメソッドを使って削除できる。

メンバーシップのテスト

InExpressionは、キーが連想配列内にあれば値へのポインタを返す。 null へのポインタを返す:

int* p;

p = "hello" in aa;
if (p !is null)
{
    *p = 4;  // キーに関連付けられた値を更新する
    assert(aa["hello"] == 4);
}
未定義の振る舞い: アドレスが返された要素の前後を指すようにポインタを調整する。 の前後を指すようにポインタを調整し、それを再参照する。

クラスをKeyTypeとして使用する

クラスをKeyTypeとして使うことができる。そのためには をオーバーライドしなければならない。 Object をオーバーライドしなければならない:

opEquals のパラメータは、定義されているクラスの型ではなく Object 型であり、定義されているクラスの型ではないことに注意。

例:」である:

class Foo
{
    int a, b;

    override size_t toHash() { return a + b; }

    override bool opEquals(Object o)
    {
        Foo foo = cast(Foo) o;
        return foo && a == foo.a && b == foo.b;
    }
}
実装定義: opCmp が等しいかどうかのチェックには使われない。 連想配列による等号チェックには使用されない。しかし、実際にopEquals opCmp 呼び出されるかは実行時まで決定されないため、コンパイラは常に不一致の関数を検出できるわけではない。 不一致関数を検出することはできない。レガシーな問題のため、コンパイラは opCmp をオーバーライドする連想配列のキー型を拒否することがある。 opEquals.この制限は将来のバージョンで削除されるかもしれない。
未定義の振る舞い:
  1. toHash が一貫して同じ値でなければならない。 opEquals が真を返すとき、一貫して同じ値でなければならない。言い換えれば、等しいとみなされる2つのオブジェクト は常に同じハッシュ値を持つべきである。 そうでなければ、未定義の動作が生じる。
ベストプラクティス:
  1. @safe@nogcpureconstscope の属性をできるだけ使用する。 toHash opEquals をできるだけ使用すること。

構造体または共用体をKeyTypeとして使用する。

KeyTypeが構造体または共用体の場合、 デフォルトのメカニズムが使用される に基づいてハッシュとその比較を計算する。 のフィールドに基づいてハッシュとその比較を計算する。カスタムのメカニズムを使用することもできる。 以下の関数を構造体メンバとして提供することで、カスタムメカニズムを使用できる:

size_t toHash() const @safe pure nothrow;
bool opEquals(ref const typeof(this) s) const @safe pure nothrow;

例えば:

import std.string;

struct MyString
{
    string str;

    size_t toHash() const @safe pure nothrow
    {
        size_t hash;
        foreach (char c; str)
            hash = (hash * 9) + c;
        return hash;
    }

    bool opEquals(ref const MyString s) const @safe pure nothrow
    {
        return std.string.cmp(this.str, s.str) == 0;
    }
}

関数は@safe の代わりに@trusted を使うことができる。

実装定義: opCmp による等号チェックには使われない。 による等号チェックには使用されない。 このため、またレガシーな理由からも opCmp を定義することは許されない、 opEquals を省略することができる。この制限は この制限は、将来のDバージョンで削除される可能性がある。
未定義の振る舞い:
  1. toHash が一貫して同じ値でなければならない。 opEquals が真を返すとき、一貫して同じ値でなければならない。言い換えると、等しいとみなされる2つの構造体 は常に同じハッシュ値を持つべきである。 そうでない場合、未定義の動作が生じる。
ベストプラクティス:
  1. @nogc 属性をできるだけ使用する。 toHashopEquals のオーバーライドで使用する。

AAエントリーの設定に関する工事または割り当て

AAインデクシング・アクセスが代入演算子の左側にある場合、AAエントリーを設定するために特別に扱われる。 キーに関連するAAエントリを設定するために特別に扱われる。 キーを設定するために特別に扱われる。

string[int] aa;
string s;

//s = aa[1];        // 実行時にRangeErrorをスローする
aa[1] = "hello";    // AAエントリを設定するために処理される
s = aa[1];          // ルックアップに成功する
assert(s == "hello");

割り当てられた値型がAA要素型と等価である場合:

  1. インデックス・キーがAAにまだ存在しない場合、新しいAAエントリが割り当てられ、割り当てられた値で初期化される。 割り当てられた値で初期化される。
  2. インデックス・キーがすでにAAに存在する場合、設定は通常の割り当てを実行する。 割り当てが実行される。
struct S
{
    int val;
    void opAssign(S rhs) { this.val = rhs.val * 2; }
}
S[int] aa;
aa[1] = S(10);  // 最初の設定は、エントリaa[1]を初期化する
assert(aa[1].val == 10);
aa[1] = S(10);  // 2番目の設定は通常の代入を呼び出し、
                // 演算子オーバーロードはメンバopAssign関数に書き換える。
assert(aa[1].val == 20);

代入された値型がAA要素型と等価でない場合、式は通常のインデックスを持つ演算子のオーバーロードを呼び出すことができる。 型と等価でない場合、式は通常のインデクシングで演算子オーバーロードを呼び出すことができる。 にアクセスすることができる:

struct S
{
    int val;
    void opAssign(int v) { this.val = v * 2; }
}
S[int] aa;
aa[1] = 10;     // is rewritten to: aa[1].opAssign(10)で、
                // opAssignが呼び出される前にRangeErrorがスローされる

ただし、AA要素型が、代入値からの暗黙的コンストラクタ呼び出しをサポートする構造体である場合、暗黙的コンストラクションは実行されない。 ただし、AA要素の型が、代入された値からの暗黙のコンストラクタ呼び出しをサポー トする構造体である場合は、AAエントリの設定に暗黙の構築が使用される。 は AA エントリの設定に使用される:

struct S
{
    int val;
    this(int v) { this.val = v; }
    void opAssign(int v) { this.val = v * 2; }
}
S s = 1;    // OK、次のように書き換えられる: S s = S(1);
s = 1;      // OK、次のように書き換えられる: s.opAssign(1);

S[int] aa;
aa[1] = 10; // 最初の設定は次のように書き換えられる: aa[1] = S(10);
assert(aa[1].val == 10);
aa[1] = 10; // 2番目の設定は次のように書き換えられる: aa[1].opAssign(10);
assert(aa[1].val == 20);
これは、いくつかの"値"セマンティクスで効率的にメモリを再利用するために設計されている。 構造体" で効率的にメモリを再利用するために設計されている。 std.bigint.BigInt.
import std.bigint;
BigInt[string] aa;
aa["a"] = 10;   // BigInt(10)を構築し、AAで移動する
aa["a"] = 20;   // aa["a"].opAssign(20)を呼び出す

存在しない場合は挿入する

AAアクセスで、キーに対応する値が必要な場合、値が存在しなければ挿入することはできない。 キーに対応する値がなければならない。この require 関数は、遅延引数によって新しい値を構築する手段を提供する。 引数を介して新しい値を構築する手段を提供する。遅延引数は、キーが存在しない場合に評価される。 に評価される。require 。 この操作により、複数のキー検索を行う必要がなくなる。

class C{}
C[string] aa;

auto a = aa.require("a", new C);   // "a"を検索し、存在しなければ構築する

その値が作られたものなのか、すでに存在するものなのかを知る必要がある場合もある。 を知る必要があることがある。require "関数"は、値が構築されたかどうかを示すbooleanパラメータを提供しない。 パラメータは用意されていない。 関数やデリゲートを介して値を構築することができる。これにより、以下のように を使うことができる。

class C{}
C[string] aa;

bool constructed;
auto a = aa.require("a", { constructed=true; return new C;}());
assert(constructed == true);

C newc;
auto b = aa.require("b", { newc = new C; return newc;}());
assert(b is newc);

高度な更新

通常、連想配列の値を更新するには、assign文を使う。 assign文で行う。

int[string] aa;

aa["a"] = 3;  // キー"a"に関連付けられた値を3に設定する

値がすでに存在するのか、それとも構築する必要があるのかによって、異なる操作を行う必要がある場合もある。 値がすでに存在するのか、それとも構築する必要があるのかによって、異なる操作を行う必要があることがある。そこで update 関数は、デリゲートを介して新しい値を構築する手段を提供する。 create この関数は、デリゲートを介して新しい値を構築する手段や、デリゲートを介して既存の値を更新する手段を提供する。update デリゲートを介して新しい値を構築したり、既存の値を更新したりする手段を提供する。update 。 キーのルックアップが不要になる。

int[string] aa;

// create
aa.update("key",
    () => 1,
    (int) {} // 実行されない
    );
assert(aa["key"] == 1);

// refで値を更新する
aa.update("key",
    () => 0, // 実行されない
    (ref int v) {
        v += 1;
    });
assert(aa["key"] == 2);

詳細は以下を参照のこと。 update.

イミュータブルAAのランタイム初期化

不変連想配列はしばしば望ましいが、時には実行時に初期化しなければならないこともある。 実行時に初期化しなければならないこともある。これには コンストラクタ(スコープによっては静的コンストラクタ)、 バッファ連想配列とassumeUnique :

immutable long[string] aa;

shared static this()
{
    import std.exception : assumeUnique;
    import std.conv : to;

    long[string] temp; // 変数バッファ
    foreach (i; 0 .. 10)
    {
        temp[to!string(i)] = i;
    }
    temp.rehash; // ルックアップを高速化する

    aa = assumeUnique(temp);
}

void main()
{
    assert(aa["1"] == 1);
    assert(aa["5"] == 5);
    assert(aa["9"] == 9);
}

構成と参照セマンティクス

連想配列のデフォルトはnull で、最初のキーと値のペアを代入するときに構築される。 を代入した時点で構築される。しかし、一度構築された連想配列は参照セマンティクスを持つ。 つまり、ある配列を別の配列に代入しても、データはコピーされない。これは、同じ配列に複数の参照を作成しようとする場合に特に重要である。 これは、同じ配列に複数の参照を作成しようとする場合に特に重要である。

int[int] aa;             // デフォルトはnull
int[int] aa2 = aa;       // null参照をコピーする

aa[1] = 1;
assert(aa2.length == 0); // aa2はまだnullである
aa2 = aa;
aa2[2] = 2;
assert(aa[2] == 2);      // これで両方が同じインスタンスを参照する

プロパティ

連想配列のプロパティは以下の通りである:

連想配列のプロパティ
PropertyDescription
.sizeof連想配列の参照サイズを返す。 32ビットでは4、64ビットでは8である。
.length連想配列の値の数を返す。 連想配列の値の数を返す。動的配列とは異なり、読み取り専用である。
.dup同じサイズの新しい連想配列を作成する。 を作成し、連想配列の内容をそこにコピーする。
.keys動的配列を返す。 その要素は連想配列のキーである。
.values連想配列の要素を値とする動的配列を返す。 を返す。
.rehash連想配列を再編成し、ルックアップがより効率的になるようにする。 rehash は、例えば以下のような場合に有効である、 プログラムはシンボル・テーブルのロードを終了し、次のような場合に有効である。 高速な検索が必要な場合などに有効である。再編成された配列への参照を返す。
.clear連想配列から残りのすべてのキーと値を削除する。 既存のストレージを再利用できるように、削除後の配列は再ハッシュされない。 これは、同じインスタンスへのすべての参照に影響する。destroy(aa) への現在の参照のみを設定するnull
.byKey()として使用するのに適した前方範囲を返す。 ForeachStatementの ForeachAggregateとして使用するのに適した前方範囲を返す。 のForeachAggregateとして使用するのに適した前方範囲を返す。
.byValue()として使用するのに適した前方範囲を返す ForeachStatementの ForeachAggregateとして使用するのに適した前方範囲を返す。 として使用するのに適した前方範囲を返す。
.byKeyValue()として使用するのに適した前方範囲を返す ForeachStatementの ForeachAggregateとして使用するのに適した前方範囲を返す。 へのForeachAggregateとして使用するのに適した前方範囲を返す。返されるペアは、 と 。 .key .value プロパティを持つ。 プロパティを持つ不透明な型で表される。 これは低レベルのインターフェイスであることに注意されたい。 これは連想配列の反復処理に対する低レベルのインターフェイスであり、連想配列と互換性がないことに注意してほしい。 とは互換性がない。 Tuple 型とは互換性がない。 Tuple との互換性のために、代わりに 代わりにstd.array.byPairを使う。
.get(Key key, lazy Value defVal) key を検索し、存在すれば対応する値を返す。 さもなければdefVal を評価し返す。
.require(Key key, lazy Value value) key を検索し、存在すれば対応する値を返す。 else はvalue を評価し、連想配列に追加して返す。
.update(Key key, Value delegate() create, Value delegate(Value) update) key を調べる。もし存在すれば、update のデリゲートを適用する。 elseはcreate デリゲートを評価し、連想配列に追加する。

例:

連想配列の例:単語数

料理人が多すぎる 材料が多すぎる
import std.algorithm;
import std.stdio;

void main()
{
    ulong[string] dictionary;
    ulong wordCount, lineCount, charCount;

    foreach (line; stdin.byLine(KeepTerminator.yes))
    {
        charCount += line.length;
        foreach (word; splitter(line))
        {
            wordCount += 1;
            if (auto count = word in dictionary)
                *count += 1;
            else
                dictionary[word.idup] = 1;
        }

        lineCount += 1;
    }

    writeln("   lines   words   bytes");
    writefln("%8s%8s%8s", lineCount, wordCount, charCount);

    const char[37] hr = '-';

    writeln(hr);
    foreach (word; sort(dictionary.keys))
    {
        writefln("%3s %s", dictionary[word], word);
    }
}

フルバージョンはwcを参照のこと。

連想配列の例:ペアを数える

連想配列は、foreach文を使ってキー/値で反復することができる。 foreach文を使う。例として 例 "として、文字列中の長さ2(別名2-mers)の部分文字列の出現回数を数える。 長さ2(別名2マース)の文字列の出現数がカウントされる:

import std.range : slide;
import std.stdio : writefln;
import std.utf : byCodeUnit; // UTF-8の自動デコードを回避する

int[string] aa;

// 文字列`arr`のアルファベットは限られている: {A、C、G、T}
// したがって、パフォーマンスを向上させるために、デコードを行わずに反復を行うことができる
auto arr = "AGATAGA".byCodeUnit;

// 文字列内のすべてのペアを繰り返し、各ペアをカウントする
// ('A', 'G'), ('G', 'A'), ('A', 'T'), ...
foreach (window; arr.slide(2))
    aa[window.source]++; // ソースはコード・ユニットのラップを解く

// 連想配列のすべてのキーと値のペアを反復処理する
foreach (key, value; aa)
{
    writefln("key: %s, value: %d", key, value);
}
> rdmd count.d
key: AT, value: 1
key: GA, value: 2
key: TA, value: 1
key: AG, value: 2