Подтвердить что ты не робот

Почему я не могу хранить строковые ключи в ассоциативном массиве?

Я новичок в языке программирования D, только что начал читать книгу "Язык программирования D".

Я сталкиваюсь с ошибкой при попытке использовать пример кода ассоциативного массива

#!/usr/bin/rdmd
import std.stdio, std.string;

void main() {
    uint[string] dict;
    foreach (line; stdin.byLine()) {
        foreach (word; splitter(strip(line))) {
            if (word in dict) continue;
            auto newId = dict.length;
            dict[word] = newId;
            writeln(newId, '\t', word);
        }   
    }   
}

DMD показывает это сообщение об ошибке:

./vocab.d(11): Ошибка: ассоциативным массивам могут быть назначены только значения с неизменяемыми клавишами, а не char []

Я использую компиляцию DMD 2.051

Я предполагал, что правила для ассоциативных массивов изменились со времен книги TDPL.

Как использовать ассоциативные массивы со строковыми клавишами?

Спасибо.

Update:

Я нашел решение в последующих частях книги.

используйте string.idup, чтобы сделать дублирующее неизменяемое значение перед помещением в массив.

так

dict[word.idup] = newId;

выполнит эту работу.

Но эффективен?

4b9b3361

Ответ 1

Ассоциативные массивы требуют, чтобы их ключи были неизменными. Это имеет смысл, когда вы думаете о том, что, если он не является неизменным, тогда он может измениться, а это означает, что его хеш изменяется, а это означает, что когда вы снова получите значение, компьютер не найдет его. И если вы замените его, вы получите другое значение, добавленное в ассоциативный массив (так что у вас будет один с правильным хешем и один с неправильным хэшем). Однако, если ключ неизменен, он не может измениться, и поэтому такой проблемы нет.

До dmd 2.051 этот пример работал (который был bug). Теперь он исправлен, поэтому пример в TDPL больше не правильный. Однако это не так, что правила для ассоциативных массивов изменились, поскольку в них была ошибка, которая не была поймана. Пример составлен, когда он не должен был, и Андрей пропустил его. Он указан в официальных ошибках для TDPL и должен быть исправлен в будущих печатных изданиях.

Исправленный код должен использовать либо dictionary[word.idup], либо dictionary[to!string(word)]. word.idup создает дубликат word, который является неизменным. to!string(word), с другой стороны, преобразует word в string наиболее подходящим образом. Поскольку word является char[] в этом случае, это будет использовать idup. Однако, если word уже был string, тогда он просто вернул бы значение, которое было передано и не обязательно скопировано. Итак, в общем случае to!string(word) - лучший выбор (особенно в шаблонных функциях), но в этом случае либо работает просто отлично (to!() находится в std.conv).

Технически возможно использовать a char[] для string, но, как правило, это плохая идея. Если вы знаете, что char[] никогда не изменится, вы можете с ним справиться, но в общем случае вы рискуете проблемами, поскольку компилятор тогда предположит, что результат string никогда не изменится, и это может генерировать неверный код. Это может быть даже segfault. Таким образом, не делайте этого, если профилирование не показывает, что вам действительно нужна дополнительная эффективность, чтобы избежать копирования, вы не можете иначе избежать копирования, сделав что-то вроде просто используя string в первую очередь (так что никакое преобразование не было бы необходимо), и вы знаете, что string никогда не будет изменен.

В общем, я бы не стал слишком беспокоиться об эффективности копирования строк. Как правило, вы должны использовать string вместо char[], чтобы вы могли скопировать их (то есть скопировать их ссылку вокруг (например, str1 = str2;), а не копировать все их содержимое, например dup и idup do) не беспокоясь о том, что это особенно неэффективно. Проблема с примером заключается в том, что stdin.byLine() возвращает char[], а не string (предположительно, чтобы избежать копирования данных, если это не необходимо). Итак, splitter() возвращает a char[], поэтому word является char[] вместо string. Теперь вместо idup введите ключ splitter(strip(line.idup)) или splitter(strip(line).idup). Таким образом, splitter() вернет string, а не char[], но, вероятно, по существу так же эффективен, как idup ing word. Независимо от того, откуда исходный текст начинается, вместо string следует char[], который заставляет вас idup его где-то вдоль строки, если вы намереваетесь использовать его как ключ в ассоциативном массиве. В общем случае, однако, лучше просто использовать string, а не char[]. Тогда вам не нужно idup что-либо.

EDIT:
На самом деле, даже если вы обнаружите ситуацию, когда кастинг от char[] до string кажется безопасным и необходимым, рассмотрите возможность использования std.exception.assumeUnique() (документации). Это, по сути, предпочтительный способ преобразования изменчивого массива в неизменяемый, когда вам нужно и знать, что вы можете. Обычно это делается в тех случаях, когда вы создали массив, который вы не могли бы сделать неизменным, потому что вам приходилось делать это кусочками, но у которого нет других ссылок, и вы не хотите создавать его глубокую копию. Это не было бы полезно в таких ситуациях, как пример, о котором вы спрашиваете, поскольку вам действительно нужно скопировать массив.

Ответ 2

Нет, он неэффективен, поскольку он явно дублирует строку. Если вы можете гарантировать, что созданная вами строка никогда не будет изменена в памяти, не стесняйтесь явно использовать листинг cast(immutable)str на ней, а не дублировать ее.

(Хотя, я заметил, что сборщик мусора работает хорошо, поэтому я предлагаю вам на самом деле не попробовать, если вы не видите узкое место, поскольку вы можете решить изменить строку позже. Просто поместите комментарий в свой код чтобы помочь вам найти узкое место позже, если оно существует.)