【问题标题】:Maximum of element from List based on the another oneList 中元素的最大值基于另一个元素
【发布时间】:2016-07-06 11:07:33
【问题描述】:

我需要找出属于另一个元素的最大元素。举个例子最清楚。

我的List<String> lines 包含以下数据:

1, 1, A, Aaa ...
1, 2, A, Aaa ...
1, 4, A, Aaa ...
2, 5, B, Bbb ...
2, 3, B, Bbb ...
3, 6, C, Ccc ...
4, 7, D, Ddd ...
5, 8, E, Eee ...
1, 9, A, Aaa ...
4, 10, D, Ddd ...

需要明确的是,两对数字永远不会相同,所以你永远不会得到:

1, 9, A, Aaa ...
1, 9, B, Bbb ...

我的目标是提取属于第一行的第二行最大值的行。确切地说是这些行:

1, 9, A, Aaa ...
2, 5, B, Bbb ...
3, 6, C, Ccc ...
4, 10, D, Ddd ...
5, 8, E, Eee ...

为了证明这不是作业,我使用多个for-loop 来找到最大值并存储到变量中。但是我不知道它是否有效,因为测试了大量数据 (200 000+)。

// List "lines" is declared above

List<List<String>> data = new ArrayList<>();
List<List<String>> maxValues = new ArrayList<>();

// clear and separate to clear comparable parts
for (String s: lines) {
    String parts[] = s.trim().replace("\"", "").split(";");
    List newList = Arrays.asList(parts);
    data.add(newList);
}

// naïve algorithm to find the maximum dependent to the another one
// not sure if working
for (List l: data) {
    int id = Integer.parseInt(l.get(0).toString());
    int max = 0;    
    List<String> tempMaxValues = new ArrayList<>();
    for (int i=0; i<data.size(); i++) {
        if (Integer.parseInt(l.get(0).toString()) == id) {
            int temp = Integer.parseInt(l.get(1).toString());
            if (temp > max) {
               max = temp;
               tempMaxValues = l;
            }
        }
    }
    maxValues.add(tempMaxValues);
}

此外,我需要做更多的计算。只有Stream 或更简单的方法才能达到我想要的结果?即使在我的代码中,我也会迷失方向。

【问题讨论】:

  • 寻求家庭作业的帮助并没有什么不好,坏的是没有努力产生自己的解决方案:)
  • 恐怕我不明白with the maximum of second row that belongs to the first row的意思。你能扩展一下吗?
  • @Sasha Salauyou:这确实不是功课。如果是这样,我可以坦率地承认这一点。 :) 我这样做是我在之前的工作中遇到的挑战,但在这种情况下我们使用了更好的 SQL。
  • @PaulBoddington:这就是为什么会有样本输入和期望的结果。
  • @PaulBoddington:这永远不会发生。两个值永远不会与另一个值相同。我编辑了,谢谢你的提示:)

标签: java list optimization arraylist java-stream


【解决方案1】:

在功能方面,您想要的是将每个列表值按其第一个元素分组,并仅选择第二个元素的最大值。使用 Stream API,您可以:

  1. 使用groupingBy(classifier, downstream) 收集器按列表的第一个元素分组。
  2. 应用于归类到同一键的所有值的下游收集器是maxBy(comparator),它只选择收集值中的最大值。在这种情况下,比较器在内置 comparingInt 的帮助下将每个列表的第二个值作为 int 进行比较。
  3. 由于maxBy 在没有收集到任何值的情况下返回一个Optional,我们通过调用collectingAndThen(finisher) 来包装它,其中完成器检索Optional 值(我们知道在这种情况下至少有一个值将被分类)调用Optional.get()
  4. 最后,我们只保留生成的Map&lt;String, List&lt;String&gt;&gt; 中的values(),因为这会返回一个Collection&lt;List&lt;String&gt;&gt;,所以我们基于它创建一个ArrayList

示例代码:

List<List<String>> maxValues = new ArrayList<>(
    data.stream()
        .collect(Collectors.groupingBy(
            l -> l.get(0),
            Collectors.collectingAndThen(
                Collectors.maxBy(Comparator.comparingInt(l -> Integer.parseInt(l.get(1)))), 
                Optional::get
            )
        ))
        .values()
);

导致

