Подтвердить что ты не робот

Метод библиотеки для разбиения коллекции на предикат

У меня есть набор объектов, которые я хотел бы разбить на две коллекции, одна из которых передает предикат, а один из которых не выполняет предикат. Я надеялся, что для этого будет Guava, но ближайший из них - filter, который не дает мне другую коллекцию.

Я бы хотел, чтобы подпись метода была примерно такой:

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

Я понимаю, что это очень быстро, чтобы закодировать себя, но я ищу существующий метод библиотеки, который делает то, что я хочу.

4b9b3361

Ответ 1

Использовать Guava Multimaps.index.

Вот пример, который разбивает список слов на две части: те, которые имеют длину > 3, и те, которые этого не делают.

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

печатает:

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

Ответ 2

С новыми функциями java 8 (stream и lambda epressions), вы можете написать:

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

Ответ 3

Если вы используете Eclipse Collections (ранее коллекции GS), вы можете использовать метод partition для всех 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());

Причиной использования настраиваемого типа PartitionMutableList вместо Pair является разрешение ковариантных типов возврата для getSelected() и getRejected(). Например, разбиение a MutableCollection дает две коллекции вместо списков.

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

Если ваша коллекция не является RichIterable, вы все равно можете использовать статическую утилиту в коллекциях Eclipse.

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

Примечание: Я являюсь коммиттером для коллекций Eclipse.

Ответ 5

Обратите внимание, что в случае ограниченного набора известных заранее ключей доступа может быть намного эффективнее только повторить сборку снова для каждого ключа раздела, пропускающего все элементы с разными ключами на каждой итерации. Поскольку это не будет выделять много новых объектов для 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));
}

Еще один подход, совместимый с GC и инкапсулированный подход, - это использование потоков оболочки XML 8 вокруг исходной коллекции:

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

Оба фрагмента печатают это:

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

Ответ 6

кажется хорошей работой для новых Collectors::teeing Java 12 Collectors::teeing

var dividedStrings = Stream.of("foo", "hello", "bar", "world")
            .collect(Collectors.teeing(
                    Collectors.filtering(s -> s.length() <= 3, Collectors.toList()),
                    Collectors.filtering(s -> s.length() > 3, Collectors.toList()),
                    List::of
            ));
System.out.println(dividedStrings.get(0)); //[foo, bar]
System.out.println(dividedStrings.get(1)); //[hello, world]