SortedList<TKey, TValue> trong C#

Ở trong hướng dẫn trước chúng ta đã tìm hiểu về lớp Dictionary<TKey,TValue> trong C# - một generic collection trong loạt bài hướng dẫn về Collection trong C#. Dictionary<TKey,TValue> là một từ điển gồm các cặp Khóa-Giá trị được định kiểu mạnh. Bạn có thể tìm hiểu Dictionary<TKey,TValue> ở link dưới đây nếu như chưa xem:

Dictionary<TKey, TValue> trong C# | Comdy
Dictionary<TKey, TValue> trong C# là gì? Dictionary<TKey, TValue> trong C# dùng để làm gì? Cách sử dụng Dictionary<TKey, TValue> trong C#.

Lớp SortedList<TKey, TValue> đại diện cho một tập hợp các cặp khóa-giá trị được sắp xếp theo khóa dựa trên interface IComparer<T>. SortedList<TKey, TValue> lưu trữ các cặp khóa và giá trị theo thứ tự tăng dần của khóa theo mặc định.

C# gồm hai loại SortedList: generic collection (SortedList<TKey, TValue>) và non-generic collection (SortedList). Hướng dẫn này sẽ trình bày về SortedList<TKey, TValue>.

Lớp SortedList<TKey,TValue> với TKey là kiểu dữ liệu của khóa và TValue là kiểu dữ liệu của giá trị.

Sơ đồ sau minh họa hệ thống phân cấp của lớp SortedList<TKey, TValue>.

SortedList<TKey, TValue> trong C#

Trong nội bộ, SortedList<TKey, TValue> duy trì hai mảng TKey[]TValue[], một cho các khóa và một cho các giá trị. Vì vậy, khi bạn thêm cặp khóa-giá trị, nó sẽ tìm kiếm nhị phân bằng cách sử dụng khóa để tìm một chỉ mục thích hợp để lưu trữ khóa và giá trị trong các mảng tương ứng. Nó cũng sắp xếp lại các phần tử khi bạn xóa các phần tử khỏi nó.

Bạn có thể khởi tạo SortedList<TKey, TValue> bằng cách chỉ định kiểu dữ liệu cho khóa và giá trị, như ví dụ bên dưới.

SortedList<int, string> mySortedList = new SortedList<int,string>();

Trong ví dụ trên, mySortedList sẽ lưu trữ các khóa kiểu int và các giá trị kiểu string.

Các thành viên quan trọng của SortedList<TKey, TValue>

Các thuộc tính quan trọng của SortedList<TKey, TValue>:

Thuộc tính Miêu tả
Capacity Trả về hoặc thiết lập số lượng phần tử mà SortedList<TKey, TValue> có thể lưu trữ.
Count Trả về tổng số phần tử tồn tại trong SortedList<TKey, TValue>.
IsReadOnly Trả về một giá trị cho biết liệu SortedList<TKey, TValue> có ở chế độ chỉ đọc hay không.
Item Trả về hoặc thiết lập phần tử với khóa được chỉ định trong SortedList<TKey, TValue>.
Keys Trả về danh sách các khóa của SortedList<TKey, TValue>.
Values Trả về danh sách các giá trị trong SortedList<TKey, TValue>.

Các phương thức quan trọng của SortedList<TKey, TValue>:

Phương thức Miêu tả
Add Thêm các cặp khóa-giá trị vào SortedList<TKey, TValue>.
Remove Loại bỏ phần tử với khóa được chỉ định.
RemoveAt Loại bỏ yếu tố tại chỉ mục được chỉ định.
ContainsKey Kiểm tra xem khóa được chỉ định có tồn tại trong SortedList<TKey, TValue> không.
ContainsValue Kiểm tra xem giá trị được chỉ định có tồn tại trong SortedList<TKey, TValue> không.
Clear Xóa tất cả các phần tử khỏi SortedList<TKey, TValue>.
IndexOfKey Trả về một chỉ mục của khóa được chỉ định được lưu trữ trong mảng bên trong của SortedList<TKey, TValue>.
IndexOfValue Trả về một chỉ mục của giá trị được chỉ định được lưu trữ trong mảng bên trong của SortedList<TKey, TValue>
TryGetValue Trả về true và gán giá trị với khóa được chỉ định, nếu khóa không tồn tại thì trả về false.

Thêm phần tử vào SortedList<TKey, TValue>

Sử dụng phương thức Add() để thêm các cặp khóa - giá trị vào SortedList<TKey, TValue>. Khóa không thể là null, nhưng giá trị có thể là null. Ngoài ra, kiểu dữ liệu của khóa và giá trị phải giống như kiểu dữ liệu khai báo khi tạo biến, nếu không nó sẽ gây ra lỗi khi biên dịch.

