2013-03-11 17 views
5

Ho un elenco di date che sono separate da un mese nel senso che tutte le date sono il "Primo lunedì del mese". In alcuni casi mancano mesi quindi devo scrivere una funzione per determinare se tutte le date sono consecutiveIn C#, qual è il modo migliore per trovare spazi vuoti in un array DateTime?

Quindi, ad esempio, se questo era l'elenco di date, la funzione restituiva true poiché tutti gli elementi sono il "Primo venerdì del mese "e non ci sono spazi vuoti. Questo esempio di seguito restituirebbe true.

var date = new DateTime(2013, 1, 4); 
var date1 = new DateTime(2013, 2, 1); 
var date2 = new DateTime(2013, 3, 1); 
var date3 = new DateTime(2013, 4, 5); 

var dateArray = new DateTime[]{date, date1, date2, date3}; 
bool isConsecutive = IsThisListConsecutive(dateArray); 

dove questo esempio di seguito potrebbe tornare falso perché, anche se sono anche tutti "Primo Venerdì del mese", la sua scomparsa la voce di marzo 2013.

var date = new DateTime(2013, 1, 4); 
var date1 = new DateTime(2013, 2, 1); 
var date3 = new DateTime(2013, 4, 5); 

var dateArray = new DateTime[]{date, date1, date3}; 
bool isConsecutive = IsThisListConsecutive(dateArray); 

quindi sto cercando di capire la logica giusta per l'IsThisListConsecutive() Metodo:

Qui è stato il mio primo tentativo: (Nota so già in anticipo che tutte le date sono gli stessi del giorno della settimana e la stessa settimana del mese, quindi l'unica cosa che sto cercando è una slot mancante)

private bool IsThisListConsecutive(IEnumerable<DateTime> orderedSlots) 
    { 
     DateTime firstDate = orderedSlots.First(); 
     int count = 0; 
     foreach (var slot in orderedSlots) 
     { 
      if (slot.Month != firstDate.AddMonths(count).Month) 
      { 
       return false; 
      } 
      count++; 
     } 
     return true; 
    } 

Questo codice sopra funziona eccetto se la lista attraversa da un anno all'altro. Volevo avere qualche consiglio su un modo migliore per creare questa funzione e su come quella riga potrebbe essere riscritta per gestire date che attraversano anni.

+1

Da dove provengono gli 'orderedSlots' nel codice? Inoltre penso che stai usando la parola "consecutiva" in un modo strano. – poke

+0

@poke - Ho corretto il tipo nel codice attorno a orderedSlots. puoi pensare a una parola migliore da usare rispetto a "consecutivi" per attraversare quello che sto guardando a – leora

+0

Btw. è inteso che 'date' e' date1' sono un mercoledì, ma 'date2' e' date3' sono un giovedì? – poke

risposta

2

Nota: Questo è completamente testato, e le verifiche di data sono probabilmente piuttosto male o un po 'ridondante, ma questo è il meglio che ho potuto venire con questo momento ^^

public bool AreSameWeekdayEveryMonth(IEnumerable<DateTime> dates) 
{ 
    var en = dates.GetEnumerator(); 
    if (en.MoveNext()) 
    { 
     DayOfWeek weekday = en.Current.DayOfWeek; 
     DateTime previous = en.Current; 
     while (en.MoveNext()) 
     { 
      DateTime d = en.Current; 
      if (d.DayOfWeek != weekday || d.Day > 7) 
       return false; 
      if (d.Month != previous.Month && ((d - previous).Days == 28 || (d - previous).Days == 35)) 
       return false; 
      previous = d; 
     } 
    } 
    return true; 
} 
+0

Perché hai scelto di utilizzare direttamente l'interfaccia 'IEnumerable ' invece di un ciclo foreach? –

+1

@NathanAnderson Buona domanda. Volevo scegliere il primo elemento separatamente, ma non avevo voglia di fare una cosa 'bool first = true', o rendere' feriale' e 'precedente' nullable ... – poke

+0

Ha senso. Grazie per il chiarimento. –

2

Vorrei raccomandare la struttura TimeSpan. Grazie al sovraccarico dell'operatore è possibile ottenere uno TimeSpan sottraendo due date e quindi ricevere un TimeSpan che esprime la differenza tra le due date.

http://msdn.microsoft.com/en-us/library/system.timespan.aspx

+0

il problema con l'utilizzo di Tempo è che sottrarre le date non aiuta con la logica di confronto (non essendo un mese a parte) – leora

+0

Se si è fatto uso dell'operatore modulo e un po 'di logica si potrebbe adeguatamente ospitare per le date che sono 30, 60, o qualunque giorni a parte (ma ancora pari numero di mesi a parte). –

+0

@NathanAnderson Non tutti i mesi sono della stessa durata e i giorni della settimana (soprattutto prima/ultima) non si allineano in modo uniforme con i giorni del mese. Le date sono dannatamente difficili. –

0

ho potuto interpretare male ciò che stai cercando di fare, ma penso che funzionerà, supponendo che tu non debba gestire date antiche. Vedere se ci sono delle lacune nelle date convertite in "mesi totali"

int totalMonths = date.Year * 12 + (date.Month - 1); 
2

va bene, il codice non funziona quando gli anni si incrociano becuase 1 gennaio può essere un lunedi su un anno e martedì sul prossimo. Se lo facessi, per prima cosa controllerei che

a) siano lo stesso giorno della settimana di ogni mese (utilizzare DateTime.DayOfWeek)

