Оновлення списку з іншого списку в C#

Оновлення поля одного списку значеннями з іншого списку в C# можна здійснити як за допомогою LINQ, так і без нього.

Ось як це можна зробити:

Сценарій

Припустимо, у вас є два списки:

  1. Список джерела: містить оновлені значення.
  2. Цільовий список: потребує оновлення значеннями зі списку джерела.

Обидва списки мають спільне ключове поле для зіставлення (наприклад, Id).

Використання LINQ

using System;  
using System.Collections.Generic;  
using System.Linq;  

class Program  
{  
 class Item  
 {  
 public int Id { get; set; }  
 public string Value { get; set; }  
 }  
 static void Main()  
 {  
 var targetList = new List  
 {  
 new Item { Id = 1, Value = "OldValue1" },  
 new Item { Id = 2, Value = "OldValue2" }  
 };  
 var sourceList = new List  
 {  
 new Item { Id = 1, Value = "NewValue1" },  
 new Item { Id = 2, Value = "NewValue2" }  
 };  
 // Оновлення targetList за допомогою LINQ  
 targetList = targetList.Select(target =>  
 {  
 var source = sourceList.FirstOrDefault(src => src.Id == target.Id);  
 if (source != null)  
 {  
 target.Value = source.Value;  
 }  
 return target;  
 }).ToList();  
 // Виведення оновленого targetList  
 targetList.ForEach(item => Console.WriteLine($"Id: {item.Id}, Value: {item.Value}"));  
 }  
}

Без LINQ

using System;  
using System.Collections.Generic;  

class Program  
{  
 class Item  
 {  
 public int Id { get; set; }  
 public string Value { get; set; }  
 }  
 static void Main()  
 {  
 var targetList = new List  
 {  
 new Item { Id = 1, Value = "OldValue1" },  
 new Item { Id = 2, Value = "OldValue2" }  
 };  
 var sourceList = new List  
 {  
 new Item { Id = 1, Value = "NewValue1" },  
 new Item { Id = 2, Value = "NewValue2" }  
 };  
 // Оновлення targetList без LINQ  
 foreach (var target in targetList)  
 {  
 foreach (var source in sourceList)  
 {  
 if (source.Id == target.Id)  
 {  
 target.Value = source.Value;  
 break;  
 }  
 }  
 }  
 // Виведення оновленого targetList  
 foreach (var item in targetList)  
 {  
 Console.WriteLine($"Id: {item.Id}, Value: {item.Value}");  
 }  
 }  
}

Пояснення

  1. Використання LINQ:
  • Метод Select використовується для ітерації по targetList.
  • Для кожного елемента з targetList виконується пошук відповідного елемента в sourceList за допомогою FirstOrDefault.
  • Якщо знаходиться відповідність, оновлюється поле Value.

2. Без LINQ:

  • Використовуються вкладені цикли foreach для ітерації по обох списках.
  • Коли поля Id співпадають, значення в targetList оновлюється значенням з sourceList.

Виведення

Для обох підходів виведення буде таким:

Id: 1, Value: NewValue1  
Id: 2, Value: NewValue2

Який підхід кращий?

"Кращий" підхід залежить від контексту, таких як розмір списків, вимоги до читабельності та обговорення продуктивності.

Ось аналіз кожного підходу, щоб допомогти вам вирішити:

1. Підхід з LINQ

Переваги:

  • Короткість: Підхід з LINQ зазвичай є більш компактним і виразним.
  • Читабельність: Чітко передає намір оновлення одного списку на основі іншого.
  • Функціональний стиль: Заохочує іммутабельний/функціональний стиль програмування, що допомагає уникнути небажаних побічних ефектів.

Недоліки:

  • Продуктивність: Метод FirstOrDefault всередині циклу Select може призвести до складності O(n²), якщо sourceList великий, оскільки він багаторазово шукає у списку.
  • Складність відлагодження: Запити LINQ іноді складніше відлагоджувати порівняно з імперативними циклами.

Використовувати, коли:

  • Списки мають малий або середній розмір.
  • Важливі читабельність і стислість.
  • Додаток може витримати додаткове навантаження на продуктивність.

2.

Підхід без LINQ (вкладені foreach цикли)

Переваги:

  • Ясність і зрозумілість: Вкладені цикли foreach чітко показують, як відбувається зіставлення і оновлення списків, що полегшує розуміння для початківців.
  • Простота відлагодження: Легше відлагоджувати, оскільки ви можете використовувати точки зупину в кожному циклі.
  • Відсутність додаткових витрат через LINQ: Підходить для ситуацій, коли використання LINQ обмежене або уникається.

Недоліки:

  • Шумність коду: Потрібно більше рядків коду, що може виглядати менш елегантно.
  • Продуктивність: Як і в випадку з LINQ, вкладені цикли призводять до складності O(n²), якщо sourceList великий.

Використовувати, коли:

  • Ви віддаєте перевагу імперативному стилю програмування.
  • Простота відлагодження є пріоритетом.
  • Навколишнє середовище обмежує використання LINQ (наприклад, старі системи або конкретні стандарти програмування).

Оптимальний підхід з точки зору продуктивності

Якщо продуктивність є важливим фактором (наприклад, якщо списки великі), ви повинні оптимізувати процес пошуку за допомогою словаря:

Підхід зі словарем

using System;  
using System.Collections.Generic;  

