【问题标题】:Merge two collections recursivelly combining duplicates by property合并两个集合,按属性递归组合重复项
【发布时间】:2018-10-10 13:45:45
【问题描述】:

给定两个具有递归结构的对象集合:

Collection1 = [
    {
        Header: "H1",
        Items: [{
            Header: "H1.1"
        },{
            Header: "H1.2"
        }]
    },
    {
        Header: "H2",
        Items: [{
            Header: "H2.1"
        }]
    }
]

Collection2 = [
    {
        Header: "H1",
        Items: [{
            Header: "H1.1",
            Items: [{
                Header: "H1.1.1"
            }]
        }]
    }
]

我想创建某种函数来组合这两个集合,该集合具有我指出的比较属性,在本例中为Header,并且结合了它们的属性,因此结果有点如下:

Result = [
    {
        Header: "H1",
        Items: [{
            Header: "H1.1",
            Items: [{
                Header: "H1.1.1"
            }]
        },{
            Header: "H1.2"
        }]
    },
    {
        Header: "H2",
        Items: [{
            Header: "H2.1"
        }]
    }
]

如您所见,它递归地检查对象属性,如果存在类似的项目(在本例中,比较 Header 属性),它只会合并两个对象。

我尝试过Union()Distinct() 等等,但我似乎找不到实现这一目标的方法。

编辑:合并应该在“相同级别”的基础上完成,因此只有在相同深度级别具有相同标题的项目才应该被视为相等。

【问题讨论】:

  • 只有当整个树相等时才应该合并?那么,如果您的 Collection2 有 H1.foo 而不是 H1.1 它不会合并?
  • 是的,它会合并,但是因为 Collection2 会有一个带有不同标题的项目,所以它将被视为集合内的不同项目(因此它们将共存:它将有一个H1.foo 的项目和 H1.1 的项目)。
  • 顺序重要吗?假设第二个集合在 H1 之前开始“H2/H2.1”。现在这两个集合应该如何合并?
  • 你的数据结构是递归的,所以你的合并算法也应该是递归的。这意味着您需要一个基本案例。你能列举出基本情况吗?也就是说,您可以不执行任何递归步骤即可解决问题的简单案例有哪些?
  • @EricLippert 假设我们将两个集合都传递给函数,第一个参数集合应该是排序时要考虑的参数。

标签: c# recursion collections merge


【解决方案1】:

您可以执行以下操作...

假设你有一个类似这样的类:

public class Node {
    public string Header { get; set; }
    public IEnumerable<Node> Items { get; set; }

    public Node() {
        /* Note that I like to start the collections within the object's construction, 
         * to avoid issues inside operations that manipulate these collections. */
        Items = new Collection<Node>();
    }
}

您可以使用 IEqualityComparer 的递归实现来使用 Linq Union 的优点。类似的东西:

public class NodeComparer : IEqualityComparer<Node>
{
    public bool Equals(Node me, Node another) 
    {
        if (me.Header == another.Header) 
        {
            me.Items = me.Items.Union(another.Items, new NodeComparer()).ToList();

            return true;
        }

        return false;
    }

    public int GetHashCode(Node node) 
    {
        return node.Header.GetHashCode();
    }
}

这个的主要调用是:

var result = collection1.Union(collection2, new NodeComparer()).ToList();

这基本上是使用Union方法的比较,考虑节点的头部(通过方法GetHashCode),对于每个具有相同头部值的节点,进行相同的处理为您的孩子(通过 Equals 方法),这将依次发生在所有级别。

我已经用几个场景测试了这个解决方案,似乎效果很好,但是如果仍然有一些情况没有解决,也许这是一个很好的开始。

【讨论】:

    【解决方案2】:

    这是一个为您的项目建模的类:

    class Item
    {
        public string Header { get; set; }
        public IEnumerable<Item> Items { get; set; }
    }
    

    这个递归函数以你描述的方式合并项目:

    IEnumerable<Item> Merge(IEnumerable<Item> items)
    {
        var lookup = items.ToLookup(item => item.Header);
        foreach (var grouping in lookup)
        {
            var childItems = grouping.Aggregate(
                new List<Item>(),
                (list, item) =>
                {
                    if (item.Items != null)
                        list.AddRange(item.Items);
                    return list;
                });
            yield return new Item
            {
                Header = grouping.Key,
                Items = Merge(childItems)
            };
        }
    }
    

    让我解释一下:

    • 查找类似于字典,只是每个键的值不是单个 Item,而是共享同一键 (Header) 的 Item 实例的集合。

    • 查找中的每个项目都不是KeyValuePair,而是IGrouping,通过迭代所有分组,您可以获得该级别的所有标题。

    • Aggregate 用于创建一个列表,其中包含分组中所有项目的所有子项目。此列表可以包含具有相同标题的多个项目,但这是 Merge 旨在处理的数据类型。

    • 使用递归来合并子项。

    要合并两个集合,您需要像这样调用函数:

    var mergedCollection = Merge(collection1.Concat(collection2));
    

    【讨论】:

    • 非常感谢!最后我们实现了一个不同的(而且复杂得多)模型,但我测试了你的解决方案,它按预期工作,所以我将它标记为已解决!再次感谢您的宝贵时间!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    • 2018-09-22
    • 2019-09-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多