1. Çoxsəviyyəli (iyerarxik) sıralama
Niyə müqayisə həmişə asan olmur?
C# müsahibələrində tez-tez mürəkkəb obyektlərin sıralanması və müqayisəsi barədə suallar verirlər. Bu sadəcə dəb deyil, real problemlərin əksidir və hər bir proqramçı bununla rastlaşır. Bazada yazı axtarışı, vitrin və cədvəllərin interfeysində sıralanması, kolleksiyalarda unikal obyektlər — bunların hamısı obyektlərin düzgün müqayisəsi ilə birbaşa bağlıdır.
Əgər ədədləri müqayisə edirik, hər şey asandır. Amma istifadəçiləri əvvəl soyadına, sonra adına görə sıralamaq istəsək, üstəlik bəzi sahələr boş (null) ola bilər və ya sətirlər müxtəlif dillərdə yazılıbsa... Burada artıq sistemli yanaşma lazımdır.
Tez-tez tələb olunur: əvvəl bir əlamətə görə sıralamaq, sonra bərabərlik varsa — digərinə, orada da bərabərlik varsa — üçüncüsünə.
Nümunə: Adı, soyadı və doğum tarixi olan istifadəçi
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
// Gözəllik üçün — istifadəçi haqqında məlumatı çıxarırıq
public override string ToString()
=> $"{LastName} {FirstName} ({BirthDate:yyyy-MM-dd})";
}
Niyə iyerarxiya lazımdır?
Təsəvvür et, istifadəçilər siyahımız var və onları əlifba sırası ilə çıxarmaq istəyirik: əvvəl soyadına, sonra adına görə. Əgər həm soyad, həm ad eynidirsə — doğum tarixinə görə.
Müqayisə məntiqi: "məsuliyyət zənciri"
Bu, olimpiada iştirakçılarının sıralanmasına bənzəyir: əvvəl ballara, bərabərlikdə — vaxtına, orada da bərabərlik varsa — əlifbaya görə. Kodda bunu belə yazmaq olar:
public class UserComparer : IComparer<User>
{
public int Compare(User x, User y)
{
// Soyadları müqayisə edirik
int result = string.Compare(x.LastName, y.LastName, StringComparison.OrdinalIgnoreCase);
if (result != 0) return result; // Fərqlidirsə — kifayətdir
// Soyadlar bərabərdirsə — adları müqayisə edirik
result = string.Compare(x.FirstName, y.FirstName, StringComparison.OrdinalIgnoreCase);
if (result != 0) return result;
// Soyad və ad bərabərdirsə — doğum tarixini müqayisə edirik
return x.BirthDate.CompareTo(y.BirthDate);
}
}
Necə tətbiq etmək olar:
var users = new List<User>
{
new User { FirstName = "İvan", LastName = "İvanov", BirthDate = new DateTime(1990, 1, 1) },
new User { FirstName = "Petr", LastName = "İvanov", BirthDate = new DateTime(1992, 5, 1) },
new User { FirstName = "Anna", LastName = "Petrova", BirthDate = new DateTime(1985, 8, 30) }
};
users.Sort(new UserComparer());
users.ForEach(Console.WriteLine);
// Petrova Anna (1985-08-30)
// İvanov İvan (1990-01-01)
// İvanov Petr (1992-05-01)
2. Sətirləri necə müqayisə etməli? Mədəniyyət fərqləri
Sətir müqayisəsi: Ordinal, CurrentCulture, InvariantCulture
Elə bil sətir — hər yerdə eyni sətirdir, amma hər şey bu qədər sadə deyil! Məsələn, rus ё və е, alman ss və ß, müxtəlif register...
.NET-də sətir müqayisəsi üçün xüsusi qaydalar var, StringComparison vasitəsilə təyin olunur. Bu həm sıralamaya, həm axtarışa təsir edə bilər.
Sətir müqayisəsi nümunəsi:
// alman dilində ß demək olar ki, ss-ə bərabərdir
string a = "straße";
string b = "STRASSE";
bool eq1 = string.Equals(a, b, StringComparison.Ordinal); // false
bool eq2 = string.Equals(a, b, StringComparison.OrdinalIgnoreCase); // false
bool eq3 = string.Equals(a, b, StringComparison.CurrentCultureIgnoreCase); // true
İlk iki halda müqayisə bayt-bayt aparılır — mədəniyyət və dil xüsusiyyətləri nəzərə alınmır, ona görə də ß və SS fərqli sayılır. Amma üçüncü halda cari mədəniyyət (məsələn, alman) istifadə olunur və sətir dili bilən kimi oxunur: ß ss kimi qəbul olunur, register isə nəzərə alınmır. Ona görə də eq3 true qaytarır.
Düzgün müqayisə üsulunu necə seçmək olar?
- Ordinal — sürətli, bayt səviyyəsində, texniki məsələlər üçün yaxşıdır (məsələn, identifikatorların müqayisəsi).
- CurrentCulture / InvariantCulture — istifadəçi mətnləri üçün, əməliyyat sisteminin və ya seçilmiş mədəniyyətin qaydalarını nəzərə alır.
Sort, Compare və digər metodlarda müqayisə variantını açıq şəkildə göstərməyə çalış:
string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase)
Müxtəlif dillərdə sıralamanın xüsusiyyətləri
Rus dilində sıralama — ё е-dən sonra gələ bilər, ya da bərabər sayılır (mədəniyyətdən asılıdır!). Ona görə də ciddi bir şey edirsənsə (məsələn, arayış aparırsansa), mütləq müştəri və ya biznes-analitikdən soruş, "xüsusi" hərfləri necə düzgün sıralamaq lazımdır.
3. null-dan qorunma: bütün obyektlər tərbiyəli deyil
Əgər sahədə null varsa nə etməli?
Real proqramlarda kimsə mütləq sahəni doldurmağı unudacaq və müqayisədə — bam! — NullReferenceException çıxacaq. Bizim işimiz — buna hazır olmaqdır.
null-dan qorunmasız nümunə:
public int Compare(User x, User y)
{
return x.LastName.CompareTo(y.LastName); // əgər LastName == null, səhv olacaq!
}
Qoruma ilə nümunə (null hər hansı bir non-null-dan kiçikdir):
public int Compare(User x, User y)
{
// Null dəyərləri nəzərə alan xüsusi string comparator istifadə edirik
int byLastName = Comparer<string>.Default.Compare(x.LastName, y.LastName);
if (byLastName != 0) return byLastName;
// və s...
}
Daha qısa variant:
public int Compare(User x, User y)
{
return string.Compare(x?.LastName, y?.LastName, StringComparison.OrdinalIgnoreCase);
}
"Null"-lar əvvəl, yoxsa axırda?
Elə etmək olar ki, "boş" soyadlı istifadəçilər siyahının əvvəlində və ya axırında olsun — tapşırıqdan asılıdır.
public int Compare(User x, User y)
{
if (x.LastName == null && y.LastName == null) return 0;
if (x.LastName == null) return 1; // null — axıra
if (y.LastName == null) return -1; // null — axıra
return string.Compare(x.LastName, y.LastName, StringComparison.OrdinalIgnoreCase);
}
4. Bir neçə əlamət üzrə müqayisə
Yeni başlayanların tez-tez etdiyi səhv — müqayisədə "məsuliyyət zənciri" əvəzinə arifmetikadan istifadə etmək, məsələn:
// Belə etmə!
public int Compare(User x, User y)
{
// Pis nümunə
return (x.Age - y.Age) + string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
}
Bu üsul düzgün sıralamaya zəmanət vermir: əgər yaş fərqi -100 olsa, sətir müqayisəsi 1 qaytarırsa, nəticə -99 olacaq, bu isə gözlənilən sıralama məntiqinə uyğun deyil.
Düzgün — ardıcıl yoxlama istifadə etməkdir:
əgər artıq fərq varsa — onu qaytarırıq, yoxdursa növbəti əlamətə baxırıq.
5. Faydalı nüanslar
Əsas əlamətlər üzrə obyektlər bərabərdirsə nə etməli?
Əgər obyektlər "bərabər" çıxırsa, vacibdir ki, sıralama alqoritmi sabit olsun: müqayisəyə görə bərabər olan elementlərin sırası dəyişməsin. Daxili List<T>.Sort() sabitliyə zəmanət vermir. Əgər bu vacibdirsə (məsələn, çoxsəviyyəli istifadəçi sıralamasında), LINQ metodlarından OrderBy/ThenBy istifadə et — onlar sabitdir.
Opsional/nullable sahələr nəzərə alınmaqla müqayisə
.NET-də elə modellər olur ki, sahə məsələn, DateTime? (Nullable<DateTime>) və ya int? tipindədir. Burada məntiq sadədir: null non-null-dan kiçikdir, ya da əksinə — tapşırıqdan asılıdır. Standart kitabxanadakı köməkçilərdən istifadə etmək olar:
int result = Nullable.Compare<DateTime>(u1.BirthDate, u2.BirthDate);
Əlavə qaydalarla müqayisə
Bəzən tələb olunur ki, müqayisə "əlamətin çəkisini" nəzərə alsın, məsələn, VIP-müştərilər həmişə birinci getsin. Həll — sıralamanın əvvəlinə "VIP-flagi" əlavə etməkdir.
public int Compare(User x, User y)
{
// VIP-lər əvvəldə olur
int vipResult = y.IsVip.CompareTo(x.IsVip); // true = 1, false = 0; azalan sıralama
if (vipResult != 0) return vipResult;
// Qalanı — adət üzrə
int result = string.Compare(x.LastName, y.LastName, StringComparison.OrdinalIgnoreCase);
if (result != 0) return result;
return string.Compare(x.FirstName, y.FirstName, StringComparison.OrdinalIgnoreCase);
}
6. Məsləhətlər və ən yaxşı praktika
Həmişə null-u yoxla
Müasir C# getdikcə "null-təhlükəsizliyə" can atır, amma köhnə kod — "null-təhlükəsiz" deyil. Həmişə qoruma əlavə et və ya uyğun metodlardan istifadə et.
"Sehrli" ədədlərdən qaç
return x.Field - y.Field; yazma, əgər sahələrdə tipin həddindən çıxmaq (overflow) mümkündürsə. Əgər sahələr long tipindədirsə — səhvlər ola bilər.
StringComparison istifadə et
Sətir müqayisəsinin "default" davranışına güvənmə. Açıq şəkildə StringComparison.OrdinalIgnoreCase və ya tapşırığa uyğun başqa variantı ötür.
Müqayisə və bərabərliyi ayır
IComparable<T> və IEqualityComparer<T> interfeysləri fərqli məsələləri həll edir. Sıralama üçün comparator, unikal obyekt axtarışı üçün ekvivalentlik istifadə et. Bəzən nəticələri fərqli ola bilər!
"Tipik olmayan" hallar üçün testlər əlavə et
Yoxla ki, sıralama düzgün işləyir, əgər sahələr bərabərdirsə, sahələr boşdursa, sətirlər müxtəlif register və ya dildədirsə.
GO TO FULL VERSION