Ví dụ sau đây cho thấy cách thêm cặp khóa-giá trị vào SortedList<TKey, TValue>.

SortedList<int,string> sortedList1 = new SortedList<int,string>();
sortedList1.Add(3, "Three");
sortedList1.Add(4, "Four");
sortedList1.Add(1, "One");
sortedList1.Add(5, "Five");
sortedList1.Add(2, "Two");

SortedList<string,int> sortedList2 = new SortedList<string,int>();
sortedList2.Add("one", 1);
sortedList2.Add("two", 2);
sortedList2.Add("three", 3);
sortedList2.Add("four", 4);
// Compile time error: cannot convert from <null> to <int>
// sortedList2.Add("Five", null);
    
SortedList<double,int?> sortedList3 = new SortedList<double,int?>();
sortedList3.Add(1.5, 100);
sortedList3.Add(3.5, 200);
sortedList3.Add(2.4, 300);
sortedList3.Add(2.3, null);
sortedList3.Add(1.1, null);

SortedList<TKey, TValue> sắp xếp các phần tử mỗi khi bạn thêm các phần tử mới. Vì vậy, nếu bạn mở chế độ Debug cho ví dụ trên, bạn sẽ thấy các khóa được sắp xếp theo thứ tự tăng dần ngay cả khi chúng được thêm ngẫu nhiên. Hình ảnh sau đây cho thấy SortedList<TKey, TValue> trong chế độ Debug.

SortedList<TKey, TValue> trong chế độ Debug

Như bạn có thể thấy trong hình trên, biến sortList1 lưu trữ các cặp khóa-giá trị theo thứ tự tăng dần của khóa và biến sortList2 lưu các mục theo thứ tự chữ cái của khóa ngay cả khi chúng không được thêm vào theo thứ tự đó. Biến sortList3 khai báo kiểu dữ liệu của giá trị là int? (int nullable) cho phép giá trí null.

Các cặp khóa / giá trị cũng có thể được thêm bằng cú pháp khởi tạo đối tượng, như ví dụ bên dưới.

SortedList<int,string> sortedList1 = new SortedList<int,string>()
{
	{3, "Three"},
	{4, "Four"},
	{1, "One"},
	{5, "Five"},
	{2, "Two")}
};

Truy cập phần tử trong SortedList<TKey, TValue>

Sử dụng bộ chỉ mục

Các phần tử trong SortedList<TKey, TValue> có thể được truy cập bởi chỉ mục hoặc khóa. Không giống như các collection khác, bộ chỉ mục của SortedList<TKey, TValue> yêu cầu khóa và trả về giá trị cho khóa đó. Tuy nhiên, phải đảm bảo rằng khóa đó tồn tại trong SortedList<TKey, TValue>, nếu không nó sẽ ném ra ngoại lệ KeyNotFoundException.

SortedList<string,int> sortedList2 = new SortedList<string,int>();
sortedList2.Add("one", 1);
sortedList2.Add("two", 2);
sortedList2.Add("three", 3);
sortedList2.Add("four", 4);

Console.WriteLine(sortedList2["one"]);
Console.WriteLine(sortedList2["two"]);
Console.WriteLine(sortedList2["three"]);

//Following will throw runtime exception: KeyNotFoundException
Console.WriteLine(sortedList2["ten"]);

Đây là kết quả khi biên dịch và chạy chương trình:

1
2
3

Sử dụng vòng lặp for

Bộ  chỉ mục của Khóa và Giá trị có thể sử dụng để truy cập khóa và giá trị của SortedList<TKey, TValue> bằng vòng lặp for như dưới đây:

SortedList<string,int> sortedList2 = new SortedList<string,int>();
sortedList2.Add("one", 1);
sortedList2.Add("two", 2);
sortedList2.Add("three", 3);
sortedList2.Add("four", 4);

for (int i = 0; i < sortedList2.Count; i++)
{
    Console.WriteLine("key: {0}, value: {1}", sortedList2.Keys[i], sortedList2.Values[i]);
}

Đây là kết quả khi biên dịch và chạy chương trình:

key: four, value: 4
key: one, value: 1
key: three, value: 3
key: two, value: 2

Sử dụng vòng lặp foreach

Câu lệnh foreach trong C# có thể được sử dụng để truy cập các phần tử của SortedList<TKey, TValue>. Các phần tử của SortedList<TKey, TValue> là các cặp khóa và giá trị có kiểu dữ liệu là struct KeyValuePair<TKey, TValue> chứ không phải là kiểu dữ liệu của khóa hoặc giá trị.