b) sono la stessa settimana del mese in ciascun mese * uso metodo di estensione DayOfMonth (vedi link) * Calculate week of month in .NET *

(Hai detto che conosce già un & b per essere vero così lascia passare alla terza condizione)

c) dobbiamo determinare se sono in mesi consecutivi

//order the list of dates & place it into an array for ease of looping 
DateTime[] orderedSlots = slots.OrderBy(t => t).ToArray<DateTime>(); 


//create a variable to hold the date from the previous month 
DateTime temp = orderedSlots[0]; 


for(i= 1; index < orderedSlots.Length; index++) 
{ 
    if((orderedSlots[index].Month != temp.AddMonths(1).Month | 
     orderedSlots[index].Year != temp.AddMonths(1).Year)){ 
     return false; 
    } 

    previousDate = orderedSlots[index]; 
} 

return true; 

se avete bisogno di controllare le condizioni di un & b anche aggiungere modificare l'istruzione if come segue

if(orderedSlots[index].Month != temp.AddMonths(1).Month | 
     orderedSlots[index].Year != temp.AddMonths(1).Year) | 
     orderedSlots[index].DayOfWeek != temp.DayOfWeek  | 
     orderedSlots[index].GetWeekOfMonth != temp.AddMonths(1).GetWeekOfMonth){ 
     return false; 
    } 

ricordare che per utilizzare settimana get del metodo di estensione mese è necessario includere il codice in Calculate week of month in .NET I Sono sicuro che ci sono errori di battitura come ho fatto in un editor di testo.

1

Bene, ecco il mio il mio pensiero iniziale su come avrei affrontato questo problema.

Innanzitutto, è necessario definire una funzione che trasformi le date in valori ordinali corrispondenti all'ordine in cui appaiono .

int ToOrdinal(DateTime d, DateTime baseline) { 
    if (d.Day <= 7 
     && d.DayInWeek == baseline.DayInWeek) { 
     // Since there is only one "First Friday" a month, and there are 
     // 12 months in year we can easily compose the ordinal. 
     // (As per default.kramer's comment, months normalized to [0,11].) 
     return d.Year * 12 + (d.Month - 1); 
    } else { 
     // Was not correct "kind" of day - 
     // Maybe baseline is Tuesday, but d represents Wednesday or 
     // maybe d wasn't in the first week .. 
     return 0; 
    } 
} 

var dates = ..; 
var baseline = dates.FirstOrDefault(); 
var ordinals = dates.Select(d => ToOrdinal(d, baseline)); 

Poi, per le date previste, si finisce con sequenze ordinali come:

[24156 + 0, 24156 + 1, 24156 + 2, 24156 + 3] 

E

[24156 + 0, 24156 + 1, /* !!!! */ 24156 + 3] 

Da qui si tratta solo di una questione banale di iterazione della lista e assicurando che gli interi avvengano in sequenza senza spazi vuoti o bancarelle - cioè, ogni item/integer è esattamente uno in più rispetto al precedente.

+0

'd.Month' restituisce 1 per gennaio, quindi è necessario sottrarre 1. Altrimenti dicembre 2012 == gennaio 2013. –

+0

@ default.kramer Whoops! Corretto, grazie. –

3

Quindi, per implementarlo, inizieremo con un semplice metodo di supporto che accetta una sequenza e restituisce una sequenza di coppie che compone ciascun elemento con il suo elemento precedente.

public static IEnumerable<Tuple<T, T>> Pair<T>(this IEnumerable<T> source) 
{ 
    T previous; 
    using (var iterator = source.GetEnumerator()) 
    { 
     if (iterator.MoveNext()) 
      previous = iterator.Current; 
     else 
      yield break; 

     while(iterator.MoveNext()) 
     { 
      yield return Tuple.Create(previous, iterator.Current); 
      previous = iterator.Current; 
     } 
    } 
} 

Ci sarà anche utilizzato questo metodo semplice per determinare se sono due date nello stesso mese:

public static bool AreSameMonth(DateTime first, DateTime second) 
{ 
    return first.Year == second.Year 
     && first.Month == second.Month; 
} 

Usando questo, si può facilmente afferrare il mese di ogni data e vedere se è il mese dopo il mese precedente. Se è vero per tutte le coppie, allora abbiamo mesi consecutivi.

private static bool IsThisListConsecutive(IEnumerable<DateTime> orderedSlots) 
{ 
    return orderedSlots.Pair() 
     .All(pair => AreSameMonth(pair.Item1.AddMonths(1), pair.Item2)); 
} 
+0

@ pst A destra, corretto. Oh, e usando 'Zip' la sequenza verrà ripetuta due volte se si fa qualcosa come' source.Zip (source.Skip (1) ', quindi per evitare la doppia iterazione (e mantenere la pigrizia) non si può usare' Zip. – Servy

+0

Hmm. Non pensavo a quel caso con Zip .. Immagino di passare in una lista e di solito non ci penso (di solito non sono un programmatore efficiente in questo aspetto, ma ascolto ReSharpers "doppia valutazione" avverte ed emette ToList in molti casi in cui non * ho * bisogno di pigro.) –

+0

@pst Quando si tratta di un caso specifico in cui si conosce la dimensione generale di ciò che si sta passando potrebbe essere bene, ma quando si scrivono metodi di utilità più generali in cui non si conosce la dimensione del set di dati (o quando si scrive codice per estranei su Internet in cui non si conosce la dimensione del set di dati) è meglio fare come poche ipotesi possibili. – Servy

Problemi correlati