Sử dụng IComparable và IComparer


Trong C#, khi bạn làm việc với một tập hợp các đối tượng trong một mảng, một danh sách… thì yêu cầu thường gặp là sắp xếp danh sách đó. Nếu như sử dụng Generics trong C# thì bạn chỉ có thể sắp xếp được các kiểu dữ liệu cơ bản như int, float, string… nhờ sử dụng phương thức Sort. Còn những kiểu dữ liệu đặc biệt do người dùng tự định nghĩa (ví dụ là các class như Student, Person, Car…) thì bạn cần phải chỉ định rõ cách sắp xếp cho kiểu dữ liệu đó.

Để cho các bạn dễ hiểu thì chúng ta sẽ xét một ví dụ sau: Tạo một danh sách các số nguyên và sắp xếp danh sách đó theo thứ tự tăng dần. Yêu cầu sử dụng List<> để chứa các số nguyên đó và sử dụng phương thức Sort có sẵn trong List<>. Đoạn code theo yêu cầu bên trên sẽ là như sau:

List<int> intList = new List<int>();
intList.Add(42); //Thêm ngẫu nhiên một vài số vào danh sách
intList.Add(10);
intList. Add(25); 
//Sắp xếp danh sách này bằng phương thức Sort
intList.Sort();
foreach(int number in intList)
	Console.WriteLine(number);

 

Như các bạn thấy, chúng ta đã tận dụng phương thức Sort có sẵn trong List<> để sắp xếp các số nguyên này. Bởi vì int là một kiểu dữ liệu cơ bản nên trình biên dịch hoàn toàn biết cách sắp xếp thứ tự các phần tử từ bé đến lớn.

Ví dụ tiếp theo có phần phức tạp hơn, đó là thay vì danh sách các số nguyên thì chúng ta sẽ làm việc với danh sách các đối tượng của lớp Person như sau:

class Person
{
	public int Age{get;set;}
	public string Name{get;set;}
}

Việc tạo và thêm phần tử cho danh sách các đối tượng kiểu Person này cũng không có gì là khó khăn.

List<Person> personList = new List<Person>();
personList.Add(new Person{Age=10, Name="John"});
personList.Add(new Person{Age=15, Name="Ann"});
personList.Add(new Person{Age=8, Name="Kevin"});
personList.Sort(); //Lỗi ở đây
 foreach (var item in personList)
{
	Console.WriteLine(item.Name + ":" + item.Age);
}
Console.ReadKey(true);

 

Nhưng lúc này chúng ta không thể gọi phương thức Sort() trên personList bởi vì trình biên dịch sẽ không hiểu là cách sắp xếp đối tượng kiểu Person này là như thế nào (cho 2 đối tượng kiểu Person là A và B, sẽ sắp xếp A trước B hay B trước A?). Nếu bạn không tin thì có thể thử, bạn sẽ nhận được InvalidOperationException với thông điệp “Failed to compare two elements in the array”.

Phương thức Sort() của List<> sẽ chỉ có thể thực hiện được trên những class có cài đặt interface IComparable. Nói cách khác, các class này phải có một phương thức chỉ rõ cách so sánh hai đối tượng của class đó là như thế nào. IComparable có một phương thức duy nhất là CompareTo. Tham số nhận vào là một kiểu object hoặc trong trường hợp bạn sử dụng interface IComparable<> thì tham số truyền vào là kiểu dữ liệu của lớp cài đặt interface đó. Như vậy, lớp Person của chúng ta lúc này sẽ là:

class Person : IComparable<Person>
{
	public int Age {get;set;}
	public string Name{get;set;}
	public int CompareTo(Person other)
	{
	    return this.Age.CompareTo(other.Age);
	}
}

