$(DDOC $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE ) $(SPEC_S Associative Arrays, $(DDOC_BLANKLINE ) $(HEADERNAV_TOC $(HEADERNAV_ITEM literals, Literals) $(HEADERNAV_ITEM removing_keys, Removing Keys) $(HEADERNAV_ITEM testing_membership, Testing Membership) $(HEADERNAV_ITEM using_classes_as_key, Using Classes as the KeyType) $(HEADERNAV_ITEM using_struct_as_key, Using Structs or Unions as the KeyType) $(HEADERNAV_ITEM construction_assignment_entries, Construction or Assignment on Setting AA Entries) $(HEADERNAV_ITEM inserting_if_not_present, Inserting if not present) $(HEADERNAV_ITEM advanced_updating, Advanced updating) $(HEADERNAV_ITEM runtime_initialization, Runtime Initialization of Immutable AAs) $(HEADERNAV_ITEM construction_and_ref_semantic, Construction and Reference Semantics) $(HEADERNAV_ITEM properties, Properties) $(HEADERNAV_SUBITEMS examples, Examples, $(HEADERNAV_ITEM aa_example, Associative Array Example: word count) $(HEADERNAV_ITEM aa_example_iteration, Associative Array Example: counting pairs) ) ) $(DDOC_BLANKLINE ) $(P Associative arrays have an index that is not necessarily an integer, and can be sparsely populated. The index for an associative array is called the $(I key), and its type is called the $(I KeyType).) $(DDOC_BLANKLINE ) $(P Associative arrays are declared by placing the $(I KeyType) within the $(D [ ]) of an array declaration: ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD int)[string] aa; $(D_COMMENT // Associative array of ints that are ) $(D_COMMENT // indexed by string keys. ) $(D_COMMENT // The KeyType is string. )aa[$(D_STRING "hello")] = 3; $(D_COMMENT // set value associated with key "hello" to 3 )$(D_KEYWORD int) value = aa[$(D_STRING "hello")]; $(D_COMMENT // lookup value from a key )$(D_KEYWORD assert)(value == 3); ) ) $(DDOC_BLANKLINE ) $(P Neither the $(I KeyType)s nor the element types of an associative array can be function types or $(D void). ) $(DDOC_BLANKLINE ) $(IMPLEMENTATION_DEFINED The built-in associative arrays do not preserve the order of the keys inserted into the array. In particular, in a $(D foreach) loop the order in which the elements are iterated is typically unspecified.) $(DDOC_BLANKLINE )

$(LNAME2 literals, Literals)

$(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD auto) aa = [21u: $(D_STRING "he"), 38: $(D_STRING "ho"), 2: $(D_STRING "hi")]; $(D_KEYWORD static) $(D_KEYWORD assert)($(D_KEYWORD is)($(D_KEYWORD typeof)(aa) == string[$(D_KEYWORD uint)])); $(D_KEYWORD assert)(aa[2] == $(D_STRING "hi")); ) ) $(P See $(DDSUBLINK spec/expression, associative_array_literals, Associative Array Literals).) $(DDOC_BLANKLINE )

$(LNAME2 removing_keys, Removing Keys)

$(DDOC_BLANKLINE ) $(P Particular keys in an associative array can be removed with the $(D remove) function: ) $(DDOC_BLANKLINE ) $(D_CODE aa.$(CODE_HIGHLIGHT remove)($(D_STRING "hello")); ) $(DDOC_BLANKLINE ) $(P $(D remove(key)) does nothing if the given $(I key) does not exist and returns $(D false). If the given $(I key) does exist, it removes it from the AA and returns $(D true). ) $(DDOC_BLANKLINE ) $(P All keys can be removed by using the method clear.) $(DDOC_BLANKLINE )

$(LNAME2 testing_membership, Testing Membership)

