将多个对象快速映射到另一个对象的数据结构

时间:2014-06-18 11:06:24

标签: c# algorithm dictionary data-structures

我的应用程序需要不同的数据结构。

基本上我有一个由"节点"组成的自定义数据结构。 我的任务如下: 给定了许多不同的节点(我获得的节点数量未知)检索或创建新节点。它让我想起了具有多个参数的函数的缓存。唯一的区别是所有参数和返回值具有相同的类型,我返回的值可能会在稍后作为输入提供给我。

示例1 : 起初我得到节点A和C.现在我必须创建一个新节点(让它命名为AC)并返回它。 当我将来再次获得节点A和C 时,我需要能够非常快地确定我是否已经创建了AC节点并将其返回,或者,如果之前没有创建它,创建它并返回它。

示例2 : 当我得到节点C和A然后我必须返回/创建一个不同的节点!我无法返回AC,它必须是一个新节点(CA)。订单很重要!

稍后在处理它时,我也可能获得我之前创建的节点。 例如,在第三次调用我的数据结构时,我完全有可能收到节点" A和AC"。我必须再次创建一个新节点" A-AC",将其缓存并返回。

起初我使用了很多Dictionary<Tuple<Node, Node>, Node>,但这有很多问题:   - 创建和比较元组对我的应用来说太慢了   - 参数的数量是固定的,我需要多个字典,每个字典都有不同的键(2元组,3元组,......)

我也有很多节点。我已经过滤了一些输入数据,但我必须处理至少1500万到2000万个不同的节点。

字典似乎没有削减它,性能和内存消耗似乎太高了。

我可以自由修改节点的实现方式,这样可能还有另一种技巧可以直接将多个节点链接到另一个节点?

如何尽可能有效地解决这个问题? 通常使用什么数据结构来解决这个问题?

2 个答案:

答案 0 :(得分:2)

这是何时使用也称为Trie的数字搜索树的完美示例。基本上每个节点都有一个节点数组和一个空节点。当您按照示例1到A中的方式向下工作,然后到C,如果该C节点引用了空节点,那么您就知道已经加载了该节点。如果没有那么它还没有加载。我不认为有任何内置的trie实现,但它们并不难构建。我建了一次来存储英文字典并用它来查看是否存在单词。如果你正确构建它会占用内存中没有空间,并且访问时间为O(1)。

答案 1 :(得分:2)

似乎你有很多限制(时间,效率,内存占用)。说实话,我不知道你在哪里设置限制条件。

我曾经创建了一个小型数据结构,可以完成类似于你想要的东西。我想。

public class StackBlock
{
    public string Component { get; set; }
    public MyObject ResultingObject { get; set; }
    public List<StackBlock> Blocks { get; set; }
}

这个想法是你使用它们来构造一个树,它可以作为已经创建的对象的缓存。对属性的简要描述:

  • 您可以在此处存储"A""C"
  • ResultingObject是您的缓存项目"AC"
  • Blocks是您用来创建块链的方法。

因此,如果您想存储"AC"对象,这将是您保存的结构:

StackBlock
    Component: "A"
    ResultingObject: null
    Blocks: [
                StackBlock
                    Component: "C"
                    ResultingObject: MyObject "AC"
                    Blocks: [ ... ]
            ]

编辑非常简单地说,项目&#34; CGA&#34;将在:

中找到
StackBlock "C" -> StackBlock "G" -> StackBlock "A" -> ResultingItem

您可以将堆叠块保持在一起,以实现更长和更长的组合。但是当你需要检索一个对象时,你所要做的就是按顺序遍历树:

  • 查找StackBlock A。
  • 在StackBlock A中,找到子堆栈C。
  • 在子StackBlock C中,查看ResultingObject。如果它不为null,则返回缓存的对象。

请注意,对于每个步骤,如果找不到要查找的内容,则表示尚未创建,因此您必须创建对象,然后将其存储在树中。下次您提出申请时,它现在可用。

在你的情况下,&#34; AC&#34;和&#34; CA&#34;是不同的对象,树允许您将它们存储在不同的位置。

这也将确保您在对象已经在内存中时不创建对象,因为树结构只允许一个地方放置特定元素

我希望这有点朝你想要的方向发展?

注意:在LINQ推出之前,这是我的一个非常古老的项目。我只能想象当您使用LINQ遍历树时,生成的代码相当优雅和简洁。我会饶你以前的做法。

回答以下评论

如果您有项目&#34; GA&#34;和项目&#34; CGA&#34;,它们不会成为相同 StackBlock&#34; A&#34;的一部分。 如果您已经缓存了这两个对象,那么树将如下所示:

StackBlock C
    -> StackBlock G
        -> StackBlock A
            -> ResultingObject CGA        
StackBlock G
    -> StackBlock A
        -> ResultingObject GA

注意:您将上层元素存储在List中。从该列表中,您可以找到第一个元素并开始向下钻取。

我想解决一个可能的混淆点:你看到两个Stackblocks&#34; G&#34;和两个StackBlocks&#34; A&#34;。 这些不是同一个对象。您在我的所有示例中看到的每个堆栈都是不同的对象(恰好有相同的字母)。

如果将StackBlock定义为结构而不是类,那么理解它可能会更好。它的工作方式相同,并且您无法在正在构建的树的不同层上重复使用相同的StackBlock。

因此,ResultingObject不应该是一个列表,因为我们应该只有一个对象叫做#34; CGA&#34;。练习的重点是防止创建重复的对象,因此这个数据结构专门定制为只允许一个地方放置缓存的对象。

如果我充实了这个例子,也许会有所帮助,所以你可以看到一切都会结束的地方:

StackBlock C
    -> StackBlock G        
        -> ResultingObject CG
        -> StackBlock A
            -> ResultingObject CGA  
        -> StackBlock B
            -> ResultingObject CGB

StackBlock G
    -> ResultingObject G
    -> StackBlock A
        -> ResultingObject GA
        -> StackBlock X
            -> ResultingObject GAX
            -> StackBlock K
                -> ResultingObject GAXK

查看名为&#34; G&#34;的两个Stackblock。其中一个位于顶层,因此它的ResultingObject只是G.但另一个是二级StackBlock。因此,它的ResultingObject是CG,因为你必须考虑你钻下的整个链

我希望这有助于澄清它。一旦你理解了它,它就是一个简单的概念,但是我在描述它为什么会起作用时遇到一些困难:)