【问题标题】:Replace and Merge "Duplicates" From a HashSet替换和合并 HashSet 中的“重复项”
【发布时间】:2012-12-03 01:48:31
【问题描述】:

我在 C# 4.0 中有一个分层对象的HashSet<T>。主键是 int,但偶尔会有重复的辅助键。我想将条目与重复的辅助键合并。在此示例中,辅助键是名称:

struct Element
{
  int ID;
  string Name;
  List<int> Children;
  List<int> Parents;

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

HashSet<Element> elements = new HashSet<Element>();

// Example Elements
elements.Add(1, "Apple", Children = {10, 11, 12}, Parents = {13,14,15});
elements.Add(2, "Banana", Children = {20, 21, 22}, Parents = {23,24,25});
elements.Add(3, "Apple", Children = {30, 31, 32}, Parents = {33,34,35});
elements.Add(4, "Food", Children = {1, 2, 3}, Parents = {});

目标是删除第 3 个条目 {3, "Apple",...},然后更新并合并其他剩余元素中的 Parent 和 Children 引用;最终结果应该是这样的:

{ 1, "Apple", Children = { 10, 11, 12, 30, 31, 32 }, Parents = { 13,14,15, 33, 34, 35 }}
{ 2, "Banana", Children = { 20, 21, 22 }, Parents = { 23,24,25 }}
{ 4, "Food", Children = {1, 2}, Parents = {} }

这是我目前所拥有的,但我无法找出更新 HashSet 的最佳方法。我首先复制 HashSet,以便在迭代时进行删除。首先,我找到重复项。如果有重复我想更新,他们从副本中删除它们。那就是我卡住的地方。更新重复项后,我想删除它们,并防止使用跳过列表再次处理它们:

var copy = new HashSet<Element>(Elements);
HashSet<int> skip = new HashSet<int>();
foreach (var e in Elements)
{
  if (!skip.Contains(e.ID)
  {
    var duplicates = Elements.Where(x => e.Name == x.Name && e.ID != x.ID);
    if (duplicates.Any())
    {           
      foreach (var d in duplicates)
      {
        // Iterate copy and update Parent and Children references
        // How do I do this part? 
      }

      // Remove the duplicates from the copied list
      copy.RemoveWhere(x => duplicates.Select(x => x.ID)
                                      .Contains(x.ID));

      // Don't process the duplicates again
      skip.UnionWith(duplicates);
    } 
  }
}
return copy;

我被困在这一点上。另外,有没有一种巧妙的方法可以用 Linq 做到这一点?

更新:列表已经这样了,我无法控制初始内容。我想我可以创建一个新的包装器,它有一个更好的 Add 方法来防止重复。

【问题讨论】:

  • 整数ID是干什么用的?您可以使用 Dictionary 字符串是水果名称吗?
  • 在此处使用 HashSet 不会为您的算法或解决方案增加任何价值。 Elements 需要实现GetHashCodeEquals,但是由于您的密钥甚至不是Element,所以它完全没用。您可以通过Dictionary&lt;int, Element&gt; 获得更好的结果,其中密钥与Element.ID 相同。
  • var copy = Elements; 不创建副本。只是对同一件事的另一个引用。
  • @SinaIravanian 不太满足需要,他需要一个复合键。也许将子信息分离到单独的记录中,并将密钥作为实际元素的一部分进行维护?
  • @jcolebrand 我认为他可以通过为Element 结构实现GetHashCodeEquals 来实现创建复合键。只需检查需要相同的字段。

标签: c# linq .net-4.0 duplicates hashset


【解决方案1】:

尝试添加这个单一的字段元素。

struct Element
{
  int ID;
  string Name;
  List<int> Children;
  List<int> Parents;
  Bool duplicate;
}

HashSet<Element> Elements = new HashSet();

// Example Elements
Elements.Add(1, "Apple", Children = {10, 11, 12}, Parents = {13,14,15}, duplicate = false);
Elements.Add(2, "Banana", Children = {20, 21, 22}, Parents = {23,24,25}, duplicate = false);
Elements.Add(3, "Apple", Children = {30, 31, 32}, Parents = {33,34,35}, duplicate = false);
Elements.Add(4, "Food", Children = {1, 2, 3}, Parents = {}, duplicate = false);

在您对副本进行迭代时,将“重复”标记为真。或者添加一个“已删除”元素,这样您就不会重新处理。管他呢。关键是,再添加一个元素。您可以随时复制元素并在添加时创建新元素。

要更早地添加到新浪的 cmets,您可以有这样的密钥:

class ElementKey {
  int ID;
  string Name;
}

class Element {
  ElementKey Key;
  List<int> Children;
  List<int> Parents;
  ProcessFlagSet flags;
}

class ProcessFlagSet {
  bool Processed;
  bool Duplicate;
}

Dictionary<ElementKey,Element> ...

然后您可以稍后从 ProcessFlagSet 中删除所有元素,以满足重构需求。如果您不需要它们,它们会中断编译,直到它们被删除。

最后,我想建议在这里创建您自己的 Add 方法。我希望您考虑传入要添加的元素,然后检查添加时是否存在键。这为您节省了一步。

【讨论】:

  • 我编辑了这个问题。初始 HashSet 中的数据已经存在,而且很大。即使使用新的 Add() 包装器,仍然需要遍历每个元素的父母和孩子并为新添加的元素进行合并。
  • 如果初始列表不包含这些重复项会很好,但确实如此。将它们重新添加到另一个 ADT 中真的是最好的方法吗?
  • 不,最好的办法是在初始插入时删除它们。如果在初始插入时无法删除它们,那么清理操作是唯一的选择。但是,我也很好奇您是否会通过将结构更改为类来看到加速,因为结构在内存中的消耗方式。
【解决方案2】:

你可以试试这个:

var temp = Elements.GroupBy(e => e.Name)
                   .Select(g => new Element
                   {
                       ID = g.OrderBy(e => e.ID).First().ID,
                       Name = g.Key,
                       Children = g.SelectMany(e => e.Children).ToList(),
                       Parents = g.SelectMany(e => e.Parents).ToList()
                   });
var duplicates = Elements.Where(e => !temp.Any(t => t.ID == e.ID))
                         .Select(e => e.ID)
                         .Distinct();
Elements = new HashSet<Element>(temp);
foreach (Element e in Elements)
{
    e.Children.RemoveAll(i => duplicates.Contains(i));
    e.Parents.RemoveAll(i => duplicates.Contains(i));
}

据我了解,您只需按Name 对所有元素进行分组,然后选择最低的ID 并加入ChildrenParents。显然这是由这个查询完成的。

【讨论】:

  • 这很接近,但子列表和父列表最终会出现孤立的更高 ID,不再映射到任何东西。
  • 这也不太行。我需要在搬家前找到并更换。我已经搞砸了太久了,此时我无法只见树木不见森林。不过,这真的很接近(我认为)。谢谢。
【解决方案3】:

如果我理解正确,您希望:

  1. 删除具有相同名称的元素
  2. 将已移除元素的子项和父项列表合并到剩余元素中
  3. 在子列表和父列表中,用剩余元素的 ID 替换对已删除 ID 的引用

这些可以通过以下代码完成:

// Find all duplicated elements and remove them
var duplicates = Elements.GroupBy(x => x.Name)
                         .Where(x => x.Count() > 1)
                         .SelectMany(x => x.OrderBy(e => e.ID)
                                           .Skip(1)
                                           .Select(e => new { Element = e, NewID = x.Min(y => y.ID) }))
                         .ToDictionary(x => x.Element.ID, x => new { x.Element, x.NewID });
Elements.ExceptWith(duplicates.Values.Select(x => x.Element));

// Update the Children and Parents of each remaining element
foreach (var element in Elements)
{
    var removed = duplicates.Where(x => x.Value.Element.Name == element.Name);

    var mergedChildren = element.Children.Union(removed.SelectMany(x => x.Value.Element.Children))
                                         .Select(x => duplicates.ContainsKey(x) ? duplicates[x].NewID : x)
                                         .Distinct().ToList();
    element.Children.Clear();
    element.Children.AddRange(mergedChildren);


    var mergedParents = element.Parents.Union(removed.SelectMany(x => x.Value.Element.Parents))
                                       .Select(x => duplicates.ContainsKey(x) ? duplicates[x].NewID : x)
                                       .Distinct().ToList();
    element.Parents.Clear();
    element.Parents.AddRange(mergedParents);
}

【讨论】:

    猜你喜欢
    • 2012-08-18
    • 2015-03-31
    • 2018-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多