Método de biblioteca para particionar una colección por un predicado


Tengo una colección de objetos que me gustaría dividir en dos colecciones, una de las cuales pasa un predicado y otra de las cuales falla un predicado. Esperaba que hubiera un método Guava para hacer esto, pero lo más cercano que vienen es filter , que no me da la otra colección.

Imaginaría que la firma del método sería algo así:

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

Me doy cuenta de que esto es súper rápido para codificar, pero estoy buscando un existente método de biblioteca que hace lo que quiero.

Author: sebkur, 2012-05-11

5 answers

Use Guayabos Multimaps.index.

Aquí hay un ejemplo, que divide una lista de palabras en dos partes: las que tienen longitud > 3 y las que no.

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

Impresiones:

false=[foo, bar], true=[hello, world]
 24
Author: dogbane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-23 13:41:37

Con las nuevas características de java 8 ( stream y lambda epresions ), podría escribir:

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);
 9
Author: gontard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-23 09:18:04

Si está utilizando Colecciones de Eclipse (anteriormente Colecciones GS), puede usar el método partition en todos 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 razón para usar un tipo personalizado, PartitionMutableList, en lugar de Pair es permitir tipos de retorno covariantes para getSelected() y getRejected(). Por ejemplo, particionar un MutableCollection da dos colecciones en lugar de listas.

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

Si su colección no es un RichIterable, aún puede usar la utilidad estática en Eclipse Colecciones.

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

Nota: Soy un committer para las Colecciones de Eclipse.

 3
Author: Craig P. Motlin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-28 22:52:05

Colecciones de Apache Commons IterableUtils proporciona métodos para particionar objetos Iterable basados en uno o más predicados. (Busque los métodos partition(...).)

 0
Author: Christoph Leuzinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-28 09:04:35

Tenga en cuenta que en caso de un conjunto limitado de claves de partición conocidas de antemano, puede ser mucho más eficiente iterar la colección una vez más para cada clave de partición omitiendo todos los elementos de clave diferente en cada iteración. Como esto no asignaría muchos objetos nuevos para el Recolector de basura.

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

Otro enfoque amigable con GC y encapsulado es el uso de flujos de envoltura de filtrado Java 8 alrededor de la colección original:

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

Ambos fragmentos imprimen esto:

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
 0
Author: Vadzim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-01 09:12:53