【问题标题】:Collect objects with max value in a list with streams [duplicate]在带有流的列表中收集具有最大值的对象[重复]
【发布时间】:2019-07-04 13:02:08
【问题描述】:

我几乎完成了我的大学扑克项目,但我仍然在 java 流中挣扎。 我编写了一个 HandEvaluator 类,它计算每个玩家手牌的强度并将其分配给玩家。现在我正在尝试将手牌得分最高的一个或多个玩家(如果多个玩家的得分相同/分池)添加到一个列表中以计算胜率。

我遇到了流的语法问题。我正在尝试这样的事情:

playerList.stream().max(Comparator.comparing(Player::getHandScore)).get();

这是返回得分最高的玩家,但是如果有多个得分相同怎么办?以及如何将它们添加到列表中?

【问题讨论】:

    标签: java java-stream


    【解决方案1】:

    我做了这样的事情。我按分数分组,然后找到具有最大键值的分数。它将返回Map.EntryOptional。它包含最大值和拥有它的玩家。然后我可以使用getValue()方法获取玩家列表。

    List<Player> value = playerList.stream()
            .collect(groupingBy(Player::getScore))
            .entrySet()
            .stream()
            .max(Comparator.comparing(Map.Entry::getKey))
            .get()
            .getValue();
    

    【讨论】:

      【解决方案2】:

      一个易于理解的解决方案如下:

      int maxHighScore = playerList.stream()
                                   .map(player -> player.getHandScore())
                                   .max()
                                   .orElse(-1);
      
      List<Player> highestHandScores = playerList.stream()
                                                 .filter(player -> player.getHandScore() == maxHighScore)
                                                 .collect(Collectors.toList());
      

      在第一步中,我们得到 maxHighScore,在第二步中,我们过滤玩家以仅保留得分最高的玩家。

      【讨论】:

        【解决方案3】:
            int max = playerList.stream()
                    .max(Comparator.comparing(Player::getHandScore))
                    .get()
                    .getHandScore();
        
            List<Player> playerLists = playerList
                    .stream()
                    .filter(m -> m.getHandScore() == max)
                    .collect(Collectors.toList());
        
        

        【讨论】:

          【解决方案4】:

          我的答案将是带有SortedMapMC Emperor's anwer 版本,但此版本会在找到得分较高的单个玩家后立即丢弃“较低”组。这个版本适用于当元素真的很多时,保留所有可见的元素可能会成为问题。例如,当从一个非常大的文件中读取流式项目时,该文件将无法放入内存中。

          整个解决方案如下所示:

          List<Player> topScore = playersStream().collect(
              topGroup(Comparator.comparingInt(Player::getHandScore))
          );
          

          要使其正常工作,您将需要一个带有状态容器的自定义收集器来保存组。我不确定 JDK 中是否有类似的东西(无论如何都不是 8),但您可能可以在其中一个库中找到它。面向外的方法如下所示:

          static <T> Collector<T, ?, List<T>> topGroup(final Comparator<? super T> comparator) {
              Objects.requireNonNull(comparator, "comparator");
              return Collector.of(
                  () -> new Group<>(comparator),
                  // My local compiler can't infer type properly, I had to help it.
                  // Your experience may be different
                  (BiConsumer<Group<T>, T>) Group::accept,
                  Group::merge,
                  Group::asList
             );
          }
          

          最重要的部分是有状态的Group&lt;T&gt;。它的目的是成为外部比较器认为排序最高的元素的容器。一旦遇到更高阶的元素,该组就会丢弃其所有先前的内容。示例实现是:

          private static class Group<T> {
              private final Comparator<? super T> comparator;
              T sample;
              List<T> more;
          
              public Group(Comparator<? super T> comparator) {
                  this.comparator = comparator;
              }
          
              public void accept(T el) {
                  if (sample == null) {
                      sample = el;
                  }
                  else {
                      int order = comparator.compare(sample, el);
                      if (order == 0) {
                          more().add(el);
                      }
                      else if (order > 0) {
                          // element of a higher order, discard everything and make it a sample
                          sample = el;
                          more = null;
                      }
                      // else {element of a lower order, ignore}
                  }
              }
          
              public Group<T> merge(Group<T> other) {
                  if (this.comparator != other.comparator) {
                      throw new IllegalArgumentException("Cannot merge groups with different orders");
                  }
                  if (sample == null) {
                      return other; // we're empty
                  }
                  int order = comparator.compare(this.sample, other.sample);
                  if (order >= 0) {
                      if (order == 0) {
                          // merge with other group
                          more().addAll(other.asList());
                      }
                      return this;
                  }
                  else {
                      // other group is higher than us
                      return other;
                  }
              }
          
              public List<T> asList() {
                  List<T> result = new ArrayList<>();
                  if (sample != null) {
                      result.add(sample);
                  }
                  if (more != null) {
                      result.addAll(more);
                  }
                  return result;
              }
          }
          

          这个实现也是解决寻找“Top N with ties”问题的一个途径(我的实现是“Top 1 with ties”)。

          【讨论】:

            【解决方案5】:

            我对其他一些答案的问题是它们遍历了玩家列表两次——一次用于按玩家得分分组,另一次用于获取得分最高的玩家。这在正常情况下可能是微不足道的,但可能在玩家列表较大时会出现问题。

            为了避免遍历列表两次,要做的一件事是使用SortedMap。一旦我们按照分数对玩家进行分组,我们就可以简单地调用lastKey() 立即获得最高的密钥:

            SortedMap<Integer, List<Player>> topPlayers = playerList.stream()
                .collect(Collectors.groupingBy(Player::getScore, TreeMap::new, Collectors.toList()));
            topPlayers.get(topPlayers.lastKey());
            

            或者,正如 Holger 在 cmets 中所说,如果您使用 NavigableMap,您可以保存另一个地图查找:

            NavigableMap<Integer, List<Player>> topPlayers = playerList.stream()
                .collect(Collectors.groupingBy(Player::getScore, TreeMap::new, Collectors.toList()));
            topPlayers.lastEntry().getValue();
            

            但是,在我看来,链接帖子的answer given by Stuart Marks 更好,因为并非所有元素都被存储(分组到存储桶中),但是如果发现它们不属于最大值。

            这可能会节省内存。

            【讨论】:

            • topPlayers.get(topPlayers.lastKey()); 执行不必要的地图查找。当你将topPlayers的类型改为NavigableMap&lt;Integer, List&lt;Player&gt;&gt;时,可以改用topPlayers.lastEntry().getValue();
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-01-14
            • 1970-01-01
            • 2017-11-24
            • 2015-08-11
            • 1970-01-01
            • 1970-01-01
            • 2020-10-01
            相关资源
            最近更新 更多