【问题标题】:Java groupingBy and transform StringsJava groupingBy 和转换字符串
【发布时间】:2021-02-12 01:13:39
【问题描述】:

我收到一个字符串列表,我试图用.groupingBy() 进行分区,这将给我一个Map<String, List<String>>,并转换最终将出现在地图中的列表中的字符串。

Examples I've found 使用 Collectors.mapping,这是我想使用的,但所有示例都使用方法引用,例如 .mapping(SomeClass::getSomething, ...,因为我正在处理字符串,所以无法完成。

我以为我可以使用 lambda 作为映射函数,但似乎无法正确使用语法,最终出现“类型不匹配:无法转换...”错误。

这说明了我正在尝试做的类型——

import java.util.*;
import java.util.stream.Collectors;

class Main
{
  public static void main(String[] args)
  {
        List<String> inputs = Arrays.asList("D8", "S1", "S5", "D2", "D15", "S9");
        System.out.println(inputs);

        Map<String, List<String>>
        outputs = inputs.stream()
              .collect(
                  Collectors.groupingBy(s -> s.startsWith("D") ? "desperate" : "serious")
                  //,Collectors.mapping(n -> Integer.valueOf(n.replaceAll("[DS]", "")), Collectors.toList())
              )
              ;

        System.out.println(outputs);
  }
}

这将创建地图,按照我的预期对输入数据进行分组,并生成输出
{desperate=[D8, D2, D15], serious=[S1, S5, S9]}

但我想要什么,正如您可能从注释掉的行中猜到的那样
Collectors.mapping(n -&gt; Integer.valueOf(n.replaceAll("[DS]", "")), Collectors.toList())
是一个Map&lt;String, List&lt;Integer&gt;&gt;,包含通过删除前缀从字符串中提取的值。

我的实际用例可能会将一个字符串转换为另一个字符串,或者从更复杂的字符串中提取整数,但这说明了我正在尝试做的事情。

在 groupingBy 时如何使用 lambda 函数进行映射?
从我读过的例子中,我也不清楚Collectors.mapping 中第二个参数toList() 的使用。

我用这段代码创建了一个repl

【问题讨论】:

  • 没有错,只是多加了')'。
  • 我认为这只是一个错字。 mapping 收集器应该是 groupingBy 调用的第二个参数,而不是 collect 调用。
  • @Sweeper - 不确定我是否会称之为错字,我误解了收藏家协会。根据 Erik 的回答弄清楚了。
  • 我明白了。关闭投票被撤回。
  • @Horse 显然,您对流和收集器的工作方式有一个错误的想法。无论您是在收集器中还是在收集之前的步骤中应用映射,都没有区别。您可以使用this program(当您在具有更多内核和更大控制台的系统上复制和运行它时会变得更好)。您会看到,无论哪种情况,工作线程都会对特定元素执行相同的步骤,并且结果并行度是相同的。

标签: java java-stream collectors


【解决方案1】:

Collectors.groupingBy() 的版本采用另一个收集器获取值,例如:

@Test
public void streamsTest() {
    List<String> inputs = Arrays.asList("D8", "S1", "S5", "D2", "D15", "S9");
    System.out.println(inputs);

    Map<String, List<Integer>> outputs = inputs.stream()
            .collect(
                    Collectors.groupingBy(
                            s -> s.startsWith("D") ? "desperate" : "serious",
                            Collectors.mapping(
                                    s -> Integer.valueOf(s.substring(1)),
                                    Collectors.toList())));

    System.out.println(outputs);
}

这会产生:

[D8, S1, S5, D2, D15, S9]
{desperate=[8, 2, 15], serious=[1, 5, 9]}

【讨论】:

  • 由于这是一个布尔分组,对于较大的列表,使用Map&lt;String, List&lt;Integer&gt;&gt; outputs = inputs.stream() .collect(Collectors.collectingAndThen(Collectors.partitioningBy(s -&gt; s.startsWith("D"), Collectors.mapping(s -&gt; Integer.valueOf(s.substring(1)), Collectors.toList())), m -&gt; Map.of("desperate", m.get(true), "serious", m.get(false)))); 可能更有效。此外,从 Java 9 开始,我们可以在不创建字符串的情况下解析子字符串,即Integer.parseInt(s, 1, s.length(), 10)。但是对于简单的情况,例如问题的例子,这并不重要。
【解决方案2】:

这类似于Erik's answer。不同之处在于,转变可以发生在分组之外,并且可以独立发展。

流、地图、组

  1. 流式传输元素
  2. 使用map 转换每个元素(这可以委托给单独的函数)
  3. 定义收集策略
  4. 基于收集策略的分组
  5. 根据需要收集元素
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

class TransformAndGroupBy {

  public static void main(String[] args) {
    List<String> inputs = Arrays.asList("D8", "S1", "S5", "D2", "D15", "S9");

    System.out.println(inputs);

    Function<String, SimpleEntry<String, Integer>> transform = s -> s.startsWith("D")
      ? new SimpleEntry<>("desparate", Integer.parseInt(s.substring(1)))
      : new SimpleEntry<>("serious", Integer.parseInt(s.substring(1)));

    Map<String, List<Integer>> outputs = inputs.stream() // convert to stream
      .map(transform) // do the necessary transformation
      .collect(Collectors.groupingBy(e -> e.getKey(), Collectors.mapping(e -> e.getValue(), Collectors.toList()))); // group and collect as per the transformation
    System.out.println(outputs);
  }
}

【讨论】:

  • 我明白了,我的transform 可以在其他地方重复使用和定义,甚至是一个参数(?)。正如我在“实际用例”中提到的那样,我将对不同的字符串集(不同的模式)进行不同的转换。
  • @StephenP,你是绝对正确的。您可以定义为参数或外部函数。 map(transform) 类型的输出应该对collect 可用(已知)以更改收集策略(这反过来也可以更改结果类型)。主要优点是map/tranform 是一个独立的操作,因此可以代替数据来完成,而group 可以根据从转换中获得的组键创建随机播放。但是,如果函数是在组内定义的,那么要么所有数据都必须在单个节点中,要么需要多级 shuffle 和 reduce。
  • 基本上,您的转换是输入类型和输出类型中的Functiongroupby 将作用于此输出类型。对于一个简单的程序,这可能是多余的,但在大型管道中,这个拱门可以提供帮助。
  • 当然不需要重复代码。你也可以使用Function&lt;String, SimpleEntry&lt;String, Integer&gt;&gt; transform = s -&gt; new SimpleEntry&lt;&gt;(s.startsWith("D")? "desperate": "serious", Integer.parseInt(s.substring(1)));
猜你喜欢
  • 1970-01-01
  • 2016-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-28
  • 2023-03-17
  • 2013-01-29
  • 2019-11-24
相关资源
最近更新 更多