Tarih Aralığı Kesişme Kontrolü Algoritması

Tarih Aralığı Kesişme Kontrolü Algoritması

Elimizde başlangıç ve bitiş tarihlerinden oluşan bir liste olduğunu düşünelim. Yeni bir tarih aralığını bu listeye kesişme olmadan eklemeye çalıştığımızda nelerle karşılaşırız?

Elimizde başlangıç ve bitiş tarihlerinden oluşan bir liste olduğunu varsayalım. Yeni bir tarih aralığını bu listeye kesişme olmadan eklemeye çalıştığımızda nelerle karşılaşırız?

Listedeki her bir tarih aralığının başlangıç tarihi x, bitiş tarihi y
ve
listeye yeni eklenecek tarih aralığının başlangıç tarihi a, bitiş tarihi b olsun.

Yeni tarih aralığını mevcut listeye eklemek istediğimizde tespit edebildiğim kadarıyla 15 farklı durum ortaya çıkabilir. Aşağıdaki görselde bu durumları görmektesiniz.

Durumları hızlıca inceleyelim:

1 ve 9: Kesişme yoksa sorun da yoktur. ??

2, 3, 4 ve 14: Önceki kaydın sadece başlangıç tarihi güncellenir.

6, 7, 8 ve 15: Önceki kaydın sadece bitiş tarihi güncellenir.

10, 11, 12 ve 13: Yeni eklenen tarih aralığı, önceki tarihi kapsadığı için önceki tarih aralığı silinir.

5: Bu durumda önceki tarih iki parçaya ayrılır.

Her durumda yeni tarih olduğu gibi listeye eklenecektir.

Şimdi programı yavaş yavaş yazmaya başlayalım:

Önce tarih aralığı bilgisi için bir class yazalım. (Id ekledim, olur da ileride veri tabanıyla işlem yapmak gerekebilir...)

public class TarihInfo
{
    public int Id { get; set; }
    public DateTime BasTarih { get; set; } // Başlangıç tarihi
    public DateTime BitTarih { get; set; } // Bitiş tarihi
}

Şimdi bir tarih aralığı listesi oluşturalım:

List<TarihInfo> tarihler = new List<TarihInfo>();

tarihler.Add(new TarihInfo { Id = 1, BasTarih = new DateTime(2020, 10, 1), BitTarih = new DateTime(2020, 10, 10) });
tarihler.Add(new TarihInfo { Id = 2, BasTarih = new DateTime(2020, 10, 11), BitTarih = new DateTime(2020, 10, 20) });
tarihler.Add(new TarihInfo { Id = 3, BasTarih = new DateTime(2020, 10, 21), BitTarih = new DateTime(2020, 11, 10) });
tarihler.Add(new TarihInfo { Id = 4, BasTarih = new DateTime(2020, 11, 11), BitTarih = new DateTime(2020, 11, 20) });

Yeni eklenecek tarih aralığını tanımlayalım:

var bas = new DateTime(2020, 10, 15); // Başlangıç tarihi
var bit = new DateTime(2020, 11, 15); // Bitiş tarihi

Başlangıç ve bitiş tarihlerine dikkat edilirse; Id si 2 ve 4 olan kayıtlar güncellenmeli ve 3 numaralı kayıt da silinmelidir.

Yeni tarih aralığı eklendikten sonra eski tarih aralıkları için 3 durum söz konusu:

  • Silinebilir,
  • Başlangıç veya bitiş tarihi güncellenebilir,
  • İki parçaya ayrılması gerekiyorsa eski kaydın yerine 2 yeni tarih aralığı eklenebilir. ( 5 nolu durum için)

Bu ihtimalleri takip etmek için 3 ayrı liste tutacağız:

var silinenler = new List<int>();
var eklenenler = new List<TarihInfo>();
var guncellenenler = new List<TarihInfo>();

silinenler listesi sadece önceki kayıtların Id lerini tutmaktadır.

Şimdi gelelim kodun tamamını yazmaya:

