Как объединить два списка объектов на основе свойств и объединить дубликаты в новый объект - PullRequest
2 голосов
/ 07 октября 2019

Я изо всех сил пытаюсь найти простое решение моей проблемы: у меня есть два списка объектов, и я хочу сравнить их на основе одного свойства (Serial) и создать новый список, который содержит объекты из обоих списков. Если объект находится только в первом списке, я хочу отметить его как удаленный (Status), если его только во втором списке, он должен быть отмечен как новый (Status). если это в обоих, я хочу отметить это как измененный (Статус) и сохранить старое и новое значение (Amount / NewAmount).

Так это выглядит примерно так:

Список первый:

[
    { 
        serial: 63245-8,
        amount:  10
    },
    { 
        serial: 08657-5,
        amount:  100
    }
    ,
    { 
        serial: 29995-0,
        amount:  500
    }
]

Список второй:

[
    { 
        serial: 63245-8,
        amount:  100
    },
    { 
        serial: 67455-1,
        amount:  100
    }
    ,
    { 
        serial: 44187-10,
        amount:  50
    }
]

Вывод:

[
    { 
        serial: 63245-8,
        amount:  10,
        newAmount:  100
        status: "changed"
    },
    { 
        serial: 08657-5,
        amount:  100
        status: "deleted"
    },
    { 
        serial: 29995-0,
        amount:  500,
        status: "deleted"
    }
    { 
        serial: 67455-1,
        amount:  100
        status: "new"
    }
    ,
    { 
        serial: 44187-10,
        amount:  50
        status: "new"
    }
]

Я не могу придумать ни одного хорошего решения, кроме итерации по обоим спискам и сравнения с другими, построения трех разных списков и объединения их в конце и в конечном итоге даже их сортировки. Я уверен, что есть лучшее решение для этого, может быть, даже с AutoMapper? Кто-нибудь может мне помочь?

Спасибо!

Редактировать: потому что вопрос возник в комментариях. Если элементы находятся в обоих списках, статус может быть «изменен» или «неизменен». Это не имеет большого значения для реализации, так как я отображаю объекты с их старым и новым количеством и хочу только специально пометить удаленные и новые объекты. хотя статус "без изменений" был бы неплохо иметь для дальнейшего использования.

Ответы [ 4 ]

1 голос
/ 07 октября 2019

Это двустороннее сравнение списков, которое вы можете получить с помощью Linq IEnumerable.Except() и IEnumerable.Intersect().

Первое, что вы должны сделать, это написать класс для хранения элементов данных. :

sealed class Data
{
    public string Serial { get; }
    public int    Amount { get; }

    public Data(string serial, int amount)
    {
        Serial = serial;
        Amount = amount;
    }
}

