【问题标题】:Library method to partition a collection by a predicate通过谓词划分集合的库方法
【发布时间】:2012-05-19 19:55:26
【问题描述】:

我有一个对象集合,我想将它们划分为两个集合,其中一个通过谓词,另一个通过谓词。我希望有一个Guava 方法可以做到这一点,但他们最接近的是filter,它不会给我其他集合。

我会想象方法的签名是这样的:

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

我意识到自己编写代码非常快,但我正在寻找一种现有的库方法来满足我的需求。

【问题讨论】:

  • 请注意,在有限的一组预先知道的分区键的情况下,在每次迭代时跳过所有不同键项的每个分区键再次迭代集合可能会更高效.
  • 另一种 GC 友好和封装的方法是使用 Java 8 过滤原始集合周围的包装流:stackoverflow.com/questions/19940319/…
  • 有人知道分区函数首次出现在集合库中的时间/地点吗?

标签: java collections guava


【解决方案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]

【讨论】:

  • 谢谢,我没想到会去那里看看。
  • 如果你已经有一个谓词,你可以用 Functions.forPredicate 把它变成一个函数。
  • Java 8 的更新:流包中也提供了类似的方法java.util.stream.Collectors#groupingBy(java.util.function.Function&lt;....&gt;) 它类似于words.stream().collect(Collectors.groupingBy(func))
  • @PeterandtheWolf 我已根据您的建议添加了答案
【解决方案2】:

有了新的 java 8 特性(streamlambda 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 Collections),则可以对所有 RichIterables 使用 partition 方法。

    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() 的协变返回类型。例如,对MutableCollection 进行分区会产生两个集合而不是列表。

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

    如果您的收藏不是 RichIterable,您仍然可以使用 Eclipse Collections 中的静态实用程序。

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

    注意:我是 Eclipse Collections 的提交者。

    【讨论】:

      【解决方案4】:

      对于新的 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]
      

      您可以找到更多示例here

      【讨论】:

        【解决方案5】:

        Apache Commons Collections IterableUtils 提供了基于一个或多个谓词划分Iterable 对象的方法。 (查找partition(...) 方法。)

        【讨论】:

          【解决方案6】:

          请注意,如果预先知道有限的分区键集,对于每个分区键再次迭代集合可能会更有效,在每次迭代中跳过所有不同的键项。因为这不会为垃圾收集器分配很多新对象。

          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 友好且封装的方法是在原始集合周围使用 Java 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())));
          

          两个 sn-ps 都打印这个:

          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
          

          【讨论】:

            猜你喜欢
            • 2020-05-20
            • 1970-01-01
            • 2019-03-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-12
            相关资源
            最近更新 更多