Library method to partition a collection by a predicate

别说谁变了你拦得住时间么 提交于 2019-12-30 01:37:11

问题


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.


回答1:


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]



回答2:


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



回答3:


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.




回答4:


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




回答5:


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



回答6:


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]


来源:https://stackoverflow.com/questions/10547631/library-method-to-partition-a-collection-by-a-predicate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!