Следующее, что вам нужно сделать, это написать IEqualityComparer<T>, который вы можете использовать для сравнения предметов (вам понадобится это для использования Intersect() и Except():

sealed class DataComparer : IEqualityComparer<Data>
{
    public bool Equals(Data x, Data y)
    {
        return x.Serial.Equals(y.Serial);
    }

    public int GetHashCode(Data obj)
    {
        return obj.Serial.GetHashCode();
    }
}

Теперь напишите класс для получения данных сравнения:

enum ComparisonState
{
    Unchanged,
    Changed,
    New,
    Deleted
}

sealed class ComparedData
{
    public Data            Data            { get; }
    public int             PreviousAmount  { get; }
    public ComparisonState ComparisonState { get; }

    public ComparedData(Data data, ComparisonState comparisonState, int previousAmount)
    {
        Data            = data;
        ComparisonState = comparisonState;
        PreviousAmount  = previousAmount;
    }

    public override string ToString()
    {
        if (ComparisonState == ComparisonState.Changed)
            return $"Serial: {Data.Serial}, Amount: {PreviousAmount}, New amount: {Data.Amount}, Status: Changed";
        else
            return $"Serial: {Data.Serial}, Amount: {Data.Amount}, Status: {ComparisonState}";
    }
}

(я добавил ToString() к этому классу для удобства.)

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

class Program
{
    public static void Main()
    {
        var list1 = new List<Data>
        {
            new Data("63245-8",  10),
            new Data("08657-5", 100),
            new Data("29995-0", 500),
            new Data("12345-0",  42)
        };

        var list2 = new List<Data>
        {
            new Data("63245-8", 100),
            new Data("12345-0",  42),
            new Data("67455-1", 100),
            new Data("44187-10", 50),
        };

        var comparer = new DataComparer();

        var newItems     = list2.Except(list1, comparer);    // The second list without items from the first list = new items.
        var deletedItems = list1.Except(list2, comparer);    // The first list without items from the second list = deleted items.
        var keptItems    = list2.Intersect(list1, comparer); // Items in both lists = kept items (but note: Amount may have changed).

        List<ComparedData> result = new List<ComparedData>();

        result.AddRange(newItems    .Select(item => new ComparedData(item, ComparisonState.New,     0)));
        result.AddRange(deletedItems.Select(item => new ComparedData(item, ComparisonState.Deleted, 0)));

        // For each item in the kept list, determine if it changed by comparing it to the first list.
        // Note that the "list1.Find()` is an O(N) operation making this quite slow.
        // You could speed it up for large collections by putting list1 into a dictionary and looking items up in it -
        // but this is unlikely to be needed for smaller collections.

        result.AddRange(keptItems.Select(item =>
        {
            var previous = list1.Find(other => other.Serial == item.Serial);
            return new ComparedData(item, item.Amount == previous.Amount ? ComparisonState.Unchanged : ComparisonState.Changed, previous.Amount);
        }));

        // Print the result, for illustration.

        foreach (var item in result)
            Console.WriteLine(item);
    }
}

Вывод этого выглядит следующим образом:

Serial: 67455-1, Amount: 100, Status: New
Serial: 44187-10, Amount: 50, Status: New
Serial: 08657-5, Amount: 100, Status: Deleted
Serial: 29995-0, Amount: 500, Status: Deleted
Serial: 63245-8, Amount: 10, New amount: 100, Status: Changed
Serial: 12345-0, Amount: 42, Status: Unchanged

Скрипка DotNet здесь

0 голосов
/ 07 октября 2019

Уже есть несколько хороших ответов. Я просто хочу добавить способ, который не увеличивает сложность времени при увеличении набора данных. Напоминаем, что Linq делает за сценой только петли.

Поскольку вам приходится сравнивать два разных условия для каждого объекта в обоих списках, вам, к сожалению, приходится повторять каждый из них. Но есть способ ускорить процесс.

Предполагая, что в listOne есть n объектов и m объектов в listTwo.

Вы можетеСначала выполните цикл по всем объектам в listOne и listTwo отдельно, и создайте словарь для каждого из списка. то есть dictOne и dictTwo. Это займет O (n) и O (m) временную сложность соответственно.

Затем переберите listOne и проверьте, существуют ли элементы в dictTwo. Затем, переберите listTwo и проверьте, существуют ли элементы в dictOne.

Таким образом, общая сложность времени будет приблизительно равна O (n + m).

Модели данных:

public class InputData{
    public InputData(string serial, int amount){
        this.Serial = serial;
        this.Amount = amount;
    }
    public string Serial {get; set;}
    public int Amount{get;set;}
}

public class ResultData{
    public ResultData(string serial, int amount, int newAmount, string status){
        this.Serial = serial;
        this.Amount = amount;
        this.NewAmount = newAmount;
        this.Status = status;
    }

    public ResultData(string serial, int newAmount, string status){
        this.Serial = serial;
        this.NewAmount = newAmount;
        this.Status = status;
    }
    public string Serial {get; set;}
    public int Amount{get;set;}
    public int NewAmount{get;set;}
    public string Status {get;set;}
}

Основной метод:

public static void Main()
{
    List<InputData> listOne = new List<InputData>
    {
        new InputData("63245-8", 10),
        new InputData("08657-5", 100),
        new InputData("29995-0", 500)
    };

    List<InputData> listTwo = new List<InputData>
    {
        new InputData("63245-8", 100),
        new InputData("67455-1", 100),
        new InputData("44187-10", 50)
    };

    Dictionary<string, InputData> dictOne = CreateDictionary(listOne);      
    Dictionary<string, InputData> dictTwo = CreateDictionary(listTwo);

    List<ResultData> result = new List<ResultData>();

    result.AddRange(ProcessData(listOne, dictTwo, "deleted"));
    result.AddRange(ProcessData(listTwo, dictOne, "new"));

    foreach(var item in result){
        Console.WriteLine($"Serial: {item.Serial}, Amount: {item.Amount}, Status: {item.Status}");
    }
}

Результат:

Serial: 63245-8, Amount: 100, Status: changed
Serial: 08657-5, Amount: 100, Status: deleted
Serial: 29995-0, Amount: 500, Status: deleted
Serial: 63245-8, Amount: 10, Status: changed
Serial: 67455-1, Amount: 100, Status: new
Serial: 44187-10, Amount: 50, Status: new

Вот моя Скрипка , если вы хотите взглянуть на реальный код.

0 голосов
/ 07 октября 2019

Это пример возможной реализации

public class Obj
{
    public string serial { get; set; }
    public int amount { get; set; }
    public int? newAmount { get; set; }
    public Status status { get; set; }
}

public enum Status
{
    undefined,
    changed,
    deleted,
    @new
}
static void Main(string[] args)
    {
        string listOneJson = @"[
                                { 
                                    serial: '63245-8',
                                    amount:  10
                                },
                                { 
                                    serial: '08657-5',
                                    amount:  100
                                }
                                ,
                                { 
                                    serial: '29995-0',
                                    amount:  500
                                }
                            ]";
        string listTwoJson = @"[
                                {
                                    serial: '63245-8',
                                    amount: 100
                                },
                                {
                                    serial: '67455-1',
                                    amount: 100
                                }
                                ,
                                {
                                    serial: '44187-10',
                                    amount: 50
                                }
                               ]";
        IList<Obj> listOne = JsonConvert.DeserializeObject<IList<Obj>>(listOneJson);
        IList<Obj> listTwo = JsonConvert.DeserializeObject<IList<Obj>>(listTwoJson);

        var result = merge(listOne, listTwo);
    }

 public static IEnumerable<Obj> merge(IList<Obj> listOne, IList<Obj> listTwo)
 {

        List<Obj> allElements = new List<Obj>();
        allElements.AddRange(listOne);
        allElements.AddRange(listTwo);

        IDictionary<string, int> dict1 = listOne.ToDictionary(x => x.serial, x => x.amount);
        IDictionary<string, int> dict2 = listTwo.ToDictionary(x => x.serial, x => x.amount);
        IDictionary<string, Obj> dictResults = new Dictionary<string, Obj>();

        foreach (var obj in allElements)
        {
            string serial = obj.serial;

            if (!dictResults.ContainsKey(serial))
            {
                bool inListOne = dict1.ContainsKey(serial);
                bool inListTwo = dict2.ContainsKey(obj.serial);

                Obj result = new Obj { serial = serial };

                if (inListOne && inListTwo) {
                    result.status = Status.changed;
                    result.amount = dict1[serial];
                    result.newAmount = dict2[serial];
                }
                else if (!inListOne && inListTwo)
                {
                    result.status = Status.@new;
                    result.amount = dict2[serial];
                }
                else if (inListOne && !inListTwo)
                {
                    result.status = Status.deleted;
                    result.amount = dict1[serial];
                }

                dictResults.Add(serial, result);
            }
        }
        return dictResults.Values;
   }