[1, 9, A, Aaa], [2, 5, B, Bbb], [3, 6, C, Ccc], [4, 10, D, Ddd], [5, 8, E, Eee]]

用于您的示例数据。

【讨论】:

  • 谢谢你的回答,我试试。这个复杂流之王对我来说非常复杂,我需要花点时间来理解它。除了尝试和尝试之外,您还会建议我学习流的最佳方法吗?
  • @NikolasCharalambidis Oracle 在docs.oracle.com/javase/tutorial/collections/streams 有一个很好的教程来开始使用 Stream API。
【解决方案2】:

对我来说,“naïve”类似于使用 Map.merge() 通过唯一键(id 值)收集行:

static final Function<List<String>, Integer> GET_ID = l -> Integer.parseInt(l.get(0));
static final Function<List<String>, Integer> GET_TEMP = l -> Integer.parseInt(l.get(1));

Map<Integer, List<String>> max = new TreeMap<>(); 
for (List<String> l : data) 
    max.merge(GET_ID.apply(l), l, BinaryOperator.maxBy(Comparator.comparing(GET_TEMP))); 

之后,只有相同id的行中第二个值最大的行才会存储在max映射中。

【讨论】:

  • 谢谢你的回答,我试试看:)你为什么用TreeMap?请给我解释一下好吗?
  • @NikolasCharalambidis TreeMap 返回按键排序的条目(在您的情况下,按“id”),并实现SortedMapNavigableMap,允许范围查询、下一个/上一个键等
  • 非常简洁 +1。我认为您可以通过将mergeBinaryOperator.maxBy 结合使用来使其更短。
【解决方案3】:

另一种方法是使用toMap 收集器和BinaryOperator.maxBy 作为合并函数。给定List&lt;String&gt; lines 作为输入,您可以通过这种方式获得最佳字符串:

Collection<String> maxValues = lines.stream()
        .collect(Collectors.toMap(
                l -> l.split(",", 2)[0], 
                l -> l,
                BinaryOperator.maxBy(Comparator.comparingInt(
                        l -> Integer.parseInt(l.split(",", 3)[1].trim()))))).values();
System.out.println(maxValues);

可能将合并运算符提取到变量中看起来更好:

BinaryOperator<String> maxBy = BinaryOperator.maxBy(Comparator.comparingInt(
            l -> Integer.parseInt(l.split(",", 3)[1].trim())));
Collection<String> maxValues = lines.stream()
        .collect(Collectors.toMap(l -> l.split(",", 2)[0], l -> l, maxBy)).values();

如果您有List&lt;List&lt;String&gt;&gt;作为输入(已经进行了拆分和修剪),您可以通过以下方式找到Collection&lt;List&lt;String&gt;&gt;

BinaryOperator<List<String>> maxBy = BinaryOperator.maxBy(Comparator
        .comparingInt(l -> Integer.parseInt(l.get(1))));
Collection<List<String>> maxValues = lines.stream()
        .collect(Collectors.toMap(l -> l.get(0), l -> l, maxBy)).values();

【讨论】:

  • 感谢您的回答。你能给我介绍更多BinaryOperator吗?任何页面解释得很好?
  • @NikolasCharalambidis,官方javadoc 很好地涵盖了它。
【解决方案4】:

如果我理解得很好,您可以使用其他方法解决您的问题; 首先:创建一个包含您的数据(对象)的类

public class DataObject {
      int n1;
      int n2;
      String s1;
      String s2;
}

并创建您的对象列表:

List<DataObject> data = new ArrayList<DataObject>();
List maxVal = new ArrayList<DataObject>();

for (DataObject dO1 : data){
    for (DataObject dO2 : data){
        if (dO1.n1 == dO2.n2){
           /*test to determin the max value and 
            *store it in maxVal
           */
        }
    }
}

这将更容易访问数据,还是我弄错了?

【讨论】:

  • 我一直在思考如何将数据存储到Objects中。但是,由于我应用于输入文件的外部因素(更改列的顺序),我拒绝了它。因此,将数据存储到对象中会很麻烦。此外,你有错误的比较器,你使用了双 ==
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-13
  • 2016-10-07
  • 1970-01-01
  • 2011-07-10
  • 1970-01-01
  • 2011-05-06
  • 1970-01-01
相关资源
最近更新 更多