【问题标题】:Removing duplicates in a nested, cross-referenced list删除嵌套的交叉引用列表中的重复项
【发布时间】:2021-11-29 15:26:33
【问题描述】:

我知道标题可能具有误导性,常见的答案可能是“再次搜索”,但是我已经尽可能多地搜索堆栈溢出,但没有找到让我满意的答案。

简介

我需要创建一个算法来删除嵌套的交叉引用列表中的所有相似项。

一般规则:

  • 项可以有子项并且可以引用其他项。
  • 对象不能同时引用另一个对象和子对象。
  • 一个列表中可以有很多对象(最多 100k),对于子对象的深度总是最多为 1 级,但每个对象都是未知的可变长度引用链。
  • 引用链不是无限的,它们通常有多达 5-15 个元素,并且总是以已知 ClassType 字段的对象结束。
  • 可能存在“死链” - 没有被任何对象引用但它们引用其他对象的项。
  • 对象的子对象不是列表的一部分 - 它们可以引用其他元素,但不能单独引用。
  • 为了比较两个对象,它们的属性以及它们的子对象(如果存在)和引用对象(如果存在)必须相同。
  • 比较子对象或引用对象时,适用相同的规则。
  • 为每个对象分配唯一 ID(这不是哈希码,而不是对象创建的顺序) - 尽管两个对象具有不同的 ID,但它们将是相同的。不会有具有相同 ID 的对象。

未指定内存要求和 CPU 占用率 - 首选高性能。我目前的方法(将每个对象与另一个对象进行比较的蛮力方法)速度很慢,可能需要长达 1 小时的运行时间。首选执行速度最多为 1 分钟。我的方式也是无限次地迭代集合,因为找到了相同的对象并且它只使用单线程。我很想获得多线程更具确定性的方法。

说明

考虑以下对象定义:

public enum ClassType
{
  Type_Base,
  Type_A,
  Type_B,
  Type_C,
};

public class MyObject
{
  public string Name { get; }
  public uint ID { get; }
  public ClassType ClassType { get; }

  public uint ReferencedID { get; }
  public List<MyObject> Children { get; } = new List<MyObject>( );
}

还有一个以 MyObject 的 ID 属性为索引的集合,用于快速访问:

Dictionary<uint, MyObject> Preserved; // Top-most objects, has to be preserved
Dictionary<uint, MyObject> AllObjects; // Dictionary of all objects

另外,考虑以下比较方法:

public static bool AreObjectsSame( MyObject l, MyObject r, Dictionary<uint, MyObject> allObjects )
{
  // Null-Check
  if ( l is null ) throw new ArgumentNullException( nameof( l ) );
  if ( r is null ) throw new ArgumentNullException( nameof( r ) );
  if ( allObjects is null ) throw new ArgumentNullException( nameof( allObjects ) );

  // Compare class type and name
  if ( l.ClassType != r.ClassType ) return false;
  if ( l.Name != null && r.Name != null && !l.Name.Equals( r.Name, StringComparison.InvariantCulture ) ) return false;
  
  // Compare referenced ID objects
  if ( l.ReferencedID != 0 && r.ReferencedID != 0 )
  {
    if ( !AreObjectsSame( allObjects[ l.ReferencedID ], allObjects[ r.ReferencedID ], allObjects ) ) return false;
  }

  // Compare children objects
  if ( l.Children.Count != r.Children.Count ) return false;
  for ( int i = 0; i < l.Children.Count; ++i )
  {
    if ( !AreObjectsSame( l.Children[ i ], r.Children[ i ], allObjects ) ) return false;
  }

  return true;
}

现在让我们检查一个对象链。有孩子的对象以类似的方式工作,因此为了解释简单,我将跳过它们。

MyObject ("Name_A", Type_A) -> MyObject ("Name_B", Type_B) -> MyObject ("Name_C", Type_A) -> MyObject ("Name_D", Type_Base)
MyObject ("Name_E", Type_C) -> MyObject ("Name_F", Type_C) -> MyObject ("Name_C", Type_A) -> MyObject ("Name_D", Type_Base)
MyObject ("Name_G", Type_A) -> MyObject ("Name_D", Type_Base)

如您所见,我们在 AllObjects 字典中放置了 10 个对象。在每个引用链中,我们都有对象 ("Name_C", Type_A)("Name_D", Type_Base)。由于对象 ("Name_D", Type_Base) 是相等的(它们具有相同的类型和名称),对象 ("Name_C", Type_A) 也是相等的(它们具有相同的类型和名称加上它们引用的对象是相同的)。

现在,我们可以将上面的对象链优化成这样:

MyObject ("Name_A", Type_A) -> MyObject ("Name_B", Type_B) -> MyObject ("Name_C", Type_A) -> MyObject ("Name_D", Type_Base)
MyObject ("Name_E", Type_C) -> MyObject ("Name_F", Type_C) ->-|
MyObject ("Name_G", Type_A) -------------------------------->-/ 

这导致我们删除 3 个重复的节点并重新排列其中两个的参考 ID。

可能的解决方案

一种可能的解决方案是为每个元素预先计算其自己的哈希码,并使用此哈希以蛮力方式比较元素。如果比较为真,则执行逐字段比较(以消除哈希码中可能的冲突)。

有没有更好的删除重复项的方法?我该怎么做?

【问题讨论】:

    标签: c# reference nested duplicates cross-reference


    【解决方案1】:

    一些快速的建议:

    1. 是的,预先计算哈希码并仅对具有相同哈希码的项目进行深入比较
    2. 在 A 对 B 进行过检查后,不要再对 B 和 A 进行检查,按某种顺序获取项目,并且仅针对大于 X 的索引检查索引 X 处的项目
    3. 比较时不要删除项目,而是将要保留的项目收集在单独的集合中,并将原始的放在最后
    4. 在遍历所有项目的算法期间使用可索引的集合,如数组。

    但首先使用一些分析工具,例如 Redgate 的 Performance Profiler。很多时候,我用我的直觉“优化”算法,但没有取得任何可衡量的成功,但却使它变得更难理解。在尝试了探​​查器之后,结果证明一个谁会想到那个代码,例如字符串操作,过度使用的对象实例化比我的算法花费更多的时间!分析器可以为您指出真正需要优化的代码:)

    【讨论】:

      猜你喜欢
      • 2016-10-19
      • 2018-02-18
      • 1970-01-01
      • 2020-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      相关资源
      最近更新 更多