按属性递归合并两个集合

时间:2018-10-10 13:45:45

标签: c# recursion collections merge

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

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()等,但似乎无法找到实现此目标的方法。

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

2 个答案:

答案 0 :(得分:1)

这是一个为您的商品建模的类:

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而是一个Item个实例的集合,这些实例都共享相同的键(Header )。

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

  • Aggregate用于创建包含分组中所有项目的所有子项目的列表。该列表可以包含多个具有相同标题的项目,但这是Merge设计用于处理的数据。

  • 使用合并子项递归。

要合并两个集合,您需要调用以下函数:

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

答案 1 :(得分: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 ),并且对于具有相同标头值的每个节点,都执行相同的过程(通过等于方法)为您的孩子使用,并且这种情况在所有级别上都是连续发生的。

我已经在几种情况下测试了该解决方案,并且看起来效果很好,但是如果仍然存在无法解决的情况,也许这是一个很好的开始。