$(DDOC_BLANKLINE ) $(P The $(GLINK2 expression, InExpression) yields a pointer to the value if the key is in the associative array, or $(D null) if not: ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD int)* p; p = $(D_STRING "hello") $(CODE_HIGHLIGHT $(D_KEYWORD in)) aa; $(D_KEYWORD if) (p !$(D_KEYWORD is) $(D_KEYWORD null)) { *p = 4; $(D_COMMENT // update value associated with key ) $(D_KEYWORD assert)(aa[$(D_STRING "hello")] == 4); } ) $(DDOC_BLANKLINE ) $(UNDEFINED_BEHAVIOR Adjusting the pointer to point before or after the element whose address is returned, and then dereferencing it.) $(DDOC_BLANKLINE )

$(LNAME2 using_classes_as_key, Using Classes as the KeyType)

$(DDOC_BLANKLINE ) $(P Classes can be used as the $(I KeyType). For this to work, the class definition must override the following member functions of class $(D Object):) $(DDOC_BLANKLINE ) $(UL $(LI $(D size_t toHash() @trusted nothrow)) $(LI $(D bool opEquals(Object))) ) $(DDOC_BLANKLINE ) $(P Note that the parameter to $(D opEquals) is of type $(D Object), not the type of the class in which it is defined.) $(DDOC_BLANKLINE ) $(P For example:) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD class) Foo { $(D_KEYWORD int) a, b; $(D_KEYWORD override) size_t $(CODE_HIGHLIGHT toHash)() { $(D_KEYWORD return) a + b; } $(D_KEYWORD override) $(D_KEYWORD bool) $(CODE_HIGHLIGHT opEquals)(Object o) { Foo foo = $(D_KEYWORD cast)(Foo) o; $(D_KEYWORD return) foo && a == foo.a && b == foo.b; } } ) $(DDOC_BLANKLINE ) $(IMPLEMENTATION_DEFINED opCmp is not used to check for equality by the associative array. However, since the actual opEquals or opCmp called is not decided until runtime, the compiler cannot always detect mismatched functions. Because of legacy issues, the compiler may reject an associative array key type that overrides opCmp but not opEquals. This restriction may be removed in future versions.) $(DDOC_BLANKLINE ) $(UNDEFINED_BEHAVIOR $(OL $(LI If $(D toHash) must consistently be the same value when $(D opEquals) returns true. In other words, two objects that are considered equal should always have the same hash value. Otherwise, undefined behavior will result.) )) $(DDOC_BLANKLINE ) $(BEST_PRACTICE $(OL $(LI Use the attributes @safe, @nogc, pure, const, and scope as much as possible on the toHash and opEquals overrides.) )) $(DDOC_BLANKLINE )

$(LNAME2 using_struct_as_key, Using Structs or Unions as the KeyType)

$(DDOC_BLANKLINE ) $(P If the $(I KeyType) is a struct or union type, a default mechanism is used to compute the hash and comparisons of it based on the fields of the struct value. A custom mechanism can be used by providing the following functions as struct members: ) $(DDOC_BLANKLINE ) $(D_CODE size_t $(CODE_HIGHLIGHT toHash)() $(D_KEYWORD const) @safe $(D_KEYWORD pure) $(D_KEYWORD nothrow); $(D_KEYWORD bool) $(CODE_HIGHLIGHT opEquals)($(D_KEYWORD ref) $(D_KEYWORD const) $(D_KEYWORD typeof)($(D_KEYWORD this)) s) $(D_KEYWORD const) @safe $(D_KEYWORD pure) $(D_KEYWORD nothrow); ) $(DDOC_BLANKLINE ) $(P For example:) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD import) std.string; $(D_KEYWORD struct) MyString { string str; size_t $(CODE_HIGHLIGHT toHash)() $(D_KEYWORD const) @safe $(D_KEYWORD pure) $(D_KEYWORD nothrow) { size_t hash; $(D_KEYWORD foreach) ($(D_KEYWORD char) c; str) hash = (hash * 9) + c; $(D_KEYWORD return) hash; } $(D_KEYWORD bool) $(CODE_HIGHLIGHT opEquals)($(D_KEYWORD ref) $(D_KEYWORD const) MyString s) $(D_KEYWORD const) @safe $(D_KEYWORD pure) $(D_KEYWORD nothrow) { $(D_KEYWORD return) std.string.cmp($(D_KEYWORD this).str, s.str) == 0; } } ) $(DDOC_BLANKLINE ) $(P The functions can use $(D @trusted) instead of $(D @safe).) $(DDOC_BLANKLINE ) $(IMPLEMENTATION_DEFINED opCmp is not used to check for equality by the associative array. For this reason, and for legacy reasons, an associative array key is not allowed to define a specialized opCmp, but omit a specialized opEquals. This restriction may be removed in future versions of D.) $(DDOC_BLANKLINE ) $(UNDEFINED_BEHAVIOR $(OL $(LI If $(D toHash) must consistently be the same value when $(D opEquals) returns true. In other words, two structs that are considered equal should always have the same hash value. Otherwise, undefined behavior will result.) )) $(DDOC_BLANKLINE ) $(BEST_PRACTICE $(OL $(LI Use the attributes @nogc as much as possible on the toHash and opEquals overrides.) )) $(DDOC_BLANKLINE )

