2012-05-11 12 views
26

Ho una raccolta di oggetti che vorrei suddividere in due raccolte, una delle quali supera un predicato e una di queste non ha un predicato. Speravo che ci fosse un metodo Guava per farlo, ma il più vicino possibile è filter, che non mi dà l'altra collezione.Metodo di libreria per partizionare una raccolta tramite un predicato

avrei immagine la firma del metodo sarebbe qualcosa di simile:

public static <E> Pair<Collection<E>, Collection<E>> partition(Collection<E> source, Predicate<? super E> predicate) 

Mi rendo conto che è super veloce al codice stesso, ma sto cercando un metodo di libreria esistente che fa quello che voglio .

+0

notare che in caso di un numero limitato di note in anticipo rispetto alle chiavi speciali può essere molto più efficiente per GC solo per ripetere la raccolta una volta di più per ogni chiave di partizione saltando tutti gli elementi chiave differenti su ciascuna iterazione. – Vadzim

+0

Un altro approccio sia GC-friendly che incapsulato utilizza flussi di wrapper di filtraggio Java 8 attorno alla raccolta originale: https://stackoverflow.com/questions/19940319/can-you-split-a-stream-into-two-streams – Vadzim

risposta

24

Utilizzare Guava Multimaps.index.

Ecco un esempio, che suddivide un elenco di parole in due parti: quelle che hanno lunghezza> 3 e quelle che non lo fanno.

List<String> words = Arrays.asList("foo", "bar", "hello", "world"); 

ImmutableListMultimap<Boolean, String> partitionedMap = Multimaps.index(words, new Function<String, Boolean>(){ 
    @Override 
    public Boolean apply(String input) { 
     return input.length() > 3; 
    } 
}); 
System.out.println(partitionedMap); 

stampe:

false=[foo, bar], true=[hello, world] 
+1

Grazie, Non avrei pensato di guardare lì. –

+4

Se si dispone già di un predicato, è possibile trasformarlo in una funzione con funzioni.forPredicate. – roryparle

+4

Aggiornamento per Java 8: un metodo simile è fornito anche nel pacchetto di streaming 'java.util.stream.Collectors # groupingBy (java.util.function.Function <....>)' Va come 'words.stream(). (Collectors.groupingBy (func)) ' –

3

Se stai usando Eclipse Collections (ex GS Collections), è possibile utilizzare il metodo partition su tutti RichIterables.

MutableList<Integer> integers = FastList.newListWith(-3, -2, -1, 0, 1, 2, 3); 
PartitionMutableList<Integer> result = integers.partition(IntegerPredicates.isEven()); 
Assert.assertEquals(FastList.newListWith(-2, 0, 2), result.getSelected()); 
Assert.assertEquals(FastList.newListWith(-3, -1, 1, 3), result.getRejected()); 

La ragione per usare un tipo personalizzato, PartitionMutableList, invece di Pair è quello di permettere tipi restituiti covarianti per getSelected() e getRejected(). Ad esempio, il partizionamento di MutableCollection fornisce due raccolte anziché elenchi.

MutableCollection<Integer> integers = ...; 
PartitionMutableCollection<Integer> result = integers.partition(IntegerPredicates.isEven()); 
MutableCollection<Integer> selected = result.getSelected(); 

Se la vostra collezione non è un RichIterable, è comunque possibile utilizzare l'utilità statica in Eclipse collezioni.

PartitionIterable<Integer> partitionIterable = Iterate.partition(integers, IntegerPredicates.isEven()); 
PartitionMutableList<Integer> partitionList = ListIterate.partition(integers, IntegerPredicates.isEven()); 

Nota: Sono un committer per Eclipse collezioni.

8

Con le nuove funzionalità Java 8 (stream e lambda epressions), si potrebbe scrivere:

List<String> words = Arrays.asList("foo", "bar", "hello", "world"); 

Map<Boolean, List<String>> partitionedMap = 
     words.stream().collect(
       Collectors.partitioningBy(word -> word.length() > 3)); 

System.out.println(partitionedMap); 
0

Nota che in caso di un numero limitato di note in anticipo, le chiavi speciali possono essere molto più efficienti solo per ripetere la raccolta ancora una volta per ogni chiave di partizione saltando tutti gli elementi chiave su ciascuna iterazione. Poiché questo non alloca molti nuovi oggetti per Garbage Collector.

LocalDate start = LocalDate.now().with(TemporalAdjusters.firstDayOfYear()); 
LocalDate endExclusive = LocalDate.now().plusYears(1); 
List<LocalDate> daysCollection = Stream.iterate(start, date -> date.plusDays(1)) 
     .limit(ChronoUnit.DAYS.between(start, endExclusive)) 
     .collect(Collectors.toList()); 
List<DayOfWeek> keys = Arrays.asList(DayOfWeek.values()); 

for (DayOfWeek key : keys) { 
    int count = 0; 
    for (LocalDate day : daysCollection) { 
     if (key == day.getDayOfWeek()) { 
      ++count; 
     } 
    } 
    System.out.println(String.format("%s: %d days in this year", key, count)); 
} 

Un altro sia GC-friendly e approccio incapsulato sta usando Java 8 flussi di filtraggio involucro intorno alla collezione originale:

List<AbstractMap.SimpleEntry<DayOfWeek, Stream<LocalDate>>> partitions = keys.stream().map(
     key -> new AbstractMap.SimpleEntry<>(
       key, daysCollection.stream().filter(
        day -> key == day.getDayOfWeek()))) 
     .collect(Collectors.toList()); 
// partitions could be passed somewhere before being used 
partitions.forEach(pair -> System.out.println(
     String.format("%s: %d days in this year", pair.getKey(), pair.getValue().count()))); 

Entrambi i frammenti di stampare questa:

MONDAY: 57 days in this year 
TUESDAY: 57 days in this year 
WEDNESDAY: 57 days in this year 
THURSDAY: 57 days in this year 
FRIDAY: 56 days in this year 
SATURDAY: 56 days in this year 
SUNDAY: 56 days in this year 
Problemi correlati