Reputation: 30143
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.
Upvotes: 29
Views: 11398
Reputation: 3134
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]
Upvotes: 2
Reputation: 26200
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
Upvotes: 0
Reputation: 274788
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]
Upvotes: 27
Reputation: 141
Apache Commons Collections IterableUtils
provides methods for partitioning Iterable
objects based on one or more predicates. (Look for the partition(...)
methods.)
Upvotes: 0
Reputation: 26748
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.
Upvotes: 5
Reputation: 29530
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);
Upvotes: 16