Объединить две коллекции, рекурсивно объединяя дубликаты по свойству - PullRequest
0 голосов
/ 10 октября 2018

Учитывая две коллекции объектов с рекурсивной структурой:

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 голосов
/ 10 октября 2018

Вы можете сделать следующее ...

Предположим, что у вас есть класс, подобный этому:

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>();
    }
}

Вы можете использовать достоинства Linq Union , используя рекурсивную реализацию IEqualityComparer.Примерно так:

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 ), и для каждого узла, имеющего одинаковое значение заголовка, тот же процесс выполняется для вашего ребенка (с помощью метода равно ), и это происходит последовательно на всех уровнях.

Я протестировал это решение с несколькими сценариями, и, похоже, оно сработало нормально, но если есть ситуация, которая не решается, возможно, это хороший способ начать.

0 голосов
/ 10 октября 2018

Вот класс, который моделирует ваши элементы:

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));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...