$(LNAME2 construction_assignment_entries, Construction or Assignment on Setting AA Entries)

$(DDOC_BLANKLINE ) $(P When an AA indexing access appears on the left side of an assignment operator, it is specially handled for setting an AA entry associated with the key.) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE string[$(D_KEYWORD int)] aa; string s; $(D_COMMENT //s = aa[1]; // throws RangeError in runtime )aa[1] = $(D_STRING "hello"); $(D_COMMENT // handled for setting AA entry )s = aa[1]; $(D_COMMENT // succeeds to lookup )$(D_KEYWORD assert)(s == $(D_STRING "hello")); ) ) $(P If the assigned value type is equivalent with the AA element type:) $(DDOC_BLANKLINE ) $(OL $(LI If the indexing key does not yet exist in AA, a new AA entry will be allocated, and it will be initialized with the assigned value.) $(LI If the indexing key already exists in the AA, the setting runs normal assignment.) ) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) val; $(D_KEYWORD void) opAssign(S rhs) { $(D_KEYWORD this).val = rhs.val * 2; } } S[$(D_KEYWORD int)] aa; aa[1] = S(10); $(D_COMMENT // first setting initializes the entry aa[1] )$(D_KEYWORD assert)(aa[1].val == 10); aa[1] = S(10); $(D_COMMENT // second setting invokes normal assignment, and ) $(D_COMMENT // operator-overloading rewrites it to member opAssign function. )$(D_KEYWORD assert)(aa[1].val == 20); ) ) $(DDOC_BLANKLINE ) $(P If the assigned value type is $(B not) equivalent with the AA element type, the expression could invoke operator overloading with normal indexing access:) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) val; $(D_KEYWORD void) opAssign($(D_KEYWORD int) v) { $(D_KEYWORD this).val = v * 2; } } S[$(D_KEYWORD int)] aa; aa[1] = 10; $(D_COMMENT // is rewritten to: aa[1].opAssign$(LPAREN)10$(RPAREN ), and ) $(D_COMMENT // throws RangeError before opAssign is called )) $(DDOC_BLANKLINE ) $(P However, if the AA element type is a struct which supports an implicit constructor call from the assigned value, implicit construction is used for setting the AA entry:) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD struct) S { $(D_KEYWORD int) val; $(D_KEYWORD this)($(D_KEYWORD int) v) { $(D_KEYWORD this).val = v; } $(D_KEYWORD void) opAssign($(D_KEYWORD int) v) { $(D_KEYWORD this).val = v * 2; } } S s = 1; $(D_COMMENT // OK, rewritten to: S s = S$(LPAREN)1$(RPAREN ); )s = 1; $(D_COMMENT // OK, rewritten to: s.opAssign$(LPAREN)1$(RPAREN ); ) S[$(D_KEYWORD int)] aa; aa[1] = 10; $(D_COMMENT // first setting is rewritten to: aa[1] = S$(LPAREN)10$(RPAREN ); )$(D_KEYWORD assert)(aa[1].val == 10); aa[1] = 10; $(D_COMMENT // second setting is rewritten to: aa[1].opAssign$(LPAREN)10$(RPAREN ); )$(D_KEYWORD assert)(aa[1].val == 20); ) ) This is designed for efficient memory reuse with some value-semantics structs, eg. $(REF BigInt, std,bigint). $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD import) std.bigint; BigInt[string] aa; aa[$(D_STRING "a")] = 10; $(D_COMMENT // construct BigInt$(LPAREN)10$(RPAREN ) and move it in AA )aa[$(D_STRING "a")] = 20; $(D_COMMENT // call aa["a"].opAssign$(LPAREN)20$(RPAREN ) )) $(DDOC_BLANKLINE )

$(LNAME2 inserting_if_not_present, Inserting if not present)

$(DDOC_BLANKLINE ) $(P When AA access requires that there must be a value corresponding to the key, a value must be constructed and inserted if not present. The $(D require) function provides a means to construct a new value via a lazy argument. The lazy argument is evaluated when the key is not present. The $(D require) operation avoids the need to perform multiple key lookups.) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD class) C{} C[string] aa; $(D_KEYWORD auto) a = aa.require($(D_STRING "a"), $(D_KEYWORD new) C); $(D_COMMENT // lookup "a", construct if not present )) $(DDOC_BLANKLINE ) $(P Sometimes it is necessary to know whether the value was constructed or already exists. The $(D require) function doesn't provide a boolean parameter to indicate whether the value was constructed but instead allows the construction via a function or delegate. This allows the use of any mechanism as demonstrated below.) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD class) C{} C[string] aa; $(D_KEYWORD bool) constructed; $(D_KEYWORD auto) a = aa.require($(D_STRING "a"), { constructed=$(D_KEYWORD true); $(D_KEYWORD return) $(D_KEYWORD new) C;}()); $(D_KEYWORD assert)(constructed == $(D_KEYWORD true)); C newc; $(D_KEYWORD auto) b = aa.require($(D_STRING "b"), { newc = $(D_KEYWORD new) C; $(D_KEYWORD return) newc;}()); $(D_KEYWORD assert)(b $(D_KEYWORD is) newc); ) ) $(DDOC_BLANKLINE )

$(LNAME2 advanced_updating, Advanced updating)

$(DDOC_BLANKLINE ) $(P Typically updating a value in an associative array is simply done with an assign statement.) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD int)[string] aa; aa[$(D_STRING "a")] = 3; $(D_COMMENT // set value associated with key "a" to 3 )) $(DDOC_BLANKLINE ) $(P Sometimes it is necessary to perform different operations depending on whether a value already exists or needs to be constructed. The $(D update) function provides a means to construct a new value via the $(D create) delegate or update an existing value via the $(D update) delegate. The $(D update) operation avoids the need to perform multiple key lookups.) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD int)[string] aa; $(D_COMMENT // create )aa.update($(D_STRING "key"), () => 1, ($(D_KEYWORD int)) {} $(D_COMMENT // not executed ) ); $(D_KEYWORD assert)(aa[$(D_STRING "key")] == 1); $(D_COMMENT // update value by ref )aa.update($(D_STRING "key"), () => 0, $(D_COMMENT // not executed ) ($(D_KEYWORD ref) $(D_KEYWORD int) v) { v += 1; }); $(D_KEYWORD assert)(aa[$(D_STRING "key")] == 2); ) ) $(DDOC_BLANKLINE ) $(P For details, see $(REF1 update, object).) $(DDOC_BLANKLINE )

$(LNAME2 runtime_initialization, Runtime Initialization of Immutable AAs)

$(DDOC_BLANKLINE ) $(P Immutable associative arrays are often desirable, but sometimes initialization must be done at runtime. This can be achieved with a constructor (static constructor depending on scope), a buffer associative array and $(D assumeUnique):) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD immutable) $(D_KEYWORD long)[string] aa; $(D_KEYWORD shared) $(D_KEYWORD static) $(D_KEYWORD this)() { $(D_KEYWORD import) std.exception : assumeUnique; $(D_KEYWORD import) std.conv : to; $(D_KEYWORD long)[string] temp; $(D_COMMENT // mutable buffer ) $(D_KEYWORD foreach) (i; 0 .. 10) { temp[to!string(i)] = i; } temp.rehash; $(D_COMMENT // for faster lookups ) aa = assumeUnique(temp); } $(D_KEYWORD void) main() { $(D_KEYWORD assert)(aa[$(D_STRING "1")] == 1); $(D_KEYWORD assert)(aa[$(D_STRING "5")] == 5); $(D_KEYWORD assert)(aa[$(D_STRING "9")] == 9); } ) ) $(DDOC_BLANKLINE )

$(LNAME2 construction_and_ref_semantic, Construction and Reference Semantics)

$(DDOC_BLANKLINE ) $(P An Associative Array defaults to null, and is constructed upon assigning the first key/value pair. However, once constructed, an associative array has $(I reference semantics), meaning that assigning one array to another does not copy the data. This is especially important when attempting to create multiple references to the same array.) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD int)[$(D_KEYWORD int)] aa; $(D_COMMENT // defaults to null )$(D_KEYWORD int)[$(D_KEYWORD int)] aa2 = aa; $(D_COMMENT // copies the null reference ) aa[1] = 1; $(D_KEYWORD assert)(aa2.length == 0); $(D_COMMENT // aa2 still is null )aa2 = aa; aa2[2] = 2; $(D_KEYWORD assert)(aa[2] == 2); $(D_COMMENT // now both refer to the same instance )) ) $(DDOC_BLANKLINE )

$(LNAME2 properties, Properties)

$(DDOC_BLANKLINE ) $(P Properties for associative arrays are:) $(DDOC_BLANKLINE ) $(TABLE_2COLS Associative Array Properties, Property, Description $(TROW $(D .sizeof), Returns the size of the reference to the associative array; it is 4 in 32-bit builds and 8 on 64-bit builds.) $(TROW $(D .length), $(ARGS Returns number of values in the associative array. Unlike for dynamic arrays, it is read-only.)) $(TROW $(D .dup), Create a new associative array of the same size and copy the contents of the associative array into it.) $(TROW $(D .keys), $(ARGS Returns dynamic array, the elements of which are the keys in the associative array.)) $(TROW $(D .values), $(ARGS Returns dynamic array, the elements of which are the values in the associative array.)) $(TROW $(D .rehash), $(ARGS Reorganizes the associative array in place so that lookups are more efficient. $(D rehash) is effective when, for example, the program is done loading up a symbol table and now needs fast lookups in it. Returns a reference to the reorganized array.)) $(TROW $(D .clear), $(ARGS Removes all remaining keys and values from an associative array. The array is not rehashed after removal, to allow for the existing storage to be reused. This will affect all references to the same instance and is not equivalent to destroy(aa) which only sets the current reference to null)) $(TROW $(D .byKey()), $(ARGS Returns a forward range suitable for use as a $(I ForeachAggregate) to a $(GLINK2 statement, ForeachStatement) which will iterate over the keys of the associative array.)) $(TROW $(D .byValue()), $(ARGS Returns a forward range suitable for use as a $(I ForeachAggregate) to a $(GLINK2 statement, ForeachStatement) which will iterate over the values of the associative array.)) $(TROW $(D .byKeyValue()), $(ARGS Returns a forward range suitable for use as a $(I ForeachAggregate) to a $(GLINK2 statement, ForeachStatement) which will iterate over key-value pairs of the associative array. The returned pairs are represented by an opaque type with $(D .key) and $(D .value) properties for accessing the key and value of the pair, respectively. Note that this is a low-level interface to iterating over the associative array and is not compatible with the $(LINK2 $(ROOT_DIR )phobos/std_typecons.html#.Tuple,Tuple) type in Phobos. For compatibility with Tuple, use $(LINK2 $(ROOT_DIR )phobos/std_array.html#.byPair,std.array.byPair) instead.)) $(TROW $(D .get(Key key, lazy Value defVal)), $(ARGS Looks up $(D key); if it exists returns corresponding value else evaluates and returns $(D defVal).)) $(TROW $(D .require(Key key, lazy Value value)), $(ARGS Looks up $(D key); if it exists returns corresponding value else evaluates $(D value), adds it to the associative array and returns it.)) $(TROW $(D .update(Key key, Value delegate() create, Value delegate(Value) update)), $(ARGS Looks up $(D key); if it exists applies the $(D update) delegate else evaluates the $(D create) delegate and adds it to the associative array)) ) $(DDOC_BLANKLINE )

$(LNAME2 examples, Examples)

$(DDOC_BLANKLINE )

$(LNAME2 aa_example, Associative Array Example: word count)

$(DDOC_BLANKLINE ) $(RUNNABLE_EXAMPLE $(RUNNABLE_EXAMPLE_STDIN too many cooks too many ingredients ) $(D_CODE $(D_KEYWORD import) std.algorithm; $(D_KEYWORD import) std.stdio; $(D_KEYWORD void) main() { $(D_KEYWORD ulong)[string] dictionary; $(D_KEYWORD ulong) wordCount, lineCount, charCount; $(D_KEYWORD foreach) (line; stdin.byLine(KeepTerminator.yes)) { charCount += line.length; $(D_KEYWORD foreach) (word; splitter(line)) { wordCount += 1; $(D_KEYWORD if) ($(D_KEYWORD auto) count = word $(D_KEYWORD in) dictionary) *count += 1; $(D_KEYWORD else) dictionary[word.idup] = 1; } lineCount += 1; } writeln($(D_STRING " lines words bytes")); writefln($(D_STRING "%8s%8s%8s"), lineCount, wordCount, charCount); $(D_KEYWORD const) $(D_KEYWORD char)[37] hr = '-'; writeln(hr); $(D_KEYWORD foreach) (word; sort(dictionary.keys)) { writefln($(D_STRING "%3s %s"), dictionary[word], word); } } ) ) $(P See $(DDLINK wc, wc, wc) for the full version.) $(DDOC_BLANKLINE )

$(LNAME2 aa_example_iteration, Associative Array Example: counting pairs)

$(DDOC_BLANKLINE ) $(P An Associative Array can be iterated in key/value fashion using a $(DDSUBLINK spec/statement, ForeachStatement, foreach statement). As an example, the number of occurrences of all possible substrings of length 2 (aka 2-mers) in a string will be counted:) $(DDOC_BLANKLINE ) $(SPEC_RUNNABLE_EXAMPLE_RUN $(D_CODE $(D_KEYWORD import) std.range : slide; $(D_KEYWORD import) std.stdio : writefln; $(D_KEYWORD import) std.utf : byCodeUnit; $(D_COMMENT // avoids UTF-8 auto-decoding ) $(D_KEYWORD int)[string] aa; $(D_COMMENT // The string `arr` has a limited alphabet: {A, C, G, T} )$(D_COMMENT // Thus, for better performance, iteration can be done _without_ decoding )$(D_KEYWORD auto) arr = $(D_STRING "AGATAGA").byCodeUnit; $(D_COMMENT // iterate over all pairs in the string and count each pair )$(D_COMMENT // $(LPAREN)'A', 'G'$(RPAREN ), $(LPAREN)'G', 'A'$(RPAREN ), $(LPAREN)'A', 'T'$(RPAREN ), ... )$(D_KEYWORD foreach) (window; arr.slide(2)) aa[window.source]++; $(D_COMMENT // source unwraps the code unit range ) $(D_COMMENT // iterate over all key/value pairs of the Associative Array )$(D_KEYWORD foreach) (key, value; aa) { writefln($(D_STRING "key: %s, value: %d"), key, value); } ) ) $(CONSOLE $(GT ) rdmd count.d key: AT, value: 1 key: GA, value: 2 key: TA, value: 1 key: AG, value: 2 ) ) $(DDOC_BLANKLINE ) $(SPEC_SUBNAV_PREV_NEXT arrays, Arrays, struct, Structs and Unions) )