2011-01-03 18 views
12

Esiste un modo migliore per calcolare il numero di anni bisestili tra due anni. Supponendo che io abbia data di inizio e data di fine.Come calcolare il numero di anni bisestili tra due anni in C#

Ho il mio codice, ma penso che ci dovrebbe essere un modo più elegante.

codice chiamando:

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

stessa funzione:

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; 
    } 

Quindi, se ho startDate = "2006/10/16" e endDate = "2004/04/18" Dovrei solo avere 1 anno bisestile (2000) nel risultato. Altre parole: l'anno di inizio anno e l'anno di fine anno non dovrebbero essere calcolati, solo tra anni.

Grazie in anticipo per il vostro aiuto.

+3

Questa sembra un'implementazione ragionevole –

+0

Suppongo che intendessi il 16/10/1996. In questo caso, non ti interessa il 29/2/2004, ovvero un giorno bisestile che si è verificato prima della data di fine del 18/04/2004? –

+3

Hai bisogno di prendere in considerazione che i calcoli dell'anno bisestile erano diversi prima della riforma del calendario gregoriano? Inoltre, è necessario tenere conto del fatto che alcuni paesi hanno adottato il calendario riformato in momenti diversi? Il numero di anni bisestili tra due date * in Inghilterra * può essere diverso dal numero di anni bisestili tra due date * negli Stati Uniti *, ad esempio. –

risposta

23

È possibile contarlo utilizzando l'approccio analitico. Un anno è un anno bisestile se può essere diviso per 4, ma non può essere diviso per 100, salvo il caso in cui può essere diviso per 400. Supponendo di poter contare tale numero seguendo il codice:

 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); 
     } 

Una specie di magia matematica. È una soluzione molto efficace rispetto all'uso di LINQ.

+0

Mi piace questo approccio poiché non c'è iterazione per ogni anno. Nell'accenno mi piace l'approccio LINQ, ma dopo aver analizzato il tuo codice ho scoperto che non dovrebbe avere un impatto sulle prestazioni dal momento che l'approccio LINQ deve passare ogni anno e scoprire se è saltato o meno. La tua soluzione è pura matematica. Ho provato e dà lo stesso risultato di LINQ. Grazie ancora. –

+0

@Vlad Bezden, attualmente tutti gli anni sono sotto i 10000, quindi non c'è nessun problema di prestazioni fattibile e poiché verrà fatto in memoria non è male in tutti, in questo caso personalmente preferisco il codice pulito, se fosse per il database leggere scrivere , sì, sarebbe meglio farlo meglio. –

+1

Personalmente, proverei a seguire gli algoritmi O (1) su O (n) laddove possibile. –

11

È possibile farlo con LINQ semplicemente come muggito:

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

Mi piace molto questo codice. Lo segnerò come risposta. –

+3

@Vlad Bezden LINQ è un ottimo strumento, ma per questo compito c'è una soluzione molto efficiente (vedi la mia risposta). Quando la portata è ampia, la soluzione LINQ è lenta, ma il mio algoritmo è indipendente dalla dimensione dell'intervallo. –

+0

@mace, guarda il mio commento. –

3

Questo dovrebbe svolgere molto meglio per grandi luci di tempo:

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; 
} 

Si noti che questo conta l'anno prima, se si tratta di un anno bisestile, ma non l'anno successivo. Dovrai modificare la funzione se hai bisogno di un comportamento diverso.

0

Non potresti semplicemente testare i primi 4 anni e trovare il primo anno bisestile e sottrarlo dagli anni complessivi e dal mod 4?

fondamentalmente:

anno_fine - mod first_leap_year_found 4.

non sarei sorpreso se ci sono un paio di modifiche in là per spiegare il fatto che sia specificato l'intero data, non solo l'anno , ma anche quelle modifiche dovrebbero essere semplici.

Si finisce per usare solo l'istruzione DateTime.IsLeapYear (anno) al massimo 4 volte, e dopo è semplice matematica.

+1

Questo ignora il fatto che gli anni che sono equamente divisibili per 100 non sono anni bisestili a meno che non siano anche equamente divisibili per 400. – chezy525

+0

ah, sì, d'accordo. Certo, questo è un altro semplice ritocco. Ma più "semplici ritocchi" sostengo, meno semplice è la mia soluzione proposta, quindi buon punto :) – jaydel

2

Un'altra 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

In realtà mi piace la soluzione di mace, dato che è puro calcolo e nessuna iterazione, quindi penso che la soluzione di mace abbia un minore utilizzo della CPU. Ho più di 100.000 di record per calcolare gli anni bisestili. –

2

Solo un altro codice con tipi primitivi

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

On tuoi calcoli (divisibile per 100 non sono anni bisestili meno che non siano anche divisibile per 400) questo vorrebbe dire 1900 non era un anno bisestile? errato

Ciò significa che il seguente è corretto: Non potresti semplicemente testare i primi 4 anni e trovare il primo anno bisestile e sottrarlo dal totale degli anni e del mod 4? in fondo: end_year - first_leap_year_found mod 4 = 0 è un anno bisestile

+0

Se si chiama 'DateTime.IsLeapYear (1900)' questo restituirà 'false'. E se date un'occhiata a [Wikipedia] (http://en.wikipedia.org/wiki/Leap_year) l'anno 1900 non è stato un anno bisestile. Quindi il calcolo dei Vincitori è corretto e la tua ipotesi è falsa. – Oliver

Problemi correlati