2015-08-25 19 views
8

Ho una classe Task simile alla seguente (utilizzando Java 8 Time API).Combinazione di intervalli di date sovrapposti - Java

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

Ho due ordinato (la prima volta da inizio, quindi entro la fine) gli elenchi contenenti tali casi Task, consente di dire List<Task> tasksList1 e List<Task> tasksList2. Voglio combinare attività sovrapposte (interrompendo le attività se necessario e aggiungendo actionItem da altre attività che si sovrappongono in un singolo oggetto task).

Ad esempio, supponiamo di avere un'attività denominata T1 che inizia il 01/01/2015 e termina il 31/01/2015, che contiene gli elementi di azione A e B. Quindi un utente crea un nuovo Task T2 che inizia 15/01/2015 e termina il 15/02/2015 e aggiunge l'elemento di azione C al suo interno. Quando combino, dovrei ottenere tre oggetti Task come segue.

  • Task X - da 01/01/2015 a 2015/01/15, contiene elementi di azione A, B
  • Task Y - dal 2015/01/15 al 2015/01/31, contiene articoli A , B e C
  • Task Z - dal 2015/01/31 al 2015/02/15, contiene voce C

per visualizzare, se il mio oggetto compito dalle due liste simile al seguente in una timeline :

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

Quindi l'elenco delle attività risultante conterrà le attività come segue.

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

compiti sovrapposti dovrebbero avere le actionItems combinati da entrambi i compiti che si sovrappongono per il periodo in cui si sovrappongono.

Qual è il modo più efficiente per gestirlo? Al momento sto provando diverse opzioni con un PeekableIterator, ma ancora nessuna fortuna. Anche le soluzioni che utilizzano JodaTime anziché le API Java 8 sono benvenute.

risposta

10

In primo luogo se ci si preoccupa solo delle date (non importa i tempi), è preferibile utilizzare LocalDate. Secondo, suppongo che tu abbia un costruttore di compiti. Così ho usato il Task seguente oggetto:

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

Ecco la soluzione del compito più generale che ha appena unisce tutti i compiti di data collection in base alle proprie regole (la raccolta di input non è necessariamente allineati):

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

La cosa principale è la NavigableMap dove ogni chiave è l'inizio del periodo successivo e il valore è la raccolta di azioni per il periodo dall'avvio dato fino alla chiave successiva (i valori vuoti corrispondono ai periodi senza azioni). Dopo aver aggiunto la nuova attività, le voci esistenti vengono aggiornate di conseguenza. Esempio di utilizzo:

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

uscita:

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

Thank You Tagir! Scalata :) Mi spiace non essere stato chiaro nella mia domanda che anche il tempo è importante per me. Per semplicità nell'esempio l'ho lasciato fuori. Credo che la soluzione dovrebbe funzionare anche per LocalDateTime? Un'altra cosa da notare è che l'output dell'algoritmo è leggermente diverso da quello che mi aspetto.Come ascolto in tre punti elenco, mi aspetto tre output come output, prima dal 01/01 al 01/15 con A, B, secondo dal 01/15 al 01/31 con A, B, C e terzo dal 01/31 a 02/15 con C. Nell'output non ci saranno sovrapposizioni. Sto cercando di modificare la tua soluzione per gestire quella ... –

+0

@YohanLiyanage, sì, per 'LocalDateTime' funzionerà allo stesso modo, basta sostituire il nome della classe ovunque. Nell'esempio di esempio ho aggiunto un compito non sovrapposto (dell'anno 2014) solo per verificare se è gestito correttamente. Rimuovilo e l'output sarà lo stesso. –

+0

Oh mi sono perso che era il 2014 :). Grazie ancora, e questo funziona come un fascino –