比较不可变数据类型

时间:2012-05-29 20:33:47

标签: c# .net algorithm collections immutability

是否有一种普遍接受的方法来比较可能包含长值列表的不可变对象?

到目前为止,我的界面如下:

interface Formula : IEquatable<Formula> {
   IList<Symbol> Symbols {get;}
}

interface Symbol : IEquatable<Symbol> {
   String Value {get;}
}

这里,不可变数据类型Formula表示Symbol的序列。所以在公式中:

x -> y

符号为x->y

我想根据内容比较两个公式(例如符号列表)。因此,对于某些任意符号列表,new Formula(symbols)将等于new Formula(symbols)

但是,我不想一直迭代地比较两个列表。

在实现中,我在考虑在Formula的初始化期间创建某种计算值 - 并将其用于比较。但是,这将要求我向我的界面添加某种方法。我怎么称呼这种方法?

我不确定是否适合使用哈希代码,因为它似乎仅限于整数。

任何帮助表示赞赏 - 如果不清楚,我会修改我的问题。 谢谢!

5 个答案:

答案 0 :(得分:5)

你绝对可以使用哈希码。不要忘记哈希码不必是唯一 - 如果它不经常发生冲突(两个具有相同哈希码的不相等序列),它就会有所帮助。 (至少,尝试提出一种避免明显的情况的相同哈希码的方法。)

因此,您可以在构造时计算哈希码一次(通过依次组合每个符号的哈希码),然后从GetHashCode返回该值,而不必每次重新计算它。这意味着你只需要比较具有相同哈希码的序列 - 这对于非等同序列很少发生。

答案 1 :(得分:2)

不,你必须比较所有元素。您不能使用哈希代码或类似方法,因为可能的公式集是无限的,而可能的哈希代码集是有限的。

正如Jon Skeet所说,你可以使用哈希码来减少逐个元素地比较公式的需要,但你不能消除的需要。当两个公式具有不相等的哈希码时,您知道公式是不相等的,但是当它们具有相同的哈希码时,您将需要进行逐元素比较以查看它们是否相等。

答案 2 :(得分:1)

我相信这不是你需要做的全部......

a+b = (a+b)

会因你的方法而导致错误。

我相信你必须为两边的表达式构造AST(抽象语法树),然后比较表达式。由于它们在AST中表示为层次结构,因此AST将取消该合成。

HTH

马里奥

答案 3 :(得分:1)

这有点像重写GetHashCode的其他答案,但我有不同的方法.... 由于公式似乎有字符串表示....

你不能覆盖GetHashCode并在覆盖中做一个

foreach(char c in ToString().ToCharArray()){

int hashCode |= c;

}

这样的结果将产生一个4字节的代码,这是代码中符号的压缩表示...

如果每个符号具有可在HashTable中查找的特定OpCode,则可以进一步采取此措施。

我将使用每个OpCode的别名来构建HashTable,这样每个Symbol都不必声明属性OpCode。

然后我会在Symbol类上创建一个扩展ToOpCode,它在上面描述的HashTable中进行查找。

然后我会在GetHashCode中使用Extension方法,例如

...式

public override int GetHashCode(){

    foreach(Symbol c in Symbols){

       int hashCode |= c.ToOpCode();

    }

}

符号...

public override int GetHashCode(){
    retuurn Extensions.ToOpCode(this);

}

这个实现会为a + b和b + a产生相同的哈希值,这对你的问题非常重要......

此外,如果您以正确的顺序指定OpCode,您将在技术上能够以下列形式比较方程式:

(a) + (b) == (a+b)

这可以通过确保括号操作码在HashCode中的值与数字不同的位置来实现......

E.g。如果你有4个字节(一个整数),范围深度可以保留在第一个字节中,堆栈中前一个或下一个方程/符号的索引将是下一个,接下来的两个字节将保留给符号数据和值等式中的/ continuation或变量数量(不包括)。

这允许您告诉某些事情,例如嵌套级别等等,因此您基本上可以覆盖等于以确保您可以区分a + bb + a以及((a) + (b))(如果需要)

例如,您可能想知道方程式是否与某种方法完全相同,但在另一种方法中,您可能想知道方程式是否做了相同的事情但是没有用同样的方法编写。

这也允许您以不同的方式确定相等性,例如检查范围深度是否匹配以及方程中是否存在完全相同的步骤数量而不是仅基于哈希代码假设...

e.g。然后,您可以按如下方式进行调整,以确定以下内容:

hash&lt;&lt; 8将是parens的部门 哈希&lt;&lt; 16将是堆栈的前一个或下一个方程指针 哈希&lt;&lt; 24将是等式中的符号或代码值延续或变量数量(不包括)

你也可以只做hash == anotherHash,但这种方式可以让你更灵活,没有任何开销。

如果你需要更多空间,那么创建一个新的方法GetExtendedHashCode,它返回long然后shift / downcast或重新格式化GetHashCode中的ExtendedHashCode以匹配CLR所需的int格式。

您还可以通过将符号保留在堆栈中并将它们像CLR一样使用它们来以这种方式表示变量和值的符号。

答案 4 :(得分:0)

首先,我建议不要为任何非密封类型IEquatable<T>实施T。在未密封类型上实现IEquatable<T>.Equals的唯一安全方法通常是调用虚方法Object.Equals。否则,其父类为一个或多个类型IEquatable<T>实现T的类可能会覆盖Object.EqualsObject.GetHashCode,而无需重新实现其所有IEquatable<T> Formula 1}}接口;因此,任何未重新实现的接口都将被破坏。

其次,如果在比较两个Symbol实例中的列表时,会发现一对相应的System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode()引用是等效的但引用不同的实例,调用{{1}可能会有所帮助在每个实例上。如果比较大于另一个,则将具有较大RunTimeHelpers.GetHashCode()值的引用替换为另一个列表中的值。这将加速未来对这些列表的比较。此外,如果重复比较具有相同项目的多个列表,则所有列表将“倾向于”具有相同的Symbol个实例。

最后,如果发现列表相同,并且列表应该是“语义上”不可变的,那么可以使用相同的RuntimeHelpers.GetHashCode()技巧来选择List实例。这将加快未来的比较。