2015-05-26 9 views
8

Ich habe eine Liste von Ereignissen und jetzt möchte ich herausfinden, welche Ereignisse sich überschneiden. Im Folgenden finden Sie den Code, den ich derzeit habe, aber ich habe das Problem, dass das Objekt, nach dem gesucht wird, auch in der Liste enthalten ist.Finden Sie überlappende Zeiträume (Ereignisse) mit LINQ

List<SomeEventObject> overlappingEvents = new List<SomeEventObject>(); 
foreach (SomeEventObject eventItem in EventList) 
{ 
    bool overlapping = false; 
    foreach (SomeEventObject anotherEventItem in EventList) 
    { 
     if (eventItem.StartDate <= anotherEventItem.EndDate && 
      eventItem.EndDate >= anotherEventItem.StartDate) 
     { 
      overlapping = true; 
      overlappingEvents.Add(anotherEventItem); 
     } 
    } 

    if (overlapping) 
     overlappingEvents.Add(eventItem); 
} 

Ich müsste eine neue Liste ohne das gesuchte Objekt erstellen. Daher frage ich, ob es einen schönen LINQ-Ausdruck gibt, der das für mich handhaben kann. Dies ist einige Pseudo-Code ich gedacht:

EventList.Where(e => 
       eventItem.StartDate <= e.EndDate && 
       eventItem.EndDate >= e.StartDate); 

In diesem Zusammenhang eventItem natürlich nicht existieren.

Als Ergebnis denke ich, ich würde zwei Listen benötigen: eine mit überlappenden Ereignissen und eine mit nicht überlappenden Ereignissen. Aber das sollte mit .Except() möglich sein, wenn ich meine überlappende Ereignisliste habe.

Edit:

ich angelegt habe ein dotnetfiddle so dass man mit ihm spielen. Eine wichtige Frage ist der überlappende Algorithmus.

Ereignis 1:
Startdatum: Heute, 10.00
EndDate: Heute, 10:05

Ereignis 2:
Startdatum: Heute, 10.05
EndDate: heute , 10:10

Wenn Sie dies dem Benutzer präsentieren, dann überlappt dies NICHT. Also muss ich meinen Algorithmus überarbeiten.

+0

Einfach einfach durch Id ausschließen. Ein Artikel passt immer in seinen eigenen Zeitrahmen. –

+0

Was meinst du mit * überlappen *. Es ist möglich, dass sich Ereignis * A * mit * B * und * B * mit * C * überschneidet. Aber das bedeutet nicht, dass sich * A * mit * C * überschneidet. Überlappen ist eine relative Eigenschaft. –

+0

Wie viele Ereignisse haben Sie in dieser Liste? Durchschnitt/Normal gegenüber Worst Case? –

Antwort

2

ich so etwas wie dies versuchen würde:

var searchedFor = EventList.First(); // Replace by the proper one 

var overlapping = EventList.Where(e => e != searchedFor && 
    EventList.Any(ev => e != ev && ev.StartDate <= e.EndDate && ev.EndDate >= e.StartDate)) 

// Alternatively with Except 

var overlapping = EventList.Except(new[] { searchFor }).Where(e => 
    EventList.Any(ev => e != ev && ev.StartDate <= e.EndDate && ev.EndDate >= e.StartDate)) 

Für die nicht-überlappende Sie einfach die überlappenden Ereignisse von Ihrem EventListExcept mit Filter:

var nonOverlapping = EventList.Except(overlappingEvents); 

Ich weiß nicht, Ihre SomeEventObject aber Sie müssen möglicherweise den Vergleich der Ungleichheit um einige Id Vergleich ändern.

Es gibt eine quite good Q&A on detecting overlapping periods in C#, die Sie untersuchen möchten. Sie können auch das Kontroll ausklammern wie der Einfachheit halber folgt:

Func<SomeEventObject, SomeEventObject, bool> areOverlapped = (e1, e2) => 
    e1.StartDate <= e2.EndDate && e1.EndDate >= e2.StartDate; 