SortedList<string,int> sortedList2 = new SortedList<string,int>();
sortedList2.Add("one", 1);
sortedList2.Add("two", 2);
sortedList2.Add("three", 3);
sortedList2.Add("four", 4);

foreach(KeyValuePair<string,int> kvp in sortedList2)
{
    Console.WriteLine("key: {0}, value: {1}", kvp.Key , kvp.Value );
}

Đây là kết quả khi biên dịch và chạy chương trình:

key: four, value: 4
key: one, value: 1
key: three, value: 3
key: two, value: 2

Truy cập khóa

Nếu bạn không chắc chắn rằng khóa cụ thể có tồn tại trong SortedList<TKey, TValue> hay không thì có thể sử dụng phương thức TryGetValue() để lấy giá trị của khóa được chỉ định. Nếu khóa không tồn tại, nó sẽ trả về false thay vì ném ngoại lệ.

SortedList<string,int> sortedList2 = new SortedList<string,int>();
sortedList2.Add("one", 1);
sortedList2.Add("two", 2);
sortedList2.Add("three", 3);
sortedList2.Add("four", 4);

int val;

if (sortedList2.TryGetValue("ten", out val))
{
    Console.WriteLine("value: {0}", val);
}
else
{
    Console.WriteLine("Key is not valid.");
}

if (sortedList2.TryGetValue("one", out val))
{
    Console.WriteLine("value: {0}", val);
}

Đây là kết quả khi biên dịch và chạy chương trình:

Key is not valid.
value: 1

Xóa phần tử khỏi SortedList<TKey, TValue>

Sử dụng các phương thức Remove(key)RemoveAt(index) để xóa các phần tử khỏi SortedList<TKey, TValue>.

Phương thức Remove(key) sẽ tìm và xóa phần tử đầu tiên bắt gặp trong SortedList<TKey, TValue> có khóa trùng với khóa chỉ định.

Phương thức RemoveAt(index) sẽ xóa phần tử tại vị trí được chỉ định.

SortedList<string,int> sortedList = new SortedList<string,int>();
sortedList.Add("a", 1);
sortedList.Add("c", 3);
sortedList.Add("b", 2);
sortedList.Add("d", 4);
    
sortedList.Remove("a");  //removes the element whose key is 'a'
sortedList.RemoveAt(0);  //removes the element at index 0

foreach(KeyValuePair<string,int> kvp in sortedList)
{
    Console.WriteLine("key: {0}, value: {1}", kvp.Key, kvp.Value);
}

Đây là kết quả khi biên dịch và chạy chương trình:

key: c, value: 3
key: d, value: 4

Kiểm tra phần tử tồn tại trong SortedList<TKey, TValue>

Phương thức ContainsKey() xác định xem khóa được chỉ định có tồn tại trong SortedList<TKey, TValue> hay không.

Phương thức ``ContainsValue() xác định xem giá trị được chỉ định có tồn tại trong SortedList<TKey, TValue> hay không.

SortedList<string,int> sortedList = new SortedList<string,int>();
sortedList.Add("one", 1);
sortedList.Add("two", 2);
sortedList.Add("three", 3);
sortedList.Add("four", 4);
sortedList.Add("five", 5);
    
sortedList.ContainsKey("One"); // returns true
sortedList.ContainsKey("Ten"); // returns false

sortedList.ContainsValue(2); // returns true
sortedList.ContainsValue(6); // returns false

Truy cập SortedList bằng LINQ

Bạn có thể sử dụng cú pháp truy vấn LINQ hoặc biểu thức lambda để truy cập bộ sưu tập SortedList<TKey, TValue> bằng các tiêu chí khác nhau.

SortedList<string,int> sortedList = new SortedList<string,int>();
sortedList.Add("one", 1);
sortedList.Add("two", 2);
sortedList.Add("three", 3);
sortedList.Add("four", 4);
sortedList.Add("five", 5);

var result =  sortedList.Where(kvp => kvp.Key == "two").FirstOrDefault();
    
Console.WriteLine("key: {0}, value: {1}", result.Key, result.Value);

Đây là kết quả khi biên dịch và chạy chương trình:

key: two, value: 2

Một ví dụ khác sử dụng truy vấn LINQ:

SortedList<string,int> sortedList = new SortedList<string,int>();
sortedList.Add("one", 1);
sortedList.Add("two", 2);
sortedList.Add("three", 3);
sortedList.Add("four", 4);
sortedList.Add("five", 5);

var query = from kvp in sortedList
            where kvp.Key == "two"
            select kvp;

var result = query.FirstOrDefault();

Console.WriteLine("key: {0}, value: {1}", result.Key, result.Value);

