Метод библиотеки для разбиения коллекции на предикат
У меня есть набор объектов, которые я хотел бы разбить на две коллекции, одна из которых передает предикат, а один из которых не выполняет предикат. Я надеялся, что для этого будет Guava, но ближайший из них - filter, который не дает мне другую коллекцию.
Я бы хотел, чтобы подпись метода была примерно такой:
public static <E> Pair<Collection<E>, Collection<E>> partition(Collection<E> source, Predicate<? super E> predicate)
Я понимаю, что это очень быстро, чтобы закодировать себя, но я ищу существующий метод библиотеки, который делает то, что я хочу.
Ответы
Ответ 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.
Ответ 4
Коллекции сообщества Apache IterableUtils
предоставляет методы для разбиения объектов Iterable
на основе одного или нескольких предикатов. (Ищите методы partition(...)
.)
Ответ 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]