英文:
Grouping a map by object's property to a new Map
问题
首先,让我添加实际的“示例代码”:
Map<CarBrand, List<Car>> allCarsAndBrands = new HashMap();
final String bmwBrandName = "BMW";
final String audiBrandName = "AUDI";
List<Car> bmwCars = new ArrayList();
bmwCars.add(new Car(CarType.FAST, "Z4", "silver", bmwBrandName));
bmwCars.add(new Car(CarType.FAST, "M3", "red", bmwBrandName));
bmwCars.add(new Car(CarType.SLOW, "X1", "black", bmwBrandName));
List<Car> audiCars = new ArrayList();
audiCars.add(new Car(CarType.FAST, "S3", "yellow", audiBrandName));
audiCars.add(new Car(CarType.FAST, "R8", "silver", audiBrandName));
audiCars.add(new Car(CarType.SLOW, "A1", "white", audiBrandName));
allCarsAndBrands.put(new CarBrand(bmwBrandName), bmwCars);
allCarsAndBrands.put(new CarBrand(audiBrandName), audiCars);
Map<CarType, Map<CarBrand, List<Car>>> mappedCars;
问题
我的目标是通过“CarType”填充mappedCars
,这将导致两个大集合:一个包含所有FAST汽车,另一个包含所有SLOW汽车(或任何未来的“类型”,每个类型都具有先前的带有CarBrand
和相关汽车的地图结构)。
我目前无法找到在这个“地图中嵌套其他地图”中使用Collections/Streams的正确方法。我在其他简单的地图/列表情况下已经尝试过,但这个案例对我来说更加棘手。
尝试
这是一个初始的代码“尝试”:
mappedCars = allCarsAndBrands.entrySet()
.stream()
.collect(
groupingBy(Car::getType,
groupingBy(entry -> entry.getKey().getType())
)
);
我还遇到了“非静态无法引用非静态”错误(Map.Entry::getKey),但这是因为我未能匹配实际的预期返回(https://stackoverflow.com/questions/40172551/static-context-cannot-access-non-static-in-collectors)
我现在感到困惑,尝试使用Collectors.toMap
,但仍然无法获得有效的分组。
附加信息
以下是此示例的类定义:
class CarBrand {
CarBrand(String name) {
this.name = name;
}
String name;
}
class Car {
Car(CarType type, String name, String color, String brandName) {
this.type = type;
this.name = name;
this.color = color;
this.brandName = brandName;
}
public CarType getType() {
return type;
}
CarType type;
String name;
String color;
String brandName;
}
enum CarType {
FAST,
SLOW,
}
编辑:“脏”解决方案
这是一个“临时”的解决方案(基于评论的建议,将查看答案!):
Map<CarType, Map<CarBrand, List<Car>>> mappedCars = allCarsAndBrands
.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.groupingBy(
Car::getType,
Collectors.groupingBy(
car -> allCarsAndBrands.keySet().stream().filter(brand -> brand.name.equals(car.brandName)).findFirst().get(),
Collectors.toList()
)
));
正如评论中提到的(在此之前应该已经添加了),这里存在一个“业务限制”为解决方案添加了一些限制。在现实世界中,我也不想创建一个新的CarBrand
,因为这并不像在这个示例中看起来那么简单... 但是再次强调,使用原始地图并进行过滤+查找是不好的解决方案。
英文:
First things first, let me add the actual "example code":
Map<CarBrand, List<Car>> allCarsAndBrands = new HashMap();
final String bmwBrandName = "BMW";
final String audiBrandName = "AUDI";
List<Car> bmwCars = new ArrayList();
bmwCars.add(new Car(CarType.FAST, "Z4", "silver", bmwBrandName));
bmwCars.add(new Car(CarType.FAST, "M3", "red", bmwBrandName));
bmwCars.add(new Car(CarType.SLOW, "X1", "black", bmwBrandName));
List<Car> audiCars = new ArrayList();
audiCars.add(new Car(CarType.FAST, "S3", "yellow", audiBrandName));
audiCars.add(new Car(CarType.FAST, "R8", "silver", audiBrandName));
audiCars.add(new Car(CarType.SLOW, "A1", "white", audiBrandName));
allCarsAndBrands.put(new CarBrand(bmwBrandName), bmwCars);
allCarsAndBrands.put(new CarBrand(audiBrandName), audiCars);
Map<CarType, Map<CarBrand, List<Car>>> mappedCars;
Problem
My goal on this is to populate mappedCars
by CarType
, which would result in two big sets: one containing all FAST cars and the other all SLOW cars (or any future "types", each one having the previous map structure with CarBrand
and the related cars).
I'm currently failing to find the proper use of Collections/Streams for this "map with lists inside other map". I've had other cases with simple maps/lists but this one is proving to be trickier for me.
Attempts
Here's an initial code "attempt":
mappedCars = allCarsAndBrands.entrySet()
.stream()
.collect(
groupingBy(Car::getType,
groupingBy(Map.Entry::getKey)
)
);
I'm also getting the "non-static cannot be referenced error" (Map.Entry::getKey) but this is due the fact that I'm failing to match the actual expected return (https://stackoverflow.com/questions/40172551/static-context-cannot-access-non-static-in-collectors)
I'm simply confused at this point, tried using Collectors.toMap
too but still can't get a working grouping.
Extras
Here are the class definitions for this example:
class CarBrand {
CarBrand(String name) {
this.name = name;
}
String name;
}
class Car {
Car(CarType type, String name, String color, String brandName) {
this.type = type;
this.name = name;
this.color = color;
this.brandName = brandName;
}
public CarType getType() {
return type;
}
CarType type;
String name;
String color;
String brandName;
}
enum CarType {
FAST,
SLOW,
}
EDIT: "DIRTY" SOLUTION
Here's a "hackish" solution (based on the comments suggestions, will check the answers!):
Map<CarType, Map<CarBrand, List<Car>>> mappedCars = allCarsAndBrands
.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.groupingBy(
Car::getType,
Collectors.groupingBy(
car -> allCarsAndBrands.keySet().stream().filter(brand -> brand.name == car.brandName).findFirst().get(),
Collectors.toList()
)
));
As mentioned in the comments (should've added here before), there's a "business constraint" that adds some limitations for the solution. I also didn't feel like creating a new CarBrand
since in the real world that's not that simple as seen on this... but again, using the original map and filtering + find is just bad.
答案1
得分: 1
如评论中所讨论的那样,如果将 Make
包含为 Car
类的一个字段,那么这个操作就很简单。
根据您最后的评论,最简单的方法是使用 Java 8 中引入的 Stream
和 Map
接口的其他特性来实现混合解决方案。
数据
Map<CarBrand, List<Car>> allCarsAndBrands = new HashMap<>();
final String bmwBrandName = "BMW";
final String audiBrandName = "AUDI";
List<Car> bmwCars = new ArrayList<>();
bmwCars.add(new Car(CarType.FAST, "Z4", "silver"));
bmwCars.add(new Car(CarType.FAST, "M3", "red"));
bmwCars.add(new Car(CarType.SLOW, "X1", "black"));
List<Car> audiCars = new ArrayList<>();
audiCars.add(new Car(CarType.FAST, "S3", "yellow"));
audiCars.add(new Car(CarType.FAST, "R8", "silver"));
audiCars.add(new Car(CarType.SLOW, "A1", "white"));
allCarsAndBrands.put(new CarBrand(bmwBrandName), bmwCars);
allCarsAndBrands.put(new CarBrand(audiBrandName), audiCars);
过程
这个操作通过为每个 CarBrand
创建一个 Map<CarType, List<Car>>
,然后颠倒键值对。唯一可能不熟悉的新特性是 computeIfAbsent。
Map<CarType, Map<CarBrand, List<Car>>> map = new HashMap<>();
allCarsAndBrands.forEach((brand, carList) -> {
Map<CarType, List<Car>> typeMap = carList.stream()
.collect(Collectors.groupingBy(Car::getType));
typeMap.forEach((type, lst) -> {
map.computeIfAbsent(type, value->
new HashMap<CarBrand, List<Car>>())
.computeIfAbsent(brand, value->new ArrayList<>())
.addAll(lst);
}
);
});
打印结果
map.forEach((carType, brandMap) -> {
System.out.println(carType);
brandMap.forEach((brand, carList) -> {
System.out.println(" " + brand + " -> " + carList);
});
});
输出
FAST
AUDI -> [{FAST, S3, yellow}, {FAST, R8, silver}]
BMW -> [{FAST, Z4, silver}, {FAST, M3, red}]
SLOW
AUDI -> [{SLOW, A1, white}]
BMW -> [{SLOW, X1, black}]
注意:{}
中的值是 Car
类的 toString
重写结果。
英文:
As was discussed in the comments, this was simple to do if one includes the Make
as a field of the Car
class.
Based on your last comment, the easiest way was to use a hybrid solution using Stream
and other features of the Map
interface introduced in Java 8.
Data
Map<CarBrand, List<Car>> allCarsAndBrands = new HashMap<>();
final String bmwBrandName = "BMW";
final String audiBrandName = "AUDI";
List<Car> bmwCars = new ArrayList<>();
bmwCars.add(new Car(CarType.FAST, "Z4", "silver"));
bmwCars.add(new Car(CarType.FAST, "M3", "red"));
bmwCars.add(new Car(CarType.SLOW, "X1", "black"));
List<Car> audiCars = new ArrayList<>();
audiCars.add(new Car(CarType.FAST, "S3", "yellow"));
audiCars.add(new Car(CarType.FAST, "R8", "silver"));
audiCars.add(new Car(CarType.SLOW, "A1", "white"));
allCarsAndBrands.put(new CarBrand(bmwBrandName), bmwCars);
allCarsAndBrands.put(new CarBrand(audiBrandName), audiCars);
Process
This works by creating a Map<CarType, List<Car>>
for each CarBrand
and then reversing the keys. The only new feature you may be unfamiliar with is computeIfAbsent
Map<CarType, Map<CarBrand, List<Car>>> map = new HashMap<>();
allCarsAndBrands.forEach((brand, carList) -> {
Map<CarType, List<Car>> typeMap = carList.stream()
.collect(Collectors.groupingBy(Car::getType));
typeMap.forEach((type, lst) -> {
map.computeIfAbsent(type, value->
new HashMap<CarBrand, List<Car>>())
.computeIfAbsent(brand, value->new ArrayList<>())
.addAll(lst);
}
);
});
Print the results
map.forEach((carType, brandMap) -> {
System.out.println(carType);
brandMap.forEach((brand, carList) -> {
System.out.println(" " + brand + " -> " + carList);
});
});
Prints
FAST
AUDI -> [{FAST, S3, yellow}, {FAST, R8, silver}]
BMW -> [{FAST, Z4, silver}, {FAST, M3, red}]
SLOW
AUDI -> [{SLOW, A1, white}]
BMW -> [{SLOW, X1, black}]
Note: the values between {}
are the toString
override of the Car
class.
答案2
得分: 1
使用现有模型,并且最初的嵌套分组方法是正确的思路。改进可以在迭代条目时考虑对“Map”的值部分进行展平。
allCarsAndBrands.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(car -> new AbstractMap.SimpleEntry<>(e.getKey(), car)))
一旦你完成了这一步,分组的概念基本上是相同的,但现在默认返回的分组值将是条目类型。因此,进一步需要一个“mapping”。这将使得整体解决方案如下:
Map<CarType, Map<CarBrand, List<Car>>> mappedCars =
allCarsAndBrands.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(car -> new AbstractMap.SimpleEntry<>(e.getKey(), car)))
.collect(Collectors.groupingBy(e -> e.getValue().getType(),
Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList()))));
英文:
With the use of existing models, and the initial approach of nested grouping you were thinking in the right direction. The improvement could be made in thinking about flattening the value part of the Map
while iterating over the entries.
allCarsAndBrands.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(car -> new AbstractMap.SimpleEntry<>(e.getKey(), car)))
Once you have that, the grouping concept works pretty much the same, but now the default returned grouped values would instead be of the entry type. Hence a mapping
is further required. This leaves the overall solution to be something like :
Map<CarType, Map<CarBrand, List<Car>>> mappedCars =
allCarsAndBrands.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(car -> new AbstractMap.SimpleEntry<>(e.getKey(), car)))
.collect(Collectors.groupingBy(e -> e.getValue().getType(),
Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList()))));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论