Library method to partition a collection by a predicate

前端 未结 6 668
小鲜肉
小鲜肉 2020-12-29 21:02

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 wo

相关标签:
6条回答
  • 2020-12-29 21:39

    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);
    
    0 讨论(0)
  • 2020-12-29 21:41

    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]
    
    0 讨论(0)
  • 2020-12-29 21:43

    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]
    
    0 讨论(0)
  • 2020-12-29 21:52

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

    0 讨论(0)
  • 2020-12-29 21:59

    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
    
    0 讨论(0)
  • 2020-12-29 22:05

    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.

    0 讨论(0)
提交回复
热议问题