LinQ Peformance - Làm việc với các List có chung nhiều phần tử
Trong công việc hàng ngày, chúng ta thường tiếp xúc với các bài toán có dạng cơ bản chung như sau: từ các tập hợp cho trước, thực hiện tìm ra các phần tử giống nhau của các tập hợp (dựa vào 1 điều kiện nghiệp vụ), hoặc cập nhật thông tin của các phần tử giống nhau này. Dạng cơ bản này có thể thấy được qua các bài toán như: tổng hợp dữ liệu hàng hóa từ các kho (trong bài toán quản lý kho), tìm dữ liệu giống nhau theo dòng trong các file và lưu vào cơ sở dữ liệu (bài toán import data), loại bỏ các dữ liệu trùng lặp (trong bài toán đồng bộ dữ liệu),...
Dạng bài toán này nếu thực hiện bằng mã lệnh chương trình, ta thường thấy các lập trình viên sử dụng các vòng lặp (for, foreach,...) để giải quyết. Tuy nhiên, việc sử dụng các vòng lặp không khéo léo sẽ dẫn đến độ phức tạp O(n*m*...), và tất nhiên là thời gian xử lý đoạn mã lệnh sẽ dài và làm giảm hiệu năng của chương trình. Đơn cử bài toán yêu cầu tổng hợp số lượng sản phẩm từ 2 kho, chúng ta thường sẽ thấy cách làm như thế này:
public IEnumerable<Item> ConsolidateStockUsingJoin(List<Item> stock1, List<Item> stock2)
{
var consolidatedItems = from s1 in stock1
join s2 in stock2
on s1.Sku equals s2.Sku
select new Item
{
Sku = s1.Sku,
Quantity = s1.Quantity + s2.Quantity
};
return consolidatedItems;
}
(dùng linQ join để tìm các Item có chung mã sku và cập nhật số lượng Quantity của từng Item, sau đó trả về dữ liệu đã tổng hợp và tất nhiên cách làm này dẫn đến độ phức tạp O(n*m)).
Vậy có cách làm nào khác (vẫn dùng linQ) tốt hơn cách trên không? Tất nhiên là có, và cách mới này sẽ làm độ phức tạp giảm xuống còn O(n), tức là thời gian thực hiện mã lệnh sẽ giảm xuống. Ở đây, chúng ta tinh chỉnh như sau:
public IEnumerable<Item> ConsolidateStockUsingDictionary(List<Item> stock1, List<Item> stock2)
{
var stock2Dict = stock2.ToDictionary(x => x.Sku, x => x.Quantity);
foreach (var item in stock1)
{
var quantityOfItemInStock2 = stock2Dict[item.Sku];
yield return new Item
{
Sku = item.Sku,
Quantity = item.Quantity + quantityOfItemInStock2
};
}
}
(Việc chuyển 1 List<T> thành 1 Dictionary chính là magic trong đoạn mã lệnh trên. Và để làm được điều magic này, các bạn nên xem lại kiến thức về Dictionary Data Structure nhé.
Tổng hợp lại, ta có đoạn mã lệnh so sánh hiệu năng như sau:
public class Item
{
public string Sku { get; set; }
public int Quantity { get; set; }
}
public static IEnumerable<Item> GetItemsStock()
{
for (var i = 1; i < 1000; i++)
{
yield return new Item
{
Sku = $"Sku-{i}",
Quantity = i
};
}
}
public IEnumerable<Item> ConsolidateStockUsingJoin(List<Item> stock1, List<Item> stock2)
{
var consolidatedItems = from s1 in stock1
join s2 in stock2
on s1.Sku equals s2.Sku
select new Item
{
Sku = s1.Sku,
Quantity = s1.Quantity + s2.Quantity
};
return consolidatedItems;
}
public IEnumerable<Item> ConsolidateStockUsingDictionary(List<Item> stock1, List<Item> stock2)
{
var stock2Dict = stock2.ToDictionary(x => x.Sku, x => x.Quantity);
foreach (var item in stock1)
{
var quantityOfItemInStock2 = stock2Dict[item.Sku];
yield return new Item
{
Sku = item.Sku,
Quantity = item.Quantity + quantityOfItemInStock2
};
}
}
private void Test_ConsolidateStockUsingJoin()
{
var stock1 = GetItemsStock().ToList();
var stock2 = GetItemsStock().ToList();
var watch = new Stopwatch();
watch.Start();
var items = ConsolidateStockUsingJoin(stock1, stock2);
watch.Stop();
Console.WriteLine($"time eplased - ConsolidateStockUsingJoin - {watch.Elapsed}");
}
private void Test_ConsolidateStockUsingDictionary()
{
var stock1 = GetItemsStock().ToList();
var stock2 = GetItemsStock().ToList();
var watch = new Stopwatch();
watch.Start();
var items = ConsolidateStockUsingDictionary(stock1, stock2);
watch.Stop();
Console.WriteLine($"time eplased - ConsolidateItemsUsingDictionary - {watch.Elapsed}");
}
Test_ConsolidateStockUsingJoin();
Test_ConsolidateStockUsingDictionary();
Và kết quả chạy thử nhiều lần, thật sự ấn tượng:
time eplased - ConsolidateStockUsingJoin - 00:00:00.0004734
time eplased - ConsolidateItemsUsingDictionary - 00:00:00.0001997
Tác giả: Nguyễn Thanh Sơn
Chú ý: Tất cả các bài viết trên TEDU.COM.VN đều thuộc bản quyền TEDU, yêu cầu dẫn nguồn khi trích lại trên website khác.
Bài viết liên quan
Sử dụng kiểu tập hợp (Enum)
Enum (viết tắt của Enumeration) trong C# là một kiểu dữ liệu đặc biệt cho phép bạn định nghĩa một tập hợp các hằng số có tên
Đọc thêmTính đóng gói (Encapsulation) và best practices trong OOP
(Tính đóng gói) là một trong những nguyên tắc cơ bản của lập trình hướng đối tượng (OOP).
Đọc thêmTính trừu tượng - Abstract classes and interfaces
Tính trừu tượng (Abstraction) trong OOP là kỹ thuật ẩn đi các chi tiết triển khai và chỉ hiển thị cho người dùng những chức năng cần thiết.
Đọc thêmTính chất kế thừa (Inheritance) và đa hình (polymorphism)
Kế thừa là cơ chế cho phép một lớp (class) kế thừa các thuộc tính và phương thức từ một lớp khác.
Đọc thêmCách debug ứng dụng C#
Hướng dẫn cách debug chương trình C# trong Visual Studio và Visual Studio Code
Đọc thêmTìm hiểu về các loại Collection trong C#
Trong C#, collections là các cấu trúc dữ liệu được sử dụng để lưu trữ và quản lý các nhóm đối tượng. C# cung cấp nhiều loại collections khác nhau để phù hợp với các yêu cầu cụ thể của lập trình viên
Đọc thêmTổng quan về Generic và Non-Generic Collection
Hiểu khái niệm Generic và Non-Generic Collection và phân biệt giữa Generic Collection và Non-Generic Collection.
Đọc thêmSử dụng mảng (Arrays)
Mảng trong C# là một cấu trúc dữ liệu lưu trữ một dãy các phần tử có bộ nhớ nằm liên tiếp nhau và có kích thước cố đinh.
Đọc thêmLập trình hướng đối tượng
Lập trình hướng đối tượng (Object Oriented Programing) hay còn gọi là OOP. Là một kỹ thuật lập trình cho phép các lập trình viên có thể ánh xạ các thực thể bên ngoài đời thực và trừu tượng hoá thành các class và object trong mã nguồn.
Đọc thêmVòng lặp (loop)
Trong thực tế khi bạn cần thực thi một khối lệnh nhiều lần. Vòng lặp cho phép chúng ta thực thi một câu lệnh hoặc một khối lệnh nhiều lần.
Đọc thêm