Phương thức CompareTo sẽ dựa vào giá trị trả về để biết được thứ tự của hai đối tượng khi đem ra so sánh. Giá trị trả về nhỏ hơn 0 nghĩa là đối tượng hiện tại sẽ nhỏ hơn (hay đứng trước) đối tượng other. Giá trị trả về là 0 thì hai đối tượng bằng nhau, còn nếu giá trị trả về lớn hơn 0 thì đối tượng hiện tại lớn hơn (hay đứng sau) đối tượng other. Ở trên, thay vì phải dùng các câu lệnh if..else để trả về giá trị theo quy định thì chúng ta đã tận dụng lại phương thức CompareTo có sẵn trong các kiểu dữ liệu cơ bản (thuộc tính Age thuộc kiểu Integer). Hiện tại, chúng ta đang cài đặt cho lớp Person này sẽ sắp xếp theo thuộc tính Age. Nếu như cách so sánh phức tạp hơn, chúng ta chỉ cần cài đặt nó trong phương thức CompareTo, miễn sao giá trị trả về phản ánh đúng thứ tự mà chúng ta yêu cầu.

Sau khi đã chỉnh sửa lớp Person như trên thì ta đã có thể sử dụng được phương thức Sort của List<> để sắp xếp các đối tượng kiểu Person. Kết quả xuất ra của chúng ta là như sau:

image

Tuy nhiên, cài đặt IComparable có một hạn chế đó là chúng ta chỉ có thể quy định cho lớp Person này 1 cách sắp xếp duy nhất. Giả sử chúng ta muốn cùng lúc vừa có thể sắp xếp danh sách personList theo tên, vừa có thể sắp xếp theo tuổi thì phải làm sao? Khi đó chúng ta cần đến sự trợ giúp của một interface khác: IComparer.

Khác với IComparable được cài đặt ngay trên đối tượng mà bạn muốn sắp xếp thì IComparer như là một bộ so sánh riêng. Khi cần sắp xếp với bộ sắp xếp nào thì ta chỉ cần gắn nó với bộ so sánh đó là được (Hãy tưởng tượng bạn đang có 10 thùng hàng và có 2 loại máy sắp xếp, nếu cho 10 thùng hàng này qua máy 1 thì các thùng hàng sẽ được sắp xếp theo kích thước, còn nếu cho 10 thùng hàng này qua máy 2 thì các thùng hàng sẽ được sắp xếp theo khối lượng. Máy 1 và 2 ở đây đóng vai tròng giống như các Comparer, mỗi máy sẽ có cách sắp xếp riêng mà khi cần sử dụng thì gọi tới nó).

Chúng ta sẽ bắt đầu với việc cài đặt một Bộ so sánh cho phép so sánh hai đối tượng kiểu Person theo thuộc tính Age và một Bộ so sánh theo thuộc tính Name

class MyAgeComparer : IComparer<Person>
{
	public int Compare(Person x, Person y)
	{
		return x.Age.CompareTo(y.Age);
	}
}
class MyNameComparer : IComparer<Person>
{
	public int Compare(Person x, Person y)
	{
		return x.Name.CompareTo(y.Name);
	}
}

Tương tự như phương thức CompareTo của interface IComparable, phương thức Compare trong interface IComparer cũng dựa vào kết quả trả về để biết thứ tự của hai đối tượng. Lưu ý rằng interface IComparable được cài đặt trực tiếp ngay trên lớp các đối tượng trong danh sách, còn IComparer thì được cài đặt trên một lớp riêng (Vì thế nó sẽ có hai tham số trong phương thức Compare).

Đã có hai Comparer như trên, khi chúng ta muốn sử dụng bộ so sánh nào khi sắp xếp với phương thức Sort thì chỉ việc chỉ định đối tượng của lớp Comparer đó. Ví dụ như với danh sách personList đã tạo ra, chúng ta sẽ sắp xếp danh sách này theo tuổi như sau:

personList.Sort(new MyAgeComparer());

Còn nếu bạn muốn sắp xếp theo tên thì sao? Rất dễ dàng, chỉ cần thay đối tượng tương ứng trong tham số của phương thức Sort

personList.Sort(new MyNameComparer());

Làm theo cách này, bạn có thể cài đặt nhiều cách sắp xếp khác nhau trên một kiểu dữ liệu. Khi nào cần cách sắp xếp nào, ta chỉ việc gọi cách sắp xếp đó.

