按对象 属性 将地图分组到新地图
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 的正确用法,用于此“在其他地图中包含列表的地图”。我遇到过其他简单 maps/lists 的案例,但事实证明这个案例对我来说更棘手。
尝试次数
这是“尝试”的初始代码:
mappedCars = allCarsAndBrands.entrySet()
.stream()
.collect(
groupingBy(Car::getType,
groupingBy(Map.Entry::getKey)
)
);
我也收到“无法引用非静态错误”(Map.Entry::getKey),但这是因为我未能匹配实际预期 return ()
此时我只是感到困惑,也尝试使用 Collectors.toMap
但仍然无法获得工作分组。
额外内容
以下是此示例的 class 定义:
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,
}
编辑:“脏”解决方案
这是一个“hackish”的解决方案(基于评论建议,将检查答案!):
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()
)
));
如评论中所述(之前应该在此处添加),有一个“业务约束”为解决方案增加了一些限制。我也不想创建一个新的 CarBrand
,因为在现实世界中,这并不像上面看到的那么简单……但同样,使用原始地图和过滤 + 查找是很糟糕的。
正如评论中所讨论的那样,如果将 Make
作为 Car
class.
的一个字段,这很容易做到
根据您最后的评论,最简单的方法是使用 Stream
和 Java 8 中引入的 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}]
注意:{}
之间的值是 toString
覆盖 Car
class。
通过使用现有模型和嵌套分组的初始方法,您的思考方向是正确的。可以在迭代条目时考虑展平 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()))));
首先,让我添加实际的“示例代码”:
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 的正确用法,用于此“在其他地图中包含列表的地图”。我遇到过其他简单 maps/lists 的案例,但事实证明这个案例对我来说更棘手。
尝试次数
这是“尝试”的初始代码:
mappedCars = allCarsAndBrands.entrySet()
.stream()
.collect(
groupingBy(Car::getType,
groupingBy(Map.Entry::getKey)
)
);
我也收到“无法引用非静态错误”(Map.Entry::getKey),但这是因为我未能匹配实际预期 return (
此时我只是感到困惑,也尝试使用 Collectors.toMap
但仍然无法获得工作分组。
额外内容
以下是此示例的 class 定义:
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,
}
编辑:“脏”解决方案
这是一个“hackish”的解决方案(基于评论建议,将检查答案!):
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()
)
));
如评论中所述(之前应该在此处添加),有一个“业务约束”为解决方案增加了一些限制。我也不想创建一个新的 CarBrand
,因为在现实世界中,这并不像上面看到的那么简单……但同样,使用原始地图和过滤 + 查找是很糟糕的。
正如评论中所讨论的那样,如果将 Make
作为 Car
class.
根据您最后的评论,最简单的方法是使用 Stream
和 Java 8 中引入的 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}]
注意:{}
之间的值是 toString
覆盖 Car
class。
通过使用现有模型和嵌套分组的初始方法,您的思考方向是正确的。可以在迭代条目时考虑展平 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()))));