CodeGym /Kurslar /C# SELF /.NET-də comparator-ların praktik tətbiqi

.NET-də comparator-ların praktik tətbiqi

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Niyə comparator-lar həqiqətən vacibdir…

Real layihələrdə hər şey dərs tapşırıqlarındakı kimi sadə olmur: obyektlər mürəkkəbdir, məlumatlar istifadəçilərdən və ya başqa servislərdən gəlir, bizə lazım olan "sıralama qaydaları" (və ya ümumiyyətlə bərabərlik qaydaları) tez-tez tapşırıqdan tapşırığa dəyişir. Comparator-lar kodu çevik edir, davranışı isə proqnozlaşdırılan və idarə olunan hala gətirir.

.NET-də comparator-lar hər dəfə lazım olur ki, istifadəçi class-larının obyektlərini sıralayasan, axtarasan, qruplaşdırasan və ya dublikatları çıxarasan, həmçinin "sıra" olan data strukturları quranda. Xüsusilə bu, kolleksiyalarda, alqoritmlərdə və xarici API-lərlə inteqrasiyada tez-tez rast gəlinir.

.NET-də comparator-lar harada rast gəlinir:

  • Kolleksiyaların sıralanması (List<T>.Sort, Array.Sort, OrderBy LINQ-də)
  • Sıralı data strukturları (SortedSet<T>, SortedDictionary<TKey, TValue>)
  • Kolleksiyalarda obyektlərin axtarışı və müqayisəsi (Contains, IndexOf — "bərabərlik" yox, sıranı müəyyən etmək lazım olanda)
  • Qruplaşdırma, filtrasiya və dublikatların çıxarılması (məsələn, .Distinct())

Bu praktikada niyə lazımdır?

  • Hesabatlar, harada sıralanmış çıxış vacibdir (məsələn, tələbələrin siyahısı əlifba ilə və ya orta bala görə)
  • Gələn datanın etalon dəyərlərlə uyğunlaşdırılması (məsələn, müəyyən kriteriyaya görə yazının axtarışı)
  • Yaddaş və sürətə qənaət (düzgün data strukturu seçimi axtarışı sürətləndirir və tətbiqi daha çevik edir)
  • Unikallığın yoxlanması (məsələn, istifadəçi qeydiyyatında e-mail üzrə)

2. Həyatdan nümunə: müxtəlif kriteriyalara görə sıralama

Tutaq ki, bizdə istifadəçi class-ı var:

public class User
{
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
    public int Age { get; set; }

    // override Equals və GetHashCode əlavə etmək olar — amma bu ev tapşırığı üçün
}

Bizdə istifadəçilərin siyahısı var və istəyirik ki:

  • Onları soyadına görə, soyadlar eynidirsə — ada görə sıralayaq.
  • İstifadəçini e-mail-ə görə axtarmaq mümkün olsun (registerə fikir vermədən).
  • İstifadəçi əlavə edəndə dublikatları çıxaraq.

IComparer<T> ilə sıralama

Variant 1: klassik yol — ayrıca comparator class-ı yaratmaq.

// Soyad və ada görə sıralama üçün comparator
public class UserFullNameComparer : IComparer<User>
{
    public int Compare(User? x, User? y)
    {
        if (ReferenceEquals(x, y)) return 0;
        if (x is null) return -1;
        if (y is null) return 1;

        int lastNameComparison = StringComparer.OrdinalIgnoreCase.Compare(x.LastName, y.LastName);
        if (lastNameComparison != 0)
            return lastNameComparison;

        return StringComparer.OrdinalIgnoreCase.Compare(x.FirstName, y.FirstName);
    }
}

İstifadə:

var users = new List<User>
{
    new User { FirstName = "İvan", LastName = "Petrov", Age = 20, Email = "ivan.petrov@email.com" },
    new User { FirstName = "Anna", LastName = "Smirnova", Age = 22, Email = "anna.smirnova@email.com" },
    new User { FirstName = "Petr", LastName = "Petrov", Age = 18, Email = "petr.petrov@email.com" }
};

