2015-08-28 17 views
5

ho una serie di stringhe di input nel seguente formato:Java 8 groupingby con chiave personalizzata

typeA:code1, 
typeA:code2, 
typeA:code3, 
typeB:code4, 
typeB:code5, 
typeB:code6, 
typeC:code7, 
... 

e ho bisogno di ottenere un Map<String, List<String>> con la seguente struttura:

typeA, [code1, code2, code3] 
typeB, [code4, code5, code6] 
typeC, [code7, code8, ...] 

La cattura è che per generare ogni tipo devo chiamare una funzione come questo su ogni stringa di ingresso:

public static String getType(String code) 
{ 
    return code.split(":")[0]; // yes this is horrible code, it's just for the example, honestly 
} 

I' Sono piuttosto fiducioso che gli Stream e i Collector possono farlo, ma sto lottando per ottenere il giusto incantesimo di incantesimi per farlo accadere.

risposta

8

Ecco un modo per farlo (assumendo che la classe si chiama A):

Map<String, List<String>> result = Stream.of(input) 
          .collect(groupingBy(A::getType, mapping(A::getValue, toList()))); 

Se si desidera che l'output ordinato è possibile utilizzare un TreeMap al posto del HashMap default:

.collect(groupingBy(A::getType, TreeMap::new, mapping(A::getValue, toList()))); 

esempio completo:

public static void main(String[] args) { 
    String input[] = ("typeA:code1," + 
       "typeA:code2," + 
       "typeA:code3," + 
       "typeB:code4," + 
       "typeB:code5," + 
       "typeB:code6," + 
       "typeC:code7").split(","); 

    Map<String, List<String>> result = Stream.of(input) 
        .collect(groupingBy(A::getType, mapping(A::getValue, toList()))); 
    System.out.println(result); 
} 

public static String getType(String code) { 
    return code.split(":")[0]; 
} 
public static String getValue(String code) { 
    return code.split(":")[1]; 
} 
+0

Grazie, questo è abbastanza vicino a quello che mi è venuto in mente. Mi sono reso conto che il tipo del primo parametro in groupingBy è un func, e che potrei usare la mia funzione getType lì. – endian

6

Il codice diventa semplice se si considera ciò che avete omesso, tha t che serve la seconda parte della stringa divisa così:

Map<String, List<String>> result = Stream.of(input).map(s->s.split(":", 2)) 
    .collect(groupingBy(a->a[0], mapping(a->a[1], toList()))); 

(ammesso che abbiate un import static java.util.stream.Collectors.*;)

Non c'è niente di sbagliato con la suddivisione di un String in un array, l'attuazione ha anche un “veloce -path "per il caso comune che si sta dividendo usando un singolo carattere semplice invece di un'espressione regolare complicata.

+0

Grazie, anche questo è un buon approccio. – endian

2

Anche se ero troppo lento, ecco un MCVE che mostra come questo può essere risolto con Collectors#groupingBy.

Esistono ovviamente diverse opzioni per la definizione di "classificatore" e "mappatore". Qui sto semplicemente usando String#substring per trovare la parte prima e dopo lo ":".

import static java.util.stream.Collectors.groupingBy; 
import static java.util.stream.Collectors.mapping; 
import static java.util.stream.Collectors.toList; 

import java.util.ArrayList; 
import java.util.List; 
import java.util.Map; 
import java.util.Map.Entry; 

public class GroupingBySubstringsTest 
{ 
    public static void main(String[] args) 
    { 
     List<String> strings = new ArrayList<String>(); 
     strings.add("typeA:code1"); 
     strings.add("typeA:code2"); 
     strings.add("typeA:code3"); 
     strings.add("typeB:code4"); 
     strings.add("typeB:code5"); 
     strings.add("typeB:code6"); 
     strings.add("typeC:code7"); 

     Map<String, List<String>> result = strings.stream().collect(
      groupingBy(s -> s.substring(0, s.indexOf(":")), 
       mapping(s -> s.substring(s.indexOf(":")+1), toList()))); 

     for (Entry<String, List<String>> entry : result.entrySet()) 
     { 
      System.out.println(entry); 
     } 
    } 
}