Đây là kết quả khi biên dịch và chạy chương trình:

key: two, value: 2

Sắp xếp SortedList<TKey, TValue> giảm dần

Theo mặc định, SortedList<TKey, TValue> lưu trữ các cặp khóa-giá trị theo thứ tự tăng dần của khóa. Ở đây, bạn sẽ tìm hiểu cách tạo SortedList<TKey, TValue> lưu trữ các phần tử theo thứ tự giảm dần của khóa.

SortedList<TKey, TValue> sử dụng thể hiện của interface IComparer<T> để so sánh các khóa và sắp xếp các phần tử. Vì vậy, để sắp xếp các phần tử theo thứ tự giảm dần, chúng ta cần tạo một lớp tùy chỉnh so sánh các giá trị theo thứ tự giảm dần.

class DecendingComparer<TKey>: IComparer<int>
{
    public int Compare(int x, int y)
    {
        return y.CompareTo(x);
    }
}

class Program
{
    static void Main(string[] args)
    {
        SortedList<int, int> descSortedList = new SortedList<int, int>(new DecendingComparer<int>());
        descSortedList.Add(1, 1);
        descSortedList.Add(4, 4);
        descSortedList.Add(3, 3);
        descSortedList.Add(2, 2);

        for (int i = 0; i < descSortedList.Count; i++)
        {
            Console.WriteLine("key: {0}, value: {1}", descSortedList.Keys[i], descSortedList.Values[i]);
        }
}

Hoặc bạn cũng có thể sử dụng Comparer<T> để sắp xếp SortedList<TKey, TValue> theo thứ tự giảm dần thay vì tạo một lớp riêng biệt như ở trên.

class Program
{
    static void Main(string[] args)
    {
        var descendingComparer = Comparer<int>.Create((x, y) => y.CompareTo(x));
        
        SortedList<int, int> descSortedList = new SortedList<int, int>(descendingComparer);
        descSortedList.Add(1, 1);
        descSortedList.Add(4, 4);
        descSortedList.Add(3, 3);
        descSortedList.Add(2, 2);

        for (int i = 0; i < descSortedList.Count; i++)
        {
            Console.WriteLine("key: {0}, value: {1}", descSortedList.Keys[i], descSortedList.Values[i]);
        }
}

Do đó, bạn có thể tạo một phiên bản của SortedList<TKey, TValue> để sắp xếp các phần tử theo thứ tự giảm dần.

Những điểm cần nhớ:

  • C# có lớp SortedList (non-generic collection) và SortedList<TKey, TValue> (generic collection).
  • SortedList<TKey, TValue> lưu trữ các cặp khóa-giá trị theo thứ tự tăng dần của khóa. Khóa phải là duy nhất và không thể là null trong khi giá trị có thể là null hoặc trùng lặp.
  • SortedList<TKey, TValue> lưu trữ khóa và giá trị có kiểu dữ liệu được chỉ định khi khởi tạo. Vì vậy, không cần phải boxing và unboxing.
  • Cặp khóa-giá trị có thể ép kiểu về KeyValuePair<TKey, TValue>.
  • Một giá trị riêng lẻ có thể được truy cập bằng cách sử dụng một bộ chỉ mục. Bộ chỉ mục của SortedList<TKey, TValue> chấp nhận khóa để trả về giá trị được liên kết với nó.
Lập Trình C#Lập Trình C# Cơ Bản
Bài Viết Liên Quan:
int[] và int[,] trong C#: Ai nhanh hơn
Trung Nguyen 10/10/2020
int[] và int[,] trong C#: Ai nhanh hơn

Hiểu được sự khác biệt giữa các loại mảng trong C# sẽ giúp bạn chọn cấu trúc dữ liệu chính xác cho mọi trường hợp.

Struct và class trong C#: Ai nhanh hơn
Trung Nguyen 09/10/2020
Struct và class trong C#: Ai nhanh hơn

Trong bài viết này, tôi sẽ so sánh sự khác biệt về hiệu suất giữa struct và class trong C#: Ai nhanh hơn.

Best practice cho performance trong C#
Trung Nguyen 03/10/2020
Best practice cho performance trong C#

Mục tiêu của bài viết này là cung cấp một danh sách không đầy đủ các code mẫu cần tránh, vì chúng rủi ro hoặc performance kém.

Đọc ghi file (File I/O) trong C#
Trung Nguyen 26/04/2020
Đọc ghi file (File I/O) trong C#

Hướng dẫn này sẽ giúp bạn tìm hiểu về đọc ghi file (File I/O) trong C# và sử dụng các lớp tiện ích để đọc ghi file.