users.Sort(new UserFullNameComparer());

foreach (var user in users)
{
    Console.WriteLine($"{user.LastName} {user.FirstName} ({user.Age})");
}

// Nəticə — istifadəçilər soyadına, soyad eynidirsə — ada görə sıralanır.

Qeyd: Belə edirlər, əgər müqayisə qaydası tətbiqin bir neçə yerində lazımdırsa və lambda artıq copy-paste-dən xilas etmir.

Lambdayla "yolda" sıralama

Bir dəfəlik qayda üçün ayrıca class yazmaq istəmirsən? Lambda kömək edəcək!

users.Sort((x, y) =>
{
    int lastNameComparison = StringComparer.OrdinalIgnoreCase.Compare(x.LastName, y.LastName);
    if (lastNameComparison != 0)
        return lastNameComparison;
    return StringComparer.OrdinalIgnoreCase.Compare(x.FirstName, y.FirstName);
});

Eyni cür işləyir, amma comparator birbaşa çağırışda yaradılır. Sətirlərə qənaət edir, amma çox istifadə üçün həmişə rahat deyil.

3. Hiyləgər axtarış: e-mail müqayisəsi registerə fikir vermədən

Real həyatda istifadəçilər e-mail-i necə gəldi yazır, sən isə proqramçısan, hakim yox, ona görə də e-mail-i registerə fikir vermədən müqayisə etmək məntiqlidir.

Bunu comparator və axtarışla reallaşdıraq:

public class EmailComparer : IEqualityComparer<User>
{
    public bool Equals(User? x, User? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        return string.Equals(x.Email, y.Email, StringComparison.OrdinalIgnoreCase);
    }
    public int GetHashCode(User obj)
    {
        return obj.Email?.ToLowerInvariant().GetHashCode() ?? 0;
    }
}

HashSet-də istifadə:

var usersSet = new HashSet<User>(new EmailComparer());
usersSet.Add(new User { Email = "Petrov@example.com" });
bool contains = usersSet.Contains(new User { Email = "petrov@example.com" }); // true!

Vacib qeyd: Öz EqualityComparer yazanda, iki metodu da implement etməyi unutma: Equals və GetHashCode. İkinciyi unutdunsa — qəribə davranış olacaq: kolleksiyalarda axtarış "axsayacaq".

4. SortedSet və SortedDictionary-də istifadə

Bax burada comparator-lar özünü tam göstərir.

SortedSet<T> və SortedDictionary<TKey, TValue> sənin obyektlərinlə işləyə bilməz, əgər .NET-ə izah etməsən ki, onları necə müqayisə etmək lazımdır. Üstəlik, müqayisə və bərabərlik qaydası hansı elementlərin fərqli sayılmasına təsir edir!

SortedSet<User> nümunəsi

var sortedUsersByFullName = new SortedSet<User>(new UserFullNameComparer())
{
    new User { FirstName = "İvan", LastName = "Petrov", Age = 20 },
    new User { FirstName = "Anna", LastName = "Smirnova", Age = 22 },
    new User { FirstName = "Petr", LastName = "Petrov", Age = 18 },
    new User { FirstName = "İvan", LastName = "Petrov", Age = 25 } // Ad və soyad üzrə dublikat
};

// Müxtəlif yaşda olan "İvan Petrov" — yalnız biri set-ə düşəcək
Console.WriteLine("SortedSet-də istifadəçilər:");
foreach (var user in sortedUsersByFullName)
    Console.WriteLine($"{user.LastName} {user.FirstName} ({user.Age})");

SortedSet iki "İvan Petrov" əlavə etməyəcək!

Vacibdir: Burada comparator "unikallıq" məntiqini müəyyən edir. Əgər yalnız ad və soyada görə müqayisə edirsənsə, eyni ad-soyada, amma fərqli yaşda olan istifadəçilər bir istifadəçi sayılır.

