我正在开发一款用于科学研究的软件,该软件主要涉及化学配方。我使用内部Dictionary<Isotope, int>
来跟踪化学式的内容,其中Isotope
是一个像"碳-13"、"氮-14"这样的物体,而int
代表化学式中这些同位素的数量。所以公式C2H3NO是这样存在的:
{"C12", 2
"H1", 3
"N14", 1
"O16", 1}
这一切都很好,但当我想把两个化学公式加在一起时,我最终不得不计算两次Isotope
的哈希函数来更新一个值,请参阅下面的代码示例。
public class ChemicalFormula {
internal Dictionary<Isotope, int> _isotopes = new Dictionary<Isotope, int>();
public void Add(Isotope isotope, int count)
{
if (count != 0)
{
int curValue = 0;
if (_isotopes.TryGetValue(isotope, out curValue))
{
int newValue = curValue + count;
if (newValue == 0)
{
_isotopes.Remove(isotope);
}
else
{
_isotopes[isotope] = newValue;
}
}
else
{
_isotopes.Add(isotope, count);
}
_isDirty = true;
}
}
}
虽然这看起来不会很慢,但当我们将数十亿个化学配方添加在一起时,这种方法始终是程序中最慢的部分(>45%的运行时间)。我正在处理像"H5921C3759N1023O1201S21"这样的大型化学配方,这些化学配方一直被添加到较小的化学配方中。
我的问题是,有没有更好的数据结构来存储这样的数据?我尝试创建一个包含int
的简单IsotopeCount
对象,这样我就可以访问引用类型(而不是值类型)中的值,以避免双重哈希函数。然而,这似乎并没有什么好处。
编辑Isotope
是不可变的,在程序的生命周期内不应该更改,所以我应该能够缓存哈希代码。
我已经链接到源代码,这样你就可以更深入地看到这些类,而不是我在这里复制粘贴它们。
我支持Isotope
应该使用预先计算的哈希来实现不可变的观点。这会让一切变得简单得多。
(事实上,面向功能的编程更适合这种类型的计算,并且它处理不可变的对象)
我尝试创建一个包含int的简单同位素计数对象,这样我就可以访问引用类型(而不是值类型)中的值,以避免双重哈希函数。然而,这似乎并没有什么好处。
好吧,这会停止双重哈希。。。但很明显,在空间方面情况更糟。您注意到了什么性能差异?
如果你经常这样做,你应该强烈考虑的另一个选项是在Isotope
类中缓存哈希,假设它是不可变的。(如果不是,那么将其用作字典关键字至少有点令人担忧。)
如果您可能使用大多数Isotope
值作为字典键(或候选者),那么在初始化期间计算散列可能是值得的。否则,选择一个特别不可能的哈希值(在理想情况下,它可以是任何值),并将其用作"未缓存"值,然后懒洋洋地计算它。
如果你在GetHashCode
中有45%的运行时间,你有没有考虑过优化它?问题究竟是GetHashCode
还是Equals
?(你说的是"散列",但我怀疑你的意思是"一般的散列查找"。)
如果你能发布Isotope
类型的相关比特,我们可能会提供更多帮助。
EDIT:如果您使用.NET 4,考虑的另一个选项是ConcurrentDictionary
及其AddOrUpdate
方法。你会这样使用它:
public void Add(Isotope isotope, int count)
{
// I prefer early exit to lots of nesting :)
if (count == 0)
{
return 0;
}
int newCount = _isotopes.AddOrUpdate(isotope, count,
(key, oldCount) => oldCount + count);
if (newCount == 0)
{
_isotopes.Remove(isotope);
}
_isDirty = true;
}
您实际上需要按类型随机访问同位素计数吗?还是使用字典将键与值关联起来?
我猜是后者。
我给你的建议是不要使用字典,而是使用同位素元组的排序数组(或列表),比如:
class IsotopeTuple{
Isotope i;
int count;
}
按同位素的名称排序。
为什么要排序?
因为,当你想把两种同位素"相加"在一起时,你可以在线性时间内通过遍历两个数组来做到这一点(希望这一点很清楚,如果需要,我可以详细说明)。不需要哈希计算,只需要超快速的顺序比较。
当处理维度为单词的向量乘法时,这是一种经典的方法。广泛用于文本挖掘。
当然,代价是初始向量的构造是(n)log(n),但我怀疑你是否会感受到这种影响。
如果同位素数量有限且没有内存问题,您可以想到的另一个解决方案:
public struct Formula
{
public int C12;
public int H1;
public int N14;
public int O16;
}
我猜你在研究有机化学,所以你可能不需要处理那么多同位素,如果查找是个问题,这个会很快。。。