static void Main(string[] args)
{
    List<TarihInfo> tarihler = new List<TarihInfo>();

    tarihler.Add(new TarihInfo { Id = 1, BasTarih = new DateTime(2020, 10, 1), BitTarih = new DateTime(2020, 10, 10) });
    tarihler.Add(new TarihInfo { Id = 2, BasTarih = new DateTime(2020, 10, 11), BitTarih = new DateTime(2020, 10, 20) });
    tarihler.Add(new TarihInfo { Id = 3, BasTarih = new DateTime(2020, 10, 21), BitTarih = new DateTime(2020, 11, 10) });
    tarihler.Add(new TarihInfo { Id = 4, BasTarih = new DateTime(2020, 11, 11), BitTarih = new DateTime(2020, 11, 20) });

    Console.WriteLine("Önceki tarih aralıkları:");
    foreach (var t in tarihler.OrderBy(x => x.BasTarih).ThenByDescending(x => x.BitTarih))
    {
        Console.WriteLine($"{t.Id} - {DTCevir(t.BasTarih)} - {DTCevir(t.BitTarih)}");
    }

    Console.WriteLine();

    var bas = new DateTime(2020, 10, 15);
    var bit = new DateTime(2020, 11, 15);

    Console.WriteLine("*** Yeni eklenecek tarih aralığı: ***");
    Console.WriteLine($"{DTCevir(bas)} - {DTCevir(bit)}");
    Console.WriteLine();

    var oncekiProgramlar = (from x in tarihler
                            where x.BitTarih >= bas &&
                                    x.BasTarih <= bit
                            select x).ToList();

    // db kullanmadığımızdan Id leri elle atıyoruz.
    var maxId = tarihler.Max(x => x.Id);

    var silinenler = new List<int>();
    var eklenenler = new List<TarihInfo>();
    var guncellenenler = new List<TarihInfo>();

    foreach (var p in oncekiProgramlar)
    {
        bool baslangicDegistir = false;
        bool bitisDegistir = false;
        bool sil = false;

        // 1
        if (bit < p.BasTarih)
            continue; 

        // 2
        else if (bas < p.BasTarih && bit == p.BasTarih)
            baslangicDegistir = true;

        // 3  
        else if (bas < p.BasTarih && bit > p.BasTarih && bit < p.BitTarih)
            baslangicDegistir = true;

        // 4
        else if (bas == p.BasTarih && bit > p.BasTarih && bit < p.BitTarih)
            baslangicDegistir = true;

        // 5
        else if (bas > p.BasTarih && bit < p.BitTarih)
        {
            sil = true;
            eklenenler.Add(new TarihInfo
            {
                BasTarih = p.BasTarih,
                BitTarih = bas.AddDays(-1),
                Id = ++maxId
            });

            eklenenler.Add(new TarihInfo
            {
                BasTarih = bit.AddDays(1),
                BitTarih = p.BitTarih,
                Id = ++maxId
            });
        }

        // 6
        else if (bas > p.BasTarih && bit == p.BitTarih)
            bitisDegistir = true;

        // 7
        else if (bas > p.BasTarih && bit > p.BitTarih)
            bitisDegistir = true;

        // 8
        else if (bas == p.BitTarih && bit > p.BitTarih)
            bitisDegistir = true;

        // 9
        else if (bas > p.BitTarih) 
            continue;

        // 10
        else if (bas == p.BasTarih && bit == p.BitTarih)
            sil = true;

        // 11
        else if (bas < p.BasTarih && bit > p.BitTarih)
            sil = true;

        // 12
        else if (bas == p.BasTarih && bit > p.BitTarih)
            sil = true;

        //13 
        else if (bas < p.BasTarih && bit == p.BitTarih)
            sil = true;

        // 14
        else if (bas == bit && bas == p.BasTarih)
            baslangicDegistir = true;

        // 15
        else if (bas == bit && bas == p.BitTarih)
            bitisDegistir = true;

        if (baslangicDegistir)
        {
            p.BasTarih = bit.AddDays(1);
            guncellenenler.Add(p);
        }
        else if (bitisDegistir)
        {
            p.BitTarih = bas.AddDays(-1);
            guncellenenler.Add(p);
        }

        if (p.BasTarih > p.BitTarih) sil = true;

        if (sil) silinenler.Add(p.Id);
    }

    if (silinenler.Count > 0)
    {
        Console.WriteLine("Silinen tarih aralıkları:");
        var silinecekTarihler = (from x in oncekiProgramlar
                                    where silinenler.Contains(x.Id)
                                    select x).ToList();

        foreach (var t in silinecekTarihler)
        {
            Console.WriteLine($"{t.Id} - {DTCevir(t.BasTarih)} - {DTCevir(t.BitTarih)}");
            tarihler.Remove(t);
        }
        Console.WriteLine();
    }

    if (eklenenler.Count > 0)
    {
        Console.WriteLine("Eklenen tarih aralıkları:");
        foreach (var t in eklenenler)
        {
            Console.WriteLine($"{t.Id} - {DTCevir(t.BasTarih)} - {DTCevir(t.BitTarih)}");
            tarihler.Add(t);
        }
        Console.WriteLine();
    }

    if (guncellenenler.Count > 0)
    {
        Console.WriteLine("Güncellenen tarih aralıkları:");
        foreach (var t in guncellenenler)
        {
            Console.WriteLine($"{t.Id} - {DTCevir(t.BasTarih)} - {DTCevir(t.BitTarih)}");
        }
        Console.WriteLine();
    }

    tarihler.Add(new TarihInfo
    {
        BasTarih = bas,
        BitTarih = bit,
        Id = ++maxId
    });

    Console.WriteLine("Sonraki tarih aralıkları:");
    foreach (var t in tarihler.OrderBy(x => x.BasTarih).ThenByDescending(x => x.BitTarih))
    {
        Console.WriteLine($"{t.Id} - {DTCevir(t.BasTarih)} - {DTCevir(t.BitTarih)}");
    }

    Console.ReadKey();
}

public static string DTCevir(DateTime t)
{
    return t.ToString("dd.MM.yyyy");
}

 

Programı çalıştırdığımızda ekran görüntüsü şu şekilde olacaktır:

Önceki tarih aralıkları:
1 - 01.10.2020 - 10.10.2020
2 - 11.10.2020 - 20.10.2020
3 - 21.10.2020 - 10.11.2020
4 - 11.11.2020 - 20.11.2020

*** Yeni eklenecek tarih aralığı: ***
15.10.2020 - 15.11.2020

Silinen tarih aralıkları:
3 - 21.10.2020 - 10.11.2020

Güncellenen tarih aralıkları:
2 - 11.10.2020 - 14.10.2020
4 - 16.11.2020 - 20.11.2020

Sonraki tarih aralıkları:
1 - 01.10.2020 - 10.10.2020
2 - 11.10.2020 - 14.10.2020
5 - 15.10.2020 - 15.11.2020
4 - 16.11.2020 - 20.11.2020

Sonraki tarih aralıkları dikkatlice incelendiğinde;

  • 3 nolu kayıt silindi,
  • 2 nolu kaydın bitiş tarihi değişti,
  • 4 nolu kaydın başlangıç tarihi değişti,
  • 5 nolu kayıt ise yeni eklediğimiz tarih aralığı zaten.

Güzel bir algoritma olduğunu düşünüyorum.

Siz ne dersiniz?

Herkese iyi kodlamalar...

 

Not:

Program kodlarına https://github.com/daltinkurt/Tarih-Araliklari adresinden ulaşabilirsiniz.