var overlapping = EventList.Where(e => e != searchedFor && 
    EventList.Any(ev => e != ev && areOverlapped(e, ev))); 
+0

Ich habe die Bedingung in 'ev.StartDate <= e.EndDate && ev.EndDate> e.StartDate' geändert, so dass es keine Schnittmenge gibt, wenn * ev * um 10:05 endet und * e * um 10:05 Uhr beginnt. Sie können dies [hier] (https://dotnetfiddle.net/cM0Ccn) versuchen.Wenn ich das tue, bekomme ich ein Ergebnis - nämlich * ev * ist in der Ergebnisliste. Aber warum? – testing

+0

@ testing das ist, weil ich die Prüfung verpasst habe, um die Suche nach Ereignis auszuschließen: Ich habe es in der Bearbeitung korrigiert – jnovo

+0

Wie heißt das Ausschließen genannt? Wo sollte ich die Funktion * areOverlapped * einfügen? Kann ich das irgendwo hinstellen? – testing

1

Sie das Ergebnis mit dieser bekommen:

var nonOverlappingEvents = EventList.Where(e1 => !EventList.Where(e2 => e2 != e1).Any(e2 => e1.StartDate <= e2.EndDate && e1.EndDate >= e2.StartDate)); 

Aber ich verstehe nicht, Ihre überlappende Algorithmus. Ich glaube Überlappung bedeutet, dass StartDate eines Ereignisses zwischen StartDate und EndDate eines anderen:

var nonOverlappingEvents = EventList.Where(e1 => !EventList.Where(e2 => e2 != e1).Any(e2 => e2.StartDate >= e1.StartDate && e2.StartDate <= e1.EndDate)); 
+0

Wenn ich versuche, Ihren Code auszuführen, bekomme ich * Kann Lambda nicht konvertieren Ausdruck, um 'IENumerable ' einzugeben, weil es kein Delegattyp * ist. Hast du eine Idee warum? Ich denke, ich muss den überlappenden Algorithmus überdenken ... – testing

+0

Ich hätte 'Where' anstelle von' Except' verwenden sollen. Ich habe die Antwort bearbeitet. –

+0

Danke. Ich habe versucht [beide] (https://dotnetfiddle.net/y021Gh) von Ihren Bedingungen, aber sie scheinen zu versagen. Vielleicht muss der überlappende Algorithmus überdacht werden? – testing

1

Ich weiß, Sie sagten, Sie wollen Linq verwenden und es ist sicherlich recht einfach. Früher haben wir das gemacht - aber wir arbeiten viel mit Datumsangaben und haben einen viel besseren Weg gefunden.

Wenn Sie viel von dieser Zeit der Arbeit tun, würde ich die Itenso Time Period Library empfehlen (verfügbar unter Nuget).

Es unterstützt eine Vielzahl von zeitbasierten Operationen. Wie IsSamePeriod, HasInside, OverlapsWith oder IntersectsWith sind für Periodenbeziehungen verfügbar.

Es unterstützt auch Zeitsammlungen, z.B. Alle Ereignisse für eine Person. Diese basieren auf ITimePeriodCollection können beliebige Elemente vom Typ ITimePeriod enthalten und interpretieren den frühesten Start aller Elemente als Beginn des Erfassungszeitraums. Dementsprechend gilt das letzte Ende aller Elemente als Ende der Sammlungsperiode.

Einige Codebeispiel für einen Geschmack davon, wie es

//Setup a time range 
TimeRange timeRange1 = new TimeRange(
     new DateTime(2011, 2, 22, 14, 0, 0), 
     new DateTime(2011, 2, 22, 18, 0, 0)); 
     Console.WriteLine("TimeRange1: " + timeRange1); 
     // > TimeRange1: 22.02.2011 14:00:00 - 18:00:00 | 04:00:00 

    // --- Setu uptime range 2 --- 
    TimeRange timeRange2 = new TimeRange(
    new DateTime(2011, 2, 22, 15, 0, 0), 
    new TimeSpan(2, 0, 0)); 
    Console.WriteLine("TimeRange2: " + timeRange2); 
    // > TimeRange2: 22.02.2011 15:00:00 - 17:00:00 | 02:00:00 


    // --- relation --- 
    Console.WriteLine("TimeRange1.GetRelation(TimeRange2): " + 
        timeRange1.GetRelation(timeRange2)); 

    // --- intersection --- 
    Console.WriteLine("TimeRange1.GetIntersection(TimeRange2): " + 
        timeRange1.GetIntersection(timeRange2)); 
    // > TimeRange1.GetIntersection(TimeRange2): 
    //    22.02.2011 15:00:00 - 17:00:00 | 02:00:00 
+0

Vielen Dank für Ihre Ergänzung und Ihre ausführlichere Erklärung. Ich habe mein benutzerdefiniertes Objekt. Wenn ich das richtig sehe, muss ich die Daten meiner benutzerdefinierten Objekte in 'ITimePeriodCollection' ablegen. Aber woher weiß ich, dass das Ereignis mit der ID 1234 genau die "ITimePeriod" ist, die sich überschneidet? Ereignisse können die gleiche Start- und Endzeit haben ... – testing

+1

Sie können Ihre Ereignisse implementieren eine Schnittstelle ITimeRange. Sie können alles, was ITimeRange implementiert, in eine ITimePeriodCollection laden. Sie könnten TimeRange auch als Basisklasse verwenden. – GraemeMiller

2

arbeitet ich es auf diese Weise tun würde:

var overlappingEvents = 
(
    from e1 in EventList 
    from e2 in EventList 
    where e1 != e2 
    where e1.StartDate <= e2.EndDate 
    where e1.EndDate >= e2.StartDate 
    from e in new [] { e1, e2 } 
    select e 
).ToList(); 

denke ich, dass nach vorn ganz gerade sein sollte.


Gemäß dem Kommentar, diese Version nur gibt ein EventList Element einmal unabhängig davon, wie viele Überschneidungen beteiligt sich an

var overlappingEvents = 
(
    from e1 in EventList 
    where EventList 
     .Where(e2 => e1 != e2) 
     .Where(e2 => e1.StartDate <= e2.EndDate) 
     .Where(e2 => e1.EndDate >= e2.StartDate) 
     .Any() 
    select e1 
).ToList(); 

Es kann auch geschrieben werden als:.

var overlappingEvents = 
    EventList 
     .Where(e1 => 
      EventList 
       .Where(e2 => e1 != e2) 
       .Where(e2 => e1.StartDate <= e2.EndDate) 
       .Where(e2 => e1.EndDate >= e2.StartDate) 
       .Any()) 
     .ToList(); 

Basierend auf dem weiteren Kommentar, hier ist, wie die Ereignisse zu koppeln:

var overlappingEvents = 
(
    from e1 in EventList 
    from e2 in EventList 
    where e1 != e2 
    where e1.StartDate <= e2.EndDate 
    where e1.EndDate >= e2.StartDate 
    select new [] { e1, e2 } 
).ToList(); 

Jetzt ist overlappingEvents eine Liste von Arrays - List<SomeEventObject[]> und nicht List<SomeEventObject>. Das Array enthält die überlappenden Ereignisse.

+0

Ich habe Ihren Code in [dieser dotnetfiddle] (https://dotnetfiddle.net/Q216FM) versucht. Aber es gibt zu viele Ergebnisse (vier Ergebnisse, aber nur zwei Ereignisse). – testing

+0

@testing - Dies ist eine direkte Übersetzung Ihres Codes. Beide Seiten jedes Paares werden zurückgegeben. Ich kann ändern, um nur zwei Ereignisse zurückzugeben. – Enigmativity

+0

@testing - Ich habe die Antwort aktualisiert. – Enigmativity

Verwandte Themen