2015-08-25 13 views
8

Ich habe eine Task-Klasse, die wie folgt aussieht (mit Java 8 Time API).Überlappende Datumsbereiche kombinieren - Java

class Task { 
    LocalDateTime start; 
    LocalDateTime end; 
    Set<String> actionItems; 
} 

Ich habe zwei sortiert (zuerst von Anfang an, dann bis Ende) Listen solchen Aufgabe Instanzen enthalten, können List<Task> tasksList1 und List<Task> tasksList2 sagen. Ich möchte überlappende Aufgaben kombinieren (indem Sie die Aufgaben bei Bedarf auflösen und actionItems aus anderen Aufgaben hinzufügen, die sich in ein einzelnes neues Aufgabenobjekt überschneiden).

Angenommen, ich habe eine Aufgabe namens T1, die am 01.01.2015 beginnt und am 31.01.2015 endet und die Aktionselemente A und B enthält. Dann erstellt ein Benutzer eine neue Aufgabe T2, die am beginnt 15.01.2015 und endet am 15.02.2015 und fügt den Aktionspunkt C hinzu. Wenn ich kombiniere, sollte ich drei Task-Objekte wie folgt erhalten.

  • Task X - aus 01.01.2015 bis 2015.01.15, enthält Elemente Aktion A, B
  • Task-Y - aus 2015.01.15 bis 2015.01.31 enthält einen Artikel , B und C
  • Aufgabe Z - von 2015.01.31 bis 2015.02.15, enthalten Artikel C

sichtbar zu machen, wenn meine Aufgabe Objekt aus den beiden Listen wie folgt in einer Timeline aussehen :

> [-----]  [-----]   [----]   [-----------------] 
>  [-----]   [---------------]   [------] 

Dann würde die resultierende Aufgabenliste Aufgaben wie folgt enthalten.

> [--][-][--] [-----] [-----][----][--]  [-][------][-----]` 

Overlapping Aufgaben sollten die actionItems von beiden der Aufgaben kombiniert haben, die für den Zeitraum überlappen, in dem sie sich überlappen.

Was ist der effizienteste Weg, damit umzugehen? Im Moment probiere ich verschiedene Möglichkeiten mit einem PeekableIterator aus, aber noch kein Glück. Jede Lösung, die JodaTime anstelle von Java 8 APIs verwendet, ist ebenfalls willkommen.

Antwort

10

Zuerst, wenn Sie nur für Daten interessiert sind (egal Zeiten), ist es besser, stattdessen LocalDate zu verwenden. Zweitens nehme ich an, dass Sie einen Aufgabenkonstruktor haben. So habe ich das folgende Task Objekt:

static class Task { 
    LocalDate start; 
    LocalDate end; 
    Set<String> actionItems; 

    public Task(LocalDate start, LocalDate end, 
      Collection<String> actionItems) { 
     this.start = start; 
     this.end = end; 
     this.actionItems = new HashSet<>(actionItems); 
    } 

    @Override 
    public String toString() { 
     return start + ".." + end + ": "+actionItems; 
    } 
} 

Hier ist die Lösung von allgemeinere Aufgabe, die nur alle Aufgaben in der gegebenen Sammlung verschmilzt nach Ihren Regeln (der Eingang Sammlung ist nicht unbedingt sortiert):

public static List<Task> convert(Collection<Task> input) { 
    NavigableMap<LocalDate, Set<String>> map = new TreeMap<>(); 
    map.put(LocalDate.MIN, new HashSet<>()); 

    for (Task task : input) { 
     if (!map.containsKey(task.start)) { 
      map.put(task.start, new HashSet<>(map.lowerEntry(task.start).getValue())); 
     } 
     if (!map.containsKey(task.end)) { 
      map.put(task.end, new HashSet<>(map.lowerEntry(task.end).getValue())); 
     } 
     for (Set<String> set : map.subMap(task.start, task.end).values()) { 
      set.addAll(task.actionItems); 
     } 
    } 
    List<Task> result = new ArrayList<>(); 
    LocalDate prev = null; 
    Set<String> prevValues = Collections.emptySet(); 
    for (Entry<LocalDate, Set<String>> entry : map.entrySet()) { 
     if (!prevValues.isEmpty()) { 
      result.add(new Task(prev, entry.getKey(), prevValues)); 
     } 
     prev = entry.getKey(); 
     prevValues = entry.getValue(); 
    } 
    return result; 
} 

Das Kernstück ist die , wo jeder Schlüssel der Beginn des nächsten Zeitraums ist und der Wert ist die Sammlung von Aktionen für den Zeitraum vom angegebenen Start bis zum nächsten Schlüssel (leere Werte entsprechen den Perioden ohne Aktionen). Nach dem Hinzufügen der neuen Aufgabe werden vorhandene Einträge entsprechend aktualisiert. Anwendungsbeispiel:

List<Task> res = convert(Arrays.asList(
    new Task(LocalDate.parse("2015-01-01"), LocalDate.parse("2015-01-31"), 
     Arrays.asList("A", "B")), 
    new Task(LocalDate.parse("2014-01-01"), LocalDate.parse("2014-01-31"), 
     Arrays.asList("A", "B")), 
    new Task(LocalDate.parse("2015-01-15"), LocalDate.parse("2015-02-15"), 
     Arrays.asList("C")))); 
res.stream().forEach(System.out::println); 

Ausgang:

2014-01-01..2014-01-31: [A, B] 
2015-01-01..2015-01-15: [A, B] 
2015-01-15..2015-01-31: [A, B, C] 
2015-01-31..2015-02-15: [C] 
+0

Danke Tagir! Sie rocken :) Sorry, ich war nicht klar in meiner Frage, dass die Zeit auch für mich wichtig ist. Zur Vereinfachung habe ich das im Beispiel weggelassen. Ich glaube, die Lösung sollte auch für LocalDateTime funktionieren? Noch etwas ist zu beachten, dass die Ausgabe von Ihrem Algorithmus etwas anders ist als ich es erwarte.Wie in drei Aufzählungszeichen zu hören, erwarte ich drei Aufgaben als Ausgabe, zuerst von 01/01 bis 01/15 mit A, B, Sekunde von 01/15 bis 01/31 mit A, B, C und Drittel von 01/31 bis 02/15 mit C. In der Ausgabe wird es keine Überschneidungen geben. Ich versuche, Ihre Lösung so zu ändern, dass ... –

+0

@YohanLiyanage, ja, für 'LocalDateTime' wird es auf die gleiche Weise funktionieren, ersetzen Sie einfach den Klassennamen überall. Ich fügte im Verwendungsbeispiel eine nicht überlappende Aufgabe (des Jahres 2014) hinzu, nur um zu testen, ob sie korrekt gehandhabt wird. Entfernen Sie es, und die Ausgabe wird gleich sein. –

+0

Oh, ich habe verpasst, dass es 2014 war :). Nochmals vielen Dank, und das funktioniert wie ein Zauber –

Verwandte Themen