5. Cədvəl: .NET-də hansı comparator-u nə vaxt istifadə etməli

Ssenari Nə implement etmək lazımdır İstifadə nümunəsi
Təbii sıralama qaydası (tip üçün bir, universal)
IComparable<T>
List<T>.Sort()
Birkaç sıralama variantı (ada, yaşa, e-mail-ə görə ...)
IComparer<T>
(class-lar, lambda-lar)
List<T>.Sort(comparer)
Kolleksiyada unikal elementlərin axtarışı
IEqualityComparer<T>
HashSet<T>
,
Dictionary<TKey, TValue>
Qruplaşdırma, dublikatların silinməsi
IEqualityComparer<T>
LINQ
.Distinct(comparer)
Vaxt, tarix və ya mürəkkəb sahə kombinasiyasına görə sıralama
Comparison<T>
, yolda lambda
List<T>.Sort((a, b) => ...)
LINQ-də istifadə (bir dəfəlik sorğular) lambda
OrderBy
,
ThenBy
OrderBy(x => x.Name)

6. Praktika: kolleksiyalarda axtarış və müqayisə

Gəlin tətbiqimizdə registerə fikir vermədən unikal e-mail ilə istifadəçi qeydiyyatını reallaşdıraq. Əgər belə e-mail-li istifadəçi artıq varsa, bunu demək lazımdır.

public static bool RegisterUser(List<User> users, User newUser)
{
    // Unikal e-mail axtarışı üçün Any və lambda istifadə edirik
    bool exists = users.Any(u =>
        string.Equals(u.Email, newUser.Email, StringComparison.OrdinalIgnoreCase));
    if (exists)
    {
        Console.WriteLine($"E-mail ilə istifadəçi: {newUser.Email} artıq qeydiyyatdan keçib!");
        return false;
    }
    users.Add(newUser);
    Console.WriteLine($"İstifadəçi {newUser.FirstName} əlavə olundu.");
    return true;
}

İstifadə:

var userList = new List<User>
{
    new User { Email = "first@example.com" }
};

RegisterUser(userList, new User { FirstName = "Vasya", Email = "FIRST@example.com" });
// Çıxacaq: "E-mail ilə istifadəçi: FIRST@example.com artıq qeydiyyatdan keçib!"

7. Comparator-lardan istifadə zamanı tipik səhvlər

Hamı səhvlər siyahısını sevir, amma biz bunu bir az rəngli edəcəyik.

Bəzən proqramçı istifadəçi tipi üçün comparator yazanda null-u yoxlamamağı unudur və ya — daha pis — müqayisəni elə implement edir ki, "sıra sərtliyi" pozulur. Məsələn, comparator-da ziddiyyətli dəyərlər qaytarırsansa, sıralama gözlənilməz aparacaq, kolleksiyalarda isə obyektlər itəcək və ya "birləşəcək".

Başqa bir tez-tez rast gəlinən anlaşılmazlıq — Equals və comparator məntiqinin uyğunsuzluğudur. Məsələn, Equals obyektləri fərqli sayır, comparator isə eyni, onda SortedSet və ya SortedDictionary-də xaos başlayır: element tapılmır, halbuki orada olmalıdır.

Həmçinin, proqramçı obyektin yalnız bir əlamətini müqayisə edir (məsələn, soyad), amma unudur ki, eyni soyadlı başqa istifadəçilər də ola bilər. Nəticədə obyektlər "silinir", itir, və data artıq sistemin real vəziyyətini əks etdirmir — dublikatlar, lazım olan informasiyanın itməsi və ya proqramın iş məntiqinin pozulması baş verir.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Yaşa görə sıralama üçün komparatorun yaradılması
Yaşa görə sıralama üçün komparatorun yaradılması
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Komparatorlar
Komparatorlar və obyektlərin müqayisəsi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION