2016-03-10 6 views
5

Supponiamo di avere un elenco di intervalli (ordinati per inizio) e di suddividerli in modo da avere un elenco di gruppi di intervalli sovrapposti. Così, per esempio, con Interval come:Elenco di partizioni Java 8 in gruppi in base a elementi precedenti

public class Interval { 
    private final int start; 
    private final int end; 

    public Interval(int start,int end){ 
     this.start = start; 
     this.end = end; 
    } 

    public int getStart(){return start;} 
    public int getEnd(){return end;} 

    public String toString(){ return "("+start+","+end+")"; } 
} 

E un List<Interval> come:

[(0,4),(1,7),(6,10),(13,17),(20,100),(22,31),(60,65)] 

voglio una potenza di List<List<Interval>>:

[[(0,4),(1,7),(6,10)],[(13,17)],[(20,100),(22,31),(60,65)]] 

posso codice questo in su, ma io Mi sto davvero godendo l'approccio più funzionale di Java 8 e voglio sapere se c'è qualcosa di simile a un modo idiomatico per farlo usando i flussi Java 8.

Ho dato uno sguardo agli stili "raggruppati" di Collectors forniti, ma non sembrano applicabili poiché non sto realmente raggruppando per un classificatore- non è possibile calcolare i gruppi basati solo su una proprietà di ogni singolo elemento, devi considerare le proprietà di ogni elemento in relazione ai gruppi che sono stati calcolati finora.

Certamente non esiste un modo non folle di farlo in linguaggi funzionali (anche se parlo come qualcuno che non è realmente un programmatore funzionale :-)). Come posso farlo con i flussi in Java 8?

+2

Immagino che 'this.start = end;' non è quello che vuoi. Ma è una buona cosa che tu stia usando le variabili 'final' in modo che l'errore sia immediatamente individuato dal compilatore. – Holger

+1

A proposito, quale dovrebbe essere l'output quando l'input è il seguente: '[(60, 65), (22, 31), (20, 100)]'? Dovrebbero essere uniti tutti e tre gli intervalli? In altre parole, l'ordine degli intervalli di input può modificare il risultato? –

+1

@Tagir Valeev: il prerequisito della domanda (nella prima frase) è che gli elementi sono ordinati in base al loro punto di partenza. Risolvere facilmente questo requisito è possibile anticipando una fase di ordinamento a qualsiasi soluzione. – Holger

risposta

4

Non è possibile. Gli stream non sono adatti a questo tipo di problema; i flussi non hanno nozione di "elementi precedenti" e sono autorizzati a operare su elementi in ordine arbitrario. Puoi farlo in Java, certo, e puoi farlo in linguaggi funzionali, ma ciò non significa che gli stream funzionino come le strutture dati del linguaggio funzionale a cui sei abituato.

+0

** avanza verso l'alto ma manca il rappresentante – codeCogs

+2

Non tutte le operazioni sono consentite per elaborare gli elementi in ordine arbitrario. Consentire l'ordine arbitrario renderebbe l'implementazione, ad es. 'Collectors.toList()', molto difficile ... – Holger

+0

Ma toList non consente elementi in ordine arbitrario, ma unisce gli accumulatori insieme in modo ordinato. –

4

Stavo cercando il posto giusto quando studi i collettori groupingBy, ma hai anche ragione che non forniranno la logica necessaria per gli intervalli di fusione. Ma sono elementi concettualmente fondenti nello stato creato da elementi precedenti. Devi implementare un collezionista simile tu stesso.

Contando sulla vostra specifica che gli elementi sono già preordinati dal loro indice di partenza, si può fare come:

Comparator<Interval> byStart = Comparator.comparingInt(Interval::getStart); 
Comparator<Interval> byEnd = Comparator.comparingInt(Interval::getEnd); 
Collection<List<Interval>> merged = intervalList.stream().collect(
     () -> new TreeMap<Interval,List<Interval>>(byStart), 
     (map,i) -> { 
      Map.Entry<Interval,List<Interval>> e=map.floorEntry(i); 
      if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart()) 
       e.getValue().add(i); 
      else map.computeIfAbsent(i, x->new ArrayList<>()).add(i); 
     }, 
     (m1,m2) -> m2.forEach((i,list) -> { 
      Map.Entry<Interval,List<Interval>> e=m1.floorEntry(i); 
      if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart()) 
       e.getValue().addAll(list); 
      else m1.put(i, list); 
     }) 
    ).values(); 

Questo crea un Collection piuttosto che un List, ma si può semplicemente creare un List fuori iT:

List<List<Interval>> list = new ArrayList<>(merged); 

si dovrebbe fare che sicuramente se avete intenzione di mantenere il risultato per un tempo più lungo, piuttosto che il trattamento subito, come il Collection restituito dal raccoglitore è una vista in un TreeMap in possesso di più risorse del necessario.

Immagino che nella maggior parte dei casi si stia meglio con una soluzione basata su loop.

+0

Bella soluzione! (1) Penso che il combinatore sia inutile se l'accumulatore attraversa in serie ogni elemento del flusso (che deve essere il caso se questa soluzione funzionerà). (2) Concordato: un loop potrebbe essere più trasparente ed efficiente in questo caso. – codeCogs

+1

@codeCogs: il combinatore non sarà usato per un flusso sequenziale ma è uno stile di codifica buono per fornire sempre una combinazione di lavoro per evitare sorprese nel futuro. Formalmente, non farlo è anche una violazione del contratto API. Il combinatore di questa soluzione funziona correttamente, ma fa di tutto per ottenere ciò, che può trarre vantaggio dall'elaborazione parallela, se ce ne sono da cominciare ... – Holger

Problemi correlati