2011-01-03 11 views
12

Czy istnieje lepszy sposób obliczania liczby lat przestępnych między dwoma latami. Zakładając, że mam datę rozpoczęcia i zakończenia.Jak obliczyć liczbę lat przestępnych między dwoma latami w C#

Mam mój kod, ale myślę, że powinien być bardziej elegancki sposób.

wywołanie kodu:

var numberOfLeapYears = NumberOfLeapYears(startDate.Year + 1, endDate.Year - 1); 

funkcja sama:

private static int NumberOfLeapYears(int startYear, int endYear) 
    { 
     var counter = 0; 

     for (var year = startYear; year <= endYear; year++) 
      counter += DateTime.IsLeapYear(year) ? 1 : 0; 

     return counter; 
    } 

Więc jeśli mam startDate = "16.10.2006" i endDate = "18.04.2004" powinienem tylko 1 rok przestępny (2000). Innymi słowy, rok startDate i rok zakończenia nie powinny być obliczane, tylko lata pomiędzy.

Z góry dziękuję za pomoc.

+3

Wydaje się, że to rozsądna implementacja. –

+0

Zakładam, że chodziło Ci o 10/16/1996. W tym przypadku nie obchodzi cię data 2/24/2004, który był dniem przestępnym, który miał miejsce przed datą końcową 4/18/2004? –

+3

Czy trzeba wziąć pod uwagę, że obliczenia roku przestępnego były inne przed reformą kalendarza gregoriańskiego? Ponadto, czy trzeba wziąć pod uwagę, że niektóre kraje przyjęły zreformowany kalendarz w różnym czasie? Liczba lat przestępnych między dwiema datami * w Anglii * może być inna niż liczba lat przestępnych między dwiema datami * w USA * na przykład. –

Odpowiedz

23

Można to liczyć za pomocą podejścia analitycznego. Rok jest rokiem przestępnym, jeśli można go podzielić przez 4, ale nie można go podzielić przez 100, z wyjątkiem przypadku, gdy można go podzielić przez 400. Przyjmując, że można policzyć taką liczbę, stosując następujący kod:

 static int LeapYearsBetween(int start, int end) 
     { 
      System.Diagnostics.Debug.Assert(start < end); 
      return LeapYearsBefore(end) - LeapYearsBefore(start + 1); 
     } 

     static int LeapYearsBefore(int year) 
     { 
      System.Diagnostics.Debug.Assert(year > 0); 
      year--; 
      return (year/4) - (year/100) + (year/400); 
     } 

Jakiś rodzaj magii matematycznej. Jest to rozwiązanie znacznie efektywniejsze niż użycie LINQ.

+0

Podoba mi się to podejście, ponieważ nie ma żadnej iteracji na każdy rok. Na początku bardzo mi się podoba podejście LINQ, ale po przeanalizowaniu twojego kodu stwierdziłem, że nie powinno to mieć wpływu na wydajność, ponieważ podejście LINQ musi przejść przez każdy rok i stwierdzić, czy jest to skok czy nie. Twoje rozwiązanie to czysta matematyka. Testowałem i daje ten sam wynik co LINQ. Dzięki jeszcze raz. –

+0

@ Vlad Bezden, obecnie wszystkie lata są poniżej 10000, więc nie ma żadnego możliwego problemu z wydajnością i ponieważ zostanie to zrobione w pamięci to wcale nie jest złe, w tym przypadku ja osobiście wolę czysty kod, jeśli to było dla odczytu z odczytu bazy danych , tak, lepiej byłoby zrobić to lepiej. –

+1

Osobiście próbowałem trzymać się O (1) algorytmów nad O (n), gdzie tylko to możliwe. –

11

Można to zrobić z LINQ po prostu jak poniżej:

var leepYears = Enumerable.Range(startYear, endYear - startYear + 1) 
           .Count(x => DateTime.IsLeapYear(x)); 
+0

