C#克隆图形并更新循环引用

时间:2019-02-05 22:23:50

标签: c# design-patterns

让我在开始之前先声明我看到了与此类似的帖子,但是没有一个解决方案令我满意和/或应用于C#。

我有一个Graph类,它由NodeConnection个对象组成。该图包含由与其关联的所有子Node和Connection对象组成的集合。除此之外,每个Node都有一个Connection对象的集合。

请注意:这是一个简化的玩具问题。您可以查看实际的(进行中的)生产代码here。在生产中,NeuronNode,而AxonConnection

public class Graph : IDeepCloneable<Graph>
{
  // These are basically just Dictionary<int,object>s wrapped in an ICollection
  public NodeCollection Nodes;
  public ConnectionCollection Connections;

  public Graph Clone()
  {
    return new Graph
    {
      Nodes = this.Nodes.Clone(),
      Connections = this.Connections.Clone()
    };
  }
}

public class Node : IDeepCloneable<Node>
{
  public int Id;
  public NodeConnectionCollection Connections;
  // NodeConnectionCollection is more or less the same as NodeCollection
  // except that it stores Connection objects into '.Incoming' and '.Outgoing' properties

  public Node Clone()
  {
    return new Node
    {
      Id = this.Id,
      Connections = this.Connections.Clone()
    };
  }
}

public class Connection : IDeepCloneable<Connection>
{
  public int Id;
  public Node From;
  public Node To;

  public Connection Clone()
  {
    return new Connection
    {
      Id = this.Id,
      From = this.From.Clone(),
      To = this.To.Clone()
    };
  }
}

public class ConnectionCollection : ICollection<Connection>, IDeepCloneable<ConnectionCollection>
{
  private Dictionary<int, Connection> idLookup;
  private Dictionary<ProjectionKey, Connection> projectionLookup;

  public int Count => idLookup.Count;
  public bool IsReadOnly => false;

  public Add( Connection conn )
  {
    idLookup.Add( conn.Id, conn );
    projectionLookup.Add( new ProjectionKey( conn.From, conn.To ), conn );
  }

  ...

  internal struct ProjectionKey
  {
    readonly intFrom;
    readonly int To;
    readonly int HashCode;

    public ProjectionKey( int from, int to )
    {
      From = from;
      To = to;
      HashCode = ( 23 * 397 + from ) * 397 + to;
    }

    public override int GetHashCode() { return HashCode; }
  }
}

public class NodeCollection : ICollection<Node>, IDeepCloneable<NodeCollection>
{
  private Dictionary<int, Node> nodes;
  private Dictionary<int, InputNode> inputNodes;
  private Dictionary<int, InnerNode> innerNodes;
  private Dictionary<int, OutputNode> outputNodes;

  ...

  public Node this[ int id ]
  {
    get => nodes[ id ];
  }

}

每个对象都支持深度克隆,其主要思想是使用类可以在子类上调用Clone()并以此方式处理堆栈。

但是,这在生产中不可行。对Graph.Clone()的调用将克隆NodeCollectionConnectionCollection字段,这将克隆存储在其中的每个NodeConnection实例,而每个实例都将克隆其他引用子元素。

一个常见的解决方案似乎是存储每个子对象的ID,然后在完成所有克隆后重建引用。但是,据我所知,这需要引用父对象并紧密耦合数据结构。

我对如何正确处理这个问题感到非常困惑。 我需要合理的性能,因为我的应用程序(遗传算法)会不断执行克隆操作,但是在这种情况下,我对寻找一种鲁棒的设计模式或实现方式更感兴趣。进行此图结构的深层克隆,同时在后台隐藏大量繁琐的工作。

是否有任何设计模式可以让我按原样克隆此数据结构,同时更新循环引用并保持其完整性?

1 个答案:

答案 0 :(得分:1)

我的建议是将解决问题的方法从克隆更改为重新创建。我已经解决了一个类似的问题,即保存从用户界面手动创建的图形用户,然后在导入保存的图形时重新创建它。考虑一下,听起来几乎是一样的。

所以我想出的解决方案是从中央控制序列化图形(考虑到您正在使用启发式修改图形,我假设您对图形具有中央控制)。即使您没有图形的中央控制,我相信也可以通过某种方式遍历它来获取所有信息。

最简单的图形是邻居信息的集合。

也可以是有向的或无向的

1 -> 2
1 -> 3
3 -> 2

因此,如果您想出一种生成此类列表的方法,则只需调整此简单列表即可创建新图形。

或者另一种方法是列出您的节点及其邻居,如下所示,

1, [2,3]
3, [2]

在我看来,这甚至更容易重新创建图形。

如果您对此感到好奇,这里是我运用这种方法的项目中的file-我认为这并不是答案或问题的参考。