0 голосов
/ 07 октября 2019

Я предлагаю вам создать пару пользовательских классов, чтобы облегчить понимание кода.

public class Item
{
    public string serial;
    public int? amount;
    public int? newAmount;
    public string status;
}

public class L1Item : Item
{       
    public L1Item(string s, int a)
    {
        serial = s;
        amount = a;
        status = "deleted";
    }
}

public class L2Item : Item
{
    public L2Item(string s, int a)
    {
        serial = s;
        amount = a;
        status = "new";
    }
}

Затем, используя предоставленные вами данные, вы можете создать два отдельных списка

List<Item> l1 = new List<Item>() { new L1Item("63245-8", 10), new L1Item("08657-5", 100), new L1Item("29995-0", 500) };
List<Item> l2 = new List<Item>() { new L2Item("63245-8", 100), new L2Item("67455-1", 100), new L2Item("44187-10", 50) };

Затем вы можетеобъединить их в один список и сгруппировать их по serial

var groupedList = l1.Concat(l2).GroupBy(x => x.serial);

Наконец, сгруппировав все элементы для каждого сериала, внесите соответствующие изменения и получите их.

var output = groupedList.Select(g => new Item()
{
    serial = g.Key,
    amount = g.First().amount,
    newAmount = g.Count() > 1 ? g.Last().amount : null,
    status = g.Count() > 1 ? "changed" : g.First().status
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...