Java 在单个循环中以声明方式分别按多个字段进行流分组
Java Stream Grouping by multiple fields individually in declarative way in single loop
我用谷歌搜索了它,但我发现大多数情况是按聚合字段分组或改变流的响应,但不是下面的情况:
我有一个 class User
字段 category
和 marketingChannel
.
我必须以声明式的方式编写一个方法,该方法接受用户列表并根据
category
并且也分别基于 marketingChannel
(即不是 groupingBy(... ,groupingBy(..))
)。
我无法在一个循环中完成。这就是我必须要达到的目标。
我编写了几个方法如下:
import java.util.*;
import java.util.stream.*;
public class Main
{
public static void main(String[] args) {
List<User> users = User.createDemoList();
imperative(users);
declerativeMultipleLoop(users);
declerativeMultipleColumn(users);
}
public static void imperative(List<User> users){
Map<String, Integer> categoryMap = new HashMap<>();
Map<String, Integer> channelMap = new HashMap<>();
for(User user : users){
Integer value = categoryMap.getOrDefault(user.getCategory(), 0);
categoryMap.put(user.getCategory(), value+1);
value = channelMap.getOrDefault(user.getMarketingChannel(), 0);
channelMap.put(user.getMarketingChannel(), value+1);
}
System.out.println("imperative");
System.out.println(categoryMap);
System.out.println(channelMap);
}
public static void declerativeMultipleLoop(List<User> users){
Map<String, Long> categoryMap = users.stream()
.collect(Collectors.groupingBy(
User::getCategory, Collectors.counting()));
Map<String, Long> channelMap = users.stream()
.collect(Collectors.groupingBy(
User::getMarketingChannel, Collectors.counting()));
System.out.println("declerativeMultipleLoop");
System.out.println(categoryMap);
System.out.println(channelMap);
}
public static void declerativeMultipleColumn(List<User> users){
Map<String, Map<String, Long>> map = users.stream()
.collect(Collectors.groupingBy(
User::getCategory,
Collectors.groupingBy(User::getMarketingChannel,
Collectors.counting())));
System.out.println("declerativeMultipleColumn");
System.out.println("groupingBy category and marketChannel");
System.out.println(map);
Map<String, Long> categoryMap = new HashMap<>();
Map<String, Long> channelMap = new HashMap<>();
for (Map.Entry<String, Map<String, Long>> entry: map.entrySet()) {
String category = entry.getKey();
Integer count = entry.getValue().size();
Long value = categoryMap.getOrDefault(category,0L);
categoryMap.put(category, value+count);
for (Map.Entry<String, Long> channelEntry : entry.getValue().entrySet()){
String channel = channelEntry.getKey();
Long channelCount = channelEntry.getValue();
Long channelValue = channelMap.getOrDefault(channel,0L);
channelMap.put(channel, channelValue+channelCount);
}
}
System.out.println("After Implerative Loop on above.");
System.out.println(categoryMap);
System.out.println(channelMap);
}
}
class User{
private String name;
private String category;
private String marketChannel;
public User(String name, String category, String marketChannel){
this.name = name;
this.category = category;
this.marketChannel = marketChannel;
}
public String getName(){
return this.name;
}
public String getCategory(){
return this.category;
}
public String getMarketingChannel(){
return this.marketChannel;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) &&
Objects.equals(category, user.category) &&
Objects.equals(marketChannel, user.marketChannel);
}
@Override
public int hashCode() {
return Objects.hash(name, category, marketChannel);
}
public static List<User> createDemoList(){
return Arrays.asList(
new User("a", "student","google"),
new User("b", "student","bing"),
new User("c", "business","google"),
new User("d", "business", "direct")
);
}
The method declerativeMultipleLoop
is declarative but it has a separate loop for each field. Complexity : O(noOfFields * No of users)
The problem is in declerativeMultipleColumn
Method as I end up writing imperative code and multiple loops.
I want to write the above method in completely declarative and as efficient as possible. i.e Complexity : O(No of users)
示例输出:
imperative
{business=2, student=2}
{direct=1, google=2, bing=1}
declerativeMultipleLoop
{business=2, student=2}
{direct=1, google=2, bing=1}
declerativeMultipleColumn
groupingBy category and marketChannel
{business={direct=1, google=1}, student={google=1, bing=1}}
After Implerative Loop on above.
{business=2, student=2}
{direct=1, google=2, bing=1}
如果我理解您的要求,那就是使用产生 2 个单独地图的单个流操作。这将需要一个结构来保存地图和一个收集器来构建结构。类似于以下内容:
class Counts {
public final Map<String, Integer> categoryCounts = new HashMap<>();
public final Map<String, Integer> channelCounts = new HashMap<>();
public static Collector<User,Counts,Counts> countsCollector() {
return Collector.of(Counts::new, Counts::accept, Counts::combine, CONCURRENT, UNORDERED);
}
private Counts() { }
private void accept(User user) {
categoryCounts.merge(user.getCategory(), 1, Integer::sum);
channelCounts.merge(user.getChannel(), 1, Integer::sum);
}
private Counts combine(Counts other) {
other.categoryCounts.forEach((c, v) -> categoryCounts.merge(c, v, Integer::sum));
other.channelCounts.forEach((c, v) -> channelCounts.merge(c, v, Integer::sum));
return this;
}
}
然后可以用作收集器:
Counts counts = users.stream().collect(Counts.countsCollector());
counts.categoryCounts.get("student")...
(仅供参考:在这种情况下,命令式和声明式之间的区别非常随意。对我来说,定义流操作感觉很程序化(与 Haskell 中的等价物相反)。
您可以compute
two maps in a single forEach
方法:
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("a", "student", "google"),
new User("b", "student", "bing"),
new User("c", "business", "google"),
new User("d", "business", "direct"));
Map<String, Integer> categoryMap = new HashMap<>();
Map<String, Integer> channelMap = new HashMap<>();
// group users into maps
users.forEach(user -> {
categoryMap.compute(user.getCategory(),
(key, value) -> value == null ? 1 : value + 1);
channelMap.compute(user.getChannel(),
(key, value) -> value == null ? 1 : value + 1);
});
// output
System.out.println(categoryMap); // {business=2, student=2}
System.out.println(channelMap); // {direct=1, google=2, bing=1}
}
static class User {
private final String name, category, channel;
public User(String name, String category, String channel) {
this.name = name;
this.category = category;
this.channel = channel;
}
public String getName() { return this.name; }
public String getCategory() { return this.category; }
public String getChannel() { return this.channel; }
}
我用谷歌搜索了它,但我发现大多数情况是按聚合字段分组或改变流的响应,但不是下面的情况:
我有一个 class User
字段 category
和 marketingChannel
.
我必须以声明式的方式编写一个方法,该方法接受用户列表并根据
category
并且也分别基于 marketingChannel
(即不是 groupingBy(... ,groupingBy(..))
)。
我无法在一个循环中完成。这就是我必须要达到的目标。
我编写了几个方法如下:
import java.util.*;
import java.util.stream.*;
public class Main
{
public static void main(String[] args) {
List<User> users = User.createDemoList();
imperative(users);
declerativeMultipleLoop(users);
declerativeMultipleColumn(users);
}
public static void imperative(List<User> users){
Map<String, Integer> categoryMap = new HashMap<>();
Map<String, Integer> channelMap = new HashMap<>();
for(User user : users){
Integer value = categoryMap.getOrDefault(user.getCategory(), 0);
categoryMap.put(user.getCategory(), value+1);
value = channelMap.getOrDefault(user.getMarketingChannel(), 0);
channelMap.put(user.getMarketingChannel(), value+1);
}
System.out.println("imperative");
System.out.println(categoryMap);
System.out.println(channelMap);
}
public static void declerativeMultipleLoop(List<User> users){
Map<String, Long> categoryMap = users.stream()
.collect(Collectors.groupingBy(
User::getCategory, Collectors.counting()));
Map<String, Long> channelMap = users.stream()
.collect(Collectors.groupingBy(
User::getMarketingChannel, Collectors.counting()));
System.out.println("declerativeMultipleLoop");
System.out.println(categoryMap);
System.out.println(channelMap);
}
public static void declerativeMultipleColumn(List<User> users){
Map<String, Map<String, Long>> map = users.stream()
.collect(Collectors.groupingBy(
User::getCategory,
Collectors.groupingBy(User::getMarketingChannel,
Collectors.counting())));
System.out.println("declerativeMultipleColumn");
System.out.println("groupingBy category and marketChannel");
System.out.println(map);
Map<String, Long> categoryMap = new HashMap<>();
Map<String, Long> channelMap = new HashMap<>();
for (Map.Entry<String, Map<String, Long>> entry: map.entrySet()) {
String category = entry.getKey();
Integer count = entry.getValue().size();
Long value = categoryMap.getOrDefault(category,0L);
categoryMap.put(category, value+count);
for (Map.Entry<String, Long> channelEntry : entry.getValue().entrySet()){
String channel = channelEntry.getKey();
Long channelCount = channelEntry.getValue();
Long channelValue = channelMap.getOrDefault(channel,0L);
channelMap.put(channel, channelValue+channelCount);
}
}
System.out.println("After Implerative Loop on above.");
System.out.println(categoryMap);
System.out.println(channelMap);
}
}
class User{
private String name;
private String category;
private String marketChannel;
public User(String name, String category, String marketChannel){
this.name = name;
this.category = category;
this.marketChannel = marketChannel;
}
public String getName(){
return this.name;
}
public String getCategory(){
return this.category;
}
public String getMarketingChannel(){
return this.marketChannel;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) &&
Objects.equals(category, user.category) &&
Objects.equals(marketChannel, user.marketChannel);
}
@Override
public int hashCode() {
return Objects.hash(name, category, marketChannel);
}
public static List<User> createDemoList(){
return Arrays.asList(
new User("a", "student","google"),
new User("b", "student","bing"),
new User("c", "business","google"),
new User("d", "business", "direct")
);
}
The method
declerativeMultipleLoop
is declarative but it has a separate loop for each field. Complexity : O(noOfFields * No of users)
The problem is in
declerativeMultipleColumn
Method as I end up writing imperative code and multiple loops.
I want to write the above method in completely declarative and as efficient as possible. i.e Complexity : O(No of users)
示例输出:
imperative
{business=2, student=2}
{direct=1, google=2, bing=1}
declerativeMultipleLoop
{business=2, student=2}
{direct=1, google=2, bing=1}
declerativeMultipleColumn
groupingBy category and marketChannel
{business={direct=1, google=1}, student={google=1, bing=1}}
After Implerative Loop on above.
{business=2, student=2}
{direct=1, google=2, bing=1}
如果我理解您的要求,那就是使用产生 2 个单独地图的单个流操作。这将需要一个结构来保存地图和一个收集器来构建结构。类似于以下内容:
class Counts {
public final Map<String, Integer> categoryCounts = new HashMap<>();
public final Map<String, Integer> channelCounts = new HashMap<>();
public static Collector<User,Counts,Counts> countsCollector() {
return Collector.of(Counts::new, Counts::accept, Counts::combine, CONCURRENT, UNORDERED);
}
private Counts() { }
private void accept(User user) {
categoryCounts.merge(user.getCategory(), 1, Integer::sum);
channelCounts.merge(user.getChannel(), 1, Integer::sum);
}
private Counts combine(Counts other) {
other.categoryCounts.forEach((c, v) -> categoryCounts.merge(c, v, Integer::sum));
other.channelCounts.forEach((c, v) -> channelCounts.merge(c, v, Integer::sum));
return this;
}
}
然后可以用作收集器:
Counts counts = users.stream().collect(Counts.countsCollector());
counts.categoryCounts.get("student")...
(仅供参考:在这种情况下,命令式和声明式之间的区别非常随意。对我来说,定义流操作感觉很程序化(与 Haskell 中的等价物相反)。
您可以compute
two maps in a single forEach
方法:
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("a", "student", "google"),
new User("b", "student", "bing"),
new User("c", "business", "google"),
new User("d", "business", "direct"));
Map<String, Integer> categoryMap = new HashMap<>();
Map<String, Integer> channelMap = new HashMap<>();
// group users into maps
users.forEach(user -> {
categoryMap.compute(user.getCategory(),
(key, value) -> value == null ? 1 : value + 1);
channelMap.compute(user.getChannel(),
(key, value) -> value == null ? 1 : value + 1);
});
// output
System.out.println(categoryMap); // {business=2, student=2}
System.out.println(channelMap); // {direct=1, google=2, bing=1}
}
static class User {
private final String name, category, channel;
public User(String name, String category, String channel) {
this.name = name;
this.category = category;
this.channel = channel;
}
public String getName() { return this.name; }
public String getCategory() { return this.category; }
public String getChannel() { return this.channel; }
}