Java 流 |按相同元素分组
Java Streams | groupingBy same elements
我有一个单词流,我想根据相同元素(=单词)的出现对它们进行排序。
例如:{你好,世界,你好}
到
Map<String, List<String>>
你好,{你好,你好}
世界,{世界}
我目前拥有的:
Map<Object, List<String>> list = streamofWords.collect(Collectors.groupingBy(???));
问题一:stream好像丢失了他正在处理Strings的信息,所以编译器强制我改类型为Object,List
问题 2:我不知道在括号内放什么来按同一事件对它进行分组。我知道我能够处理 lambda 表达式中的单个元素,但我不知道如何到达 "outside" 每个元素以检查是否相等。
谢谢
您要查找的KeyExtractor是恒等函数:
Map<String, List<String>> list = streamofWords.collect(Collectors.groupingBy(Function.identity()));
编辑添加说明:
Function.identity()
使用一种方法返回 'Function',该方法仅返回其获取的参数。
Collectors.groupingBy(Function<S, K> keyExtractor)
提供了一个收集器,它将流中的所有元素收集到一个Map<K, List<S>>
。它使用它获得的 keyExtractor 实现来检查流的 S
类型的对象并从中推断出 K
类型的键。此键是地图的键,用于获取(或创建)流元素添加到的结果地图中的列表。
要获得 Map<String, List<String>>
,您只需告诉 groupingBy
收集器您要按标识对值进行分组,因此函数 x -> x
.
Map<String, List<String>> occurrences =
streamOfWords.collect(groupingBy(str -> str));
然而这有点没用,因为你看到你有两次相同类型的信息。您应该查看 Map<String, Long>
,其中的值表示字符串在流中的出现次数。
Map<String, Long> occurrences =
streamOfWords.collect(groupingBy(str -> str, counting()));
基本上,不是让 return 的 groupingBy
值为 List
,而是使用下游收集器 counting()
来告诉您要计算次数出现此值。
您的排序要求应该意味着您应该有一个 Map<Long, List<String>>
(如果不同的字符串出现相同次数怎么办?),并且作为默认的 toMap
收集器 returns HashMap
,它没有排序的概念,但您可以将元素存储在 TreeMap
中。
我试图总结一下我在评论中所说的内容。
您似乎对 str -> str
如何判断 "hello" 或 "world" 是否不同感到困惑。
首先str -> str
是一个函数,即对于输入x产生一个值f(x)。例如,f(x) = x + 2
是一个函数,对于任何值 x
returns x + 2
.
这里我们使用恒等函数,即f(x) = x
。当你从Map
中的管道中收集元素时,这个函数将被调用,从值中获取键。因此,在您的示例中,您有 3 个元素,身份函数为其生成:
f("hello") = "hello"
f("world") = "world"
到目前为止一切顺利。
现在,当 collect()
被调用时,对于流中的每个值,您将对其应用函数并评估结果(这将是 Map
中的键)。如果键已经存在,我们将获取当前映射的值,并将我们想要放置的值(即您刚刚应用函数的值)与之前的映射值合并到 List
中。这就是为什么你在最后得到 Map<String, List<String>>
。
再举个例子。现在流包含值 "hello"、"world" 和 "hey",我们要应用于对元素进行分组的函数是 str -> str.substring(0, 2)
,即采用字符串的前两个字符。
同样,我们有:
f("hello") = "he"
f("world") = "wo"
f("hey") = "he"
在这里你看到 "hello" 和 "hey" 在应用函数时产生相同的键,因此在收集它们时它们将被分组在相同的 List
中,因此最终结果是:
"he" -> ["hello", "hey"]
"wo" -> ["world"]
要与数学进行类比,您可以采用任何非双射函数,例如 x2。对于 x = -2
和 x = 2
我们有 f(x) = 4
。所以如果我们用这个函数对整数进行分组,-2 和 2 就会在同一个 "bag".
一开始查看源代码并不能帮助您理解发生了什么。如果您想知道它是如何在引擎盖下实现的,它会很有用。但是首先尝试用更高的抽象层次来思考这个概念,然后事情可能会变得更清楚。
希望对您有所帮助! :)
如果你想按对象的某些字段而不是整个对象分组,并且你不想更改你的 equals 和 hashCode 方法,我会创建一个 class 保存一组键分组目的:
import java.util.Arrays;
@Getter
public class MultiKey {
public MultiKey(Object... keys) {
this.keys = keys;
}
private Object[] keys;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MultiKey multiKey = (MultiKey) o;
return Arrays.equals(keys, multiKey.keys);
}
@Override
public int hashCode() {
return Arrays.hashCode(keys);
}
}
以及 groupingBy
本身:
Map<MultiKey, List<VhfEventView>> groupedList = list
.stream()
.collect(Collectors.groupingBy(
e -> new MultiKey(e.getGroupingKey1(), e.getGroupingKey2())));
我有一个单词流,我想根据相同元素(=单词)的出现对它们进行排序。
例如:{你好,世界,你好}
到
Map<String, List<String>>
你好,{你好,你好}
世界,{世界}
我目前拥有的:
Map<Object, List<String>> list = streamofWords.collect(Collectors.groupingBy(???));
问题一:stream好像丢失了他正在处理Strings的信息,所以编译器强制我改类型为Object,List
问题 2:我不知道在括号内放什么来按同一事件对它进行分组。我知道我能够处理 lambda 表达式中的单个元素,但我不知道如何到达 "outside" 每个元素以检查是否相等。
谢谢
您要查找的KeyExtractor是恒等函数:
Map<String, List<String>> list = streamofWords.collect(Collectors.groupingBy(Function.identity()));
编辑添加说明:
Function.identity()
使用一种方法返回 'Function',该方法仅返回其获取的参数。Collectors.groupingBy(Function<S, K> keyExtractor)
提供了一个收集器,它将流中的所有元素收集到一个Map<K, List<S>>
。它使用它获得的 keyExtractor 实现来检查流的S
类型的对象并从中推断出K
类型的键。此键是地图的键,用于获取(或创建)流元素添加到的结果地图中的列表。
要获得 Map<String, List<String>>
,您只需告诉 groupingBy
收集器您要按标识对值进行分组,因此函数 x -> x
.
Map<String, List<String>> occurrences =
streamOfWords.collect(groupingBy(str -> str));
然而这有点没用,因为你看到你有两次相同类型的信息。您应该查看 Map<String, Long>
,其中的值表示字符串在流中的出现次数。
Map<String, Long> occurrences =
streamOfWords.collect(groupingBy(str -> str, counting()));
基本上,不是让 return 的 groupingBy
值为 List
,而是使用下游收集器 counting()
来告诉您要计算次数出现此值。
您的排序要求应该意味着您应该有一个 Map<Long, List<String>>
(如果不同的字符串出现相同次数怎么办?),并且作为默认的 toMap
收集器 returns HashMap
,它没有排序的概念,但您可以将元素存储在 TreeMap
中。
我试图总结一下我在评论中所说的内容。
您似乎对 str -> str
如何判断 "hello" 或 "world" 是否不同感到困惑。
首先str -> str
是一个函数,即对于输入x产生一个值f(x)。例如,f(x) = x + 2
是一个函数,对于任何值 x
returns x + 2
.
这里我们使用恒等函数,即f(x) = x
。当你从Map
中的管道中收集元素时,这个函数将被调用,从值中获取键。因此,在您的示例中,您有 3 个元素,身份函数为其生成:
f("hello") = "hello"
f("world") = "world"
到目前为止一切顺利。
现在,当 collect()
被调用时,对于流中的每个值,您将对其应用函数并评估结果(这将是 Map
中的键)。如果键已经存在,我们将获取当前映射的值,并将我们想要放置的值(即您刚刚应用函数的值)与之前的映射值合并到 List
中。这就是为什么你在最后得到 Map<String, List<String>>
。
再举个例子。现在流包含值 "hello"、"world" 和 "hey",我们要应用于对元素进行分组的函数是 str -> str.substring(0, 2)
,即采用字符串的前两个字符。
同样,我们有:
f("hello") = "he"
f("world") = "wo"
f("hey") = "he"
在这里你看到 "hello" 和 "hey" 在应用函数时产生相同的键,因此在收集它们时它们将被分组在相同的 List
中,因此最终结果是:
"he" -> ["hello", "hey"]
"wo" -> ["world"]
要与数学进行类比,您可以采用任何非双射函数,例如 x2。对于 x = -2
和 x = 2
我们有 f(x) = 4
。所以如果我们用这个函数对整数进行分组,-2 和 2 就会在同一个 "bag".
一开始查看源代码并不能帮助您理解发生了什么。如果您想知道它是如何在引擎盖下实现的,它会很有用。但是首先尝试用更高的抽象层次来思考这个概念,然后事情可能会变得更清楚。
希望对您有所帮助! :)
如果你想按对象的某些字段而不是整个对象分组,并且你不想更改你的 equals 和 hashCode 方法,我会创建一个 class 保存一组键分组目的:
import java.util.Arrays;
@Getter
public class MultiKey {
public MultiKey(Object... keys) {
this.keys = keys;
}
private Object[] keys;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MultiKey multiKey = (MultiKey) o;
return Arrays.equals(keys, multiKey.keys);
}
@Override
public int hashCode() {
return Arrays.hashCode(keys);
}
}
以及 groupingBy
本身:
Map<MultiKey, List<VhfEventView>> groupedList = list
.stream()
.collect(Collectors.groupingBy(
e -> new MultiKey(e.getGroupingKey1(), e.getGroupingKey2())));