29

I have a collection of objects that I would like to partition into two collections, one of which passes a predicate and one of which fails a predicate. I was hoping there would be a Guava method to do this, but the closest they come is filter, which doesn't give me the other collection.

I would image the signature of the method would be something like this:

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

I realize this is super fast to code myself, but I'm looking for an existing library method that does what I want.

3
  • Note that in case of limited set of known in advance partiotion keys it may be much more efficient GC-wise just to iterate the collection once more for each partition key skipping all different-key items on each iteration. Commented Jan 31, 2018 at 16:21
  • Another both GC-friendly and encapsulated approach is using Java 8 filtering wrapper streams around original collection: stackoverflow.com/questions/19940319/… Commented Jan 31, 2018 at 16:31
  • Anybody know when/where a partition function first appeared in a collections library? Commented Jan 23, 2021 at 5:42

6 Answers 6

27

Use Guava's Multimaps.index.

Here is an example, which partitions a list of words into two parts: those which have length > 3 and those that don't.

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

prints:

false=[foo, bar], true=[hello, world] 
Sign up to request clarification or add additional context in comments.

3 Comments

If you already have a Predicate you can turn it into a Function with Functions.forPredicate.
Update for Java 8: a similar method is also provided in the streaming package java.util.stream.Collectors#groupingBy(java.util.function.Function<....>) It goes like words.stream().collect(Collectors.groupingBy(func))
@PeterandtheWolf i have added an answer based on your suggestion
16

With the new java 8 features(stream and lambda epressions), you could write:

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

Comments

5

If you're using Eclipse Collections (formerly GS Collections), you can use the partition method on all 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()); 

The reason for using a custom type, PartitionMutableList, instead of Pair is to allow covariant return types for getSelected() and getRejected(). For example, partitioning a MutableCollection gives two collections instead of lists.

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

If your collection isn't a RichIterable, you can still use the static utility in Eclipse Collections.

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

Note: I am a committer for Eclipse Collections.

Comments

2

seems like a good job for the new 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] 

Comments

0

Apache Commons Collections IterableUtils provides methods for partitioning Iterable objects based on one or more predicates. (Look for the partition(...) methods.)

Comments

0

Note that in case of limited set of known in advance partiotion keys it may be much more efficient just to iterate the collection once more for each partition key skipping all different-key items on each iteration. As this would not allocate many new objects for 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)); } 

Another both GC-friendly and encapsulated approach is using Java 8 filtering wrapper streams around the original collection:

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

Both snippets print this:

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 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.