为什么我不能将字符串键存储在关联数组中?

时间:2011-01-06 03:30:43

标签: string d associative-array

我是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书以来已经发生了变化。

如何将关联数组与字符串键一起使用?

感谢。

更新

我在本书的后半部分找到了解决方案。

在放入数组之前使用string.idup创建重复的不可变值。

所以

dict[word.idup] = newId;

会做这个工作。

但这样有效吗?

2 个答案:

答案 0 :(得分:25)

关联数组要求它们的键是不可变的。当你想到如果它不是不可变的那个事实时它就有意义了,那么它可能会改变,这意味着它的哈希值会发生变化,这意味着当你再次获取值时,计算机将无法找到它。如果你去替换它,你将最终添加到关联数组的另一个值(所以,你将有一个具有正确的哈希值和一个具有不正确的哈希值)。但是,如果密钥是不可变的,则它不能改变,因此没有这样的问题。

在dmd 2.051之前,该示例有效(这是一个bug)。它现在已被修复,因此TDPL中的示例不再正确。然而,关联数组的规则已经发生了变化,因为它们中存在一个未被捕获的错误。这个例子在不应该编译时编译,而Andrei错过了它。它列在official errata for 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)。

技术上可以将char[]投射到string,但这通常是一个坏主意。如果你知道 char[]将永远不会改变,那么你可以侥幸逃脱它,但在一般情况下,你冒着问题,因为编译器会假设结果string永远不会改变,它可能会生成不正确的代码。它甚至可能是段错误。所以,不要这样做,除非分析显示你真的需要避免副本的额外效率,否则你不能通过做一些事情来避免副本,比如首先使用string(所以没有转换将是必要的),并且知道 string将永远不会被更改。

一般来说,我不会过分担心复制字符串的效率。通常,您应该使用string而不是char[],因此您可以复制它们(即复制它们的引用(例如str1 = str2;),而不是复制它们的全部内容,如{{1并且dup做)而不用担心它特别低效。该示例的问题是idup返回stdin.byLine()而不是char[](可能是为了避免在没有必要的情况下复制数据)。因此,string会返回splitter(),因此char[]word而不是char[]。现在,您可以执行stringsplitter(strip(line.idup))而不是splitter(strip(line).idup)密钥。这样,idup将返回splitter()而不是string,但这可能基本上与char[] idup一样有效。无论如何,由于文本来自最初的文本,它是word而不是char[],如果您打算将其用作{a} string,它会迫使您idup沿着该行的某个位置移动它关联数组中的键。但是,在一般情况下,最好只使用string而不是char[]。那么你不需要idup任何东西。

修改
实际上,即使您发现从char[]string的投射似乎既安全又必要,请考虑使用std.exception.assumeUnique()documentation)。当你需要并知道可以的时候,它本质上是将可变数组转换为不可变数组的首选方法。它通常会在你构造了一个你无法创建的数组的情况下完成,因为你必须分成几部分但没有其他引用,并且你不想创建它的深层副本。但是在你要问的例子中它没有用,因为你真的需要复制数组。

答案 1 :(得分:1)

不,它效率不高,因为它显然会复制字符串。如果您保证您创建的字符串将从不在内存中修改,请随意在其上显式使用强制转换cast(immutable)str,而不是复制它。

(虽然,我注意到垃圾收集器运行良好,所以我建议你实际上不要尝试,除非你看到瓶颈,因为你可能决定稍后更改字符串。只需在你的代码中添加注释如果存在,可以帮助您找到瓶颈。)