Tổng kết: Chúng ta đã tìm hiểu cách sử dụng hai interface IComparableIComparer khi cài đặt nó trên một ví dụ thực tế, trong bài kế tiếp, chúng ta sẽ cùng nhau tìm hiểu về hai interface khác là IEnumerableIEnumerator. See you next time!

Tác giả: xuanchien

Tran Xuan Chien. Japan Advanced Institute of Science and Technology - Japan. Senior Developer - NUS Technology.

18 thoughts on “Sử dụng IComparable và IComparer”

    1. Việc sắp xếp như thế nào thì bạn chỉ cần quy định trong hàm so sánh CompareTo hoặc Compare là được. Ví dụ trong trường hợp của bạn thì mình có thể làm như sau:
      int ret = this.Name.CompareTo(other.Name);
      if (ret == 0)
      return this.Age.CompareTo(other.Age);
      else
      return ret;

  1. cám ơn anh bài viết hay mong bài về IEnumerable của anh sớm.
    anh có thể cho e hỏi luôn cái interface IEquatable và chức năng của nó nữa đc ko

  2. Chào ,cho mình hỏi.Mình muốn sort theo tên như thế nào.
    Giả sử mình có dữ liều vào là họ và tên đây đủ,thì mình sắp xếp theo tên thì sao.
    VD: Nguyễn Văn A
    Trần Thị H
    Châu Văn B
    thì kết quả sắp xếp lại là
    Nguyễn Văn A
    Châu Văn B
    Trần Thị H

    Cảm ơn bạn !

    1. Việc sắp xếp thế nào là tùy thuộc vào hàm so sánh của bạn. Cụ thể ở đây bạn phải tách chuỗi Họ Tên để lấy được phần tên của hai chuỗi và sau đó so sánh chuỗi như bình thường. Việc tách họ tên thế nào thì bạn có thể sử dụng hàm Split, sau đây là một cách:
      string a = “Nguyen Van An”;
      string[] words = a.Split(‘ ‘);
      string ten = words[words.Length-1]; //ten sẽ có giá trị là An

      Thân,

  3. Em đã sử dụng được IComparable với kiểu int nhưng sử dụng float thì báo lỗi.
    Cho em hỏi có phải nó không hổ trợ trên kiểu float hay không ạ.
    Nếu nó không hổ trợ kiểu này thì làm cách nào để dụng kiểu float.
    Mong anh trả lời sớm.

    1. Khi ban thua ke(implement) 1 IComparable thi banm phai tuan theo cac quy dinh cua lop IComparable, IComparable quy dinh phuong thuc CompareTo phai la kieu int thi khong the su dung kieu float duoc dau, ban cung co the su dung kieu float neu ban tu viet ra 1 lop giong nhu lop IComparable nhung phuong thuc CompareTo la kieu float the thoi. Chao ban nhe.

      public int CompareTo(object obj)
      {
      return fre.CompareTo(((HuffmanTree)(obj)).fre);
      }

  4. À đây là code nè anh
    class HuffmanTree : IComparable
    {
    public string Info;
    public float fre;
    public HuffmanTree left = null, right = null;

    #region IComparable Members
    public float CompareTo(object obj)
    {
    return fre.CompareTo(((HuffmanTree)(obj)).fre);
    }
    #endregion
    }
    nó báo lỗi là:
    Error 1 ‘WindowsFormsApplication1.HuffmanTree’ does not implement interface member ‘System.IComparable.CompareTo(object)’. ‘WindowsFormsApplication1.HuffmanTree.CompareTo(object)’ cannot implement ‘System.IComparable.CompareTo(object)’ because it does not have the matching return type of ‘int’.

  5. đọc hai bài viết , thấy rất hay thank,mình có thể add yahoo bạn để tiện hỏi về .Net được không vì mình rất thích .Net thank bạn nhiều

  6. Error 1 ‘Sapxep.clsSapxepMaDT’ does not implement interface member ‘System.Collections.Generic.IComparer.Compare(Sapxep.clsDT, Sapxep.clsDT)’
    cho mình hỏi lỗi này khắc phục thế nào?

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s