Bardzo podoba mi się ten kod. Zaznaczę to jako odpowiedź. –

+3

@Vlad Bezden LINQ to świetne narzędzie, ale do tego zadania istnieje wydajne rozwiązanie (zobacz moją odpowiedź). Kiedy zasięg jest duży LINQ rozwiązanie jest powolne, ale mój algorytm jest niezależny od wielkości zasięgu. –

+0

@mace, zobacz mój komentarz. –

3

ten powinien wykonać znacznie lepiej dla dużych rozpiętości czasu:

public int LeapYearsBetween(int year1, int year2) 
{ 
    var y1 = new DateTime(year1, 1, 1); 
    var y2 = new DateTime(year2, 1, 1); 
    var nonLeapDays = 365 * (y2.Year - y1.Year); 
    var leapDays = (y2 - y1).Days - nonLeapDays; 
    return leapDays; 
} 

Zauważ, że ten zlicza poprzedni rok, jeżeli jest to rok przestępny, ale nie w późniejszym roku. Będziesz musiał zmodyfikować funkcję, jeśli potrzebujesz innego zachowania.

0

Nie możesz po prostu przetestować pierwszych 4 lat i znaleźć pierwszego roku przestępnego i odjąć go od całkowitej liczby lat i mod 4?

zasadzie:

END_YEAR - first_leap_year_found mod 4.

I nie zdziwiłbym się, gdyby istnieje kilka usprawnień w tam, aby uwzględnić fakt, że cała data jest określona, ​​a nie tylko rok , ale te zmiany również powinny być proste.

Koniec końców tylko używanie instrukcji DateTime.IsLeapYear (rok) nie więcej niż 4 razy, a potem prosta matematyka.

+1

To ignoruje fakt, że lata, które są równomiernie podzielne przez 100, nie są rokami przestępnymi, chyba że są równomiernie podzielne przez 400. – chezy525

+0

ah, tak, zgodzili się. Oczywiście to kolejna prosta korekta. Ale im więcej "prostych zmian", jak twierdzę, tym mniej proste jest moje proponowane rozwiązanie, tak dobry punkt :) – jaydel

2

Innym LINQ :-)

int start = 1980; 
int end = 2000; 
var count = Enumerable.Range(start, end - start + 1) 
         .Aggregate(0, (a, b) => DateTime.IsLeapYear(b) ? a + 1 : a); 
+0

Właściwie to lubię rozwiązanie mace, ponieważ jest to czysta kalkulacja i bez iteracji, więc myślę, że rozwiązanie mace'a ma mniejsze wykorzystanie procesora. Mam więcej niż 100K rekordów do obliczania lat przestępnych. –

2

Kolejny kod z typów prymitywów

public static int CountLeapYears(int startYear, int endYear) 
    { 
     int acc = 0; 

     while (true) 
     { 
      if ((startYear % 4 == 0 && startYear % 100 != 0) || startYear % 400 == 0) 
       acc++; 
      if (startYear + 1 == endYear) break; 
      startYear++; 
     } 

     return acc; 
    } 
0

Na swoim matematyki (podzielne przez 100 nie są skok lat, chyba że są one również podzielna przez 400) to oznaczałoby, że rok 1900 nie był rokiem przestępnym? zła

Oznacza to, że poniższe informacje są poprawne: Nie możesz przetestować pierwszych 4 lat i znaleźć pierwszego roku przestępnego i odjąć go od liczby lat i mod 4? w zasadzie: end_year - first_leap_year_found mod 4 = 0 to rok przestępny

+0

Jeśli wywołasz 'DateTime.IsLeapYear (1900)' to zwróci 'false'. A jeśli spojrzeć na [Wikipedia] (http://en.wikipedia.org/wiki/Leap_year) rok 1900 nie był rokiem przestępnym. Więc obliczenia Victora są poprawne, a twoje założenie jest fałszywe. – Oliver

Powiązane problemy