class Program  
{  
 class Item  
 {  
 public int Id { get; set; }  
 public string Value { get; set; }  
 }  
 static void Main()  
 {  
 var targetList = new List  
 {  
 new Item { Id = 1, Value = "OldValue1" },  
 new Item { Id = 2, Value = "OldValue2" }  
 };  
 var sourceList = new List  
 {  
 new Item { Id = 1, Value = "NewValue1" },  
 new Item { Id = 2, Value = "NewValue2" }  
 };  
 // Створення словаря для швидкого пошуку  
 var sourceDict = new Dictionary<int, string>();  
 foreach (var source in sourceList)  
 {  
 sourceDict[source.Id] = source.Value;  
 }  
 // Оновлення targetList за допомогою словаря  
 foreach (var target in targetList)  
 {  
 if (sourceDict.TryGetValue(target.Id, out var newValue))  
 {  
 target.Value = newValue;  
 }  
 }  
 // Виведення оновленого targetList  
 foreach (var item in targetList)  
 {  
 Console.WriteLine($"Id: {item.Id}, Value: {item.Value}");  
 }  
 }  
}

Переваги підходу зі словарем:

  • Продуктивність: Пошук у словнику виконується за O(1) в середньому, що забезпечує складність O(n) в цілому.
  • Ясність: Словник робить намір зрозумілим і уникає повторних пошуків.

Рекомендація

  1. Для малих і середніх списків: Використовуйте LINQ для кращої читабельності.
  2. Для великих списків або сценаріїв, де продуктивність критична: Використовуйте підхід зі словарем для оптимізації пошуку.
  3. Для простоти відлагодження або коли LINQ недоступний: Використовуйте підхід без LINQ (вкладені цикли foreach).

Але словник використовує додаткову пам'ять

Ви абсолютно праві, що словник використовує додаткову пам'ять для своєї внутрішньої структури, такої як ключі, значення та накладні витрати на хеш-таблицю. Якщо пам'ять є обмеженим ресурсом, потрібно знайти баланс між ефективністю використання пам'яті та часу.

Аналіз переваг і недоліків

  1. Словник:
  • Пам'ять: Для зберігання словника потрібна додаткова пам'ять.
  • Час: Швидкий пошук (O(1) в середньому) робить його придатним для великих наборів даних.
  • Використання: Коли список великий, і вигода від продуктивності перевищує додаткові витрати пам'яті.

2. Вкладені цикли:

  • Пам'ять: Без додаткового використання пам'яті.
  • Час: Порівняно повільніше (O(n²) для вкладених циклів).
  • Використання: Коли пам'ять обмежена, або списки досить малі, щоб продуктивність була прийнятною.

3. LINQ:

  • Пам'ять: Без додаткового використання пам'яті, але з певними накладними витратами через виконання LINQ.
  • Час: Схоже на вкладені цикли, складність O(n²), якщо FirstOrDefault використовується багато разів.
  • Використання: Коли читабельність має пріоритет, і продуктивність не критична.

Рішення з економією пам'яті

Якщо ви хочете уникнути використання словаря і все одно забезпечити прийнятну продуктивність, ви можете:
Сортування обох списків:

  • Відсортуйте sourceList та targetList за ключем (Id).
  • Використовуйте один цикл для злиття/оновлення значень (аналогічно злиттю двох відсортованих масивів).

Підхід на основі сортування

using System;  
using System.Collections.Generic;  
using System.Linq;  

class Program  
{  
 class Item  
 {  
 public int Id { get; set; }  
 public string Value { get; set; }  
 }  
 static void Main()  
 {  
 var targetList = new List  
 {  
 new Item { Id = 2, Value = "OldValue2" },  
 new Item { Id = 1, Value = "OldValue1" }  
 };  
 var sourceList = new List  
 {  
 new Item { Id = 1, Value = "NewValue1" },  
 new Item { Id = 2, Value = "NewValue2" }  
 };  
 // Відсортуйте обидва списки за Id  
 targetList = targetList.OrderBy(t => t.Id).ToList();  
 sourceList = sourceList.OrderBy(s => s.Id).ToList();  
 // Злиття і оновлення  
 int i = 0, j = 0;  
 while (i < targetList.Count && j < sourceList.Count)  
 {  
 if (targetList[i].Id == sourceList[j].Id)  
 {  
 targetList[i].Value = sourceList[j].Value;  
 i++;  
 j++;  
 }  
 else if (targetList[i].Id < sourceList[j].Id)  
 {  
 i++;  
 }  
 else  
 {  
 j++;  
 }  
 }  
 // Виведення оновленого targetList  
 foreach (var item in targetList)  
 {  
 Console.WriteLine($"Id: {item.Id}, Value: {item.Value}");  
 }  
 }  
}

Складність:

  • Час:
  • Сортування: O(n log n) для кожного списку.
  • Злиття: O(n) (один прохід через обидва списки).
  • Загальна складність: O(n log n) для великих списків.
  • Пам'ять: Мінімальні додаткові витрати пам'яті (без словаря).

Переваги:

  • Ефективніше, порівняно з вкладеними циклами для більших списків.
  • Використовує мінімум додаткової пам'яті.

Недоліки:

  • Сортування списків може бути непотрібним, якщо потрібно зберегти оригінальний порядок.

Рекомендації

  • Якщо пам'ять критична: Використовуйте підхід на основі сортування.
  • Якщо час критичний, а пам'ять не є проблемою: Використовуйте підхід зі словарем.
  • Для простоти або малих наборів даних: Дотримуйтеся LINQ або вкладених циклів.

Вибір правильного підходу залежить від конкретних вимог вашого застосунку, таких як обмеження щодо часу, пам'яті та чіткості коду.

Перекладено з: Update a List from another List in C#

Leave a Reply

Your email address will not be published. Required fields are marked *