基于另一个列表的 1 个列表的流过滤器
Posted
技术标签:
【中文标题】基于另一个列表的 1 个列表的流过滤器【英文标题】:Stream Filter of 1 list based on another list 【发布时间】:2021-04-02 07:06:33 【问题描述】:我在此论坛和谷歌搜索后发布了我的查询,但无法解决相同的问题。 例如:Link1Link2Link3
我正在尝试根据列表 1 中的值过滤列表 2(多列)。
List1:
- [Datsun]
- [Volvo]
- [BMW]
- [Mercedes]
List2:
- [1-Jun-1995, Audi, 25.3, 500.4, 300]
- [7-Apr-1996, BMW, 35.3, 250.2, 500]
- [3-May-1996, Porsche, 45.3, 750.8, 200]
- [2-Nov-1998, Volvo, 75.3, 150.2, 100]
- [7-Dec-1999, BMW, 95.3, 850.2, 900]
expected o/p:
- [7-Apr-1996, BMW, 35.3, 250.2, 500]
- [2-Nov-1998, Volvo, 75.3, 150.2, 100]
- [7-Dec-1999, BMW, 95.3, 850.2, 900]
代码
// List 1 in above eg
List<dataCarName> listCarName = new ArrayList<>();
// List 2 in above eg
List<dataCar> listCar = new ArrayList<>();
// Values to the 2 lists are populated from excel
List<dataCar> listOutput = listCar.stream().filter(e -> e.getName().contains("BMW")).collect(Collectors.toList());
在上面的代码中,如果我提供了一个可以过滤的特定值,但不确定如何检查列表 2 中的汽车名称是否存在于列表 1 中。
希望我面临的问题很清楚,等待指导(我对 Java 还比较陌生,因此如果上述查询非常基本,请原谅)。
编辑 我相信上面提供的链接 3 应该可以解决,但在我的情况下它不起作用。可能是因为 list-1 中的值填充为 org.gradle04.Main.Cars.dataCarName@4148db48 .. 等 只有当我通过调用 getName 方法对 List 1 执行 forEach 时,我才能以人类可读的格式获取值。
【问题讨论】:
您是在寻找完全匹配,还是汽车名称包含第二个列表中的其中一辆汽车的类型就足够了? (正如您使用contains
而不是 equals
所建议的那样)
.filter(e -> listCarName.contains(e.getName()))
?
您链接的第三个问题显示了如何实现您想要的:***.com/questions/26170264/…
@AlexisC。我遵循了该线程中的解决方案,但不适用于我。我猜这是因为列表 1 中的值存储为例如: org.gradle04.Main.Cars.dataCarName@4148db48 要进入人类可读的格式,我必须通过调用 getName 方法来执行 forEach。但我无法将其合并到过滤条件中。关于我应该如何进行的任何建议?如果我的查询非常基本,请提前致谢并道歉。
@iCoder 在这种情况下,您可以先将List<DataCarName>
转换为Set<String>
,然后在过滤器中使用contains
。
【参考方案1】:
不清楚为什么您首先使用的是 List<DataCarName>
而不是 List/Set<String>
。
您必须提供的谓词必须检查相应的数据车实例是否在列表中存在其名称。
e -> e.getName().contains("BMW")
只会检查数据车的名称是否包含您不想要的 BMW。那么你的第一次尝试可能是
e -> listCarName.contains(e.getName())
但是由于listCarName
是List<DataCarName>
和e.getName()
是一个字符串(我想),因此你会得到一个空列表。
您拥有的第一个选项是更改谓词,以便从数据车名称列表中获取一个流,将它们映射到它们的字符串表示形式,并检查这些名称中的任何一个是否与您当前的数据车实例的名称相对应当前过滤:
List<DataCar> listOutput =
listCar.stream()
.filter(e -> listCarName.stream().map(DataCarName::getName).anyMatch(name -> name.equals(e.getName())))
.collect(Collectors.toList());
现在这非常昂贵,因为您为数据汽车流管道中的每个实例创建一个流。更好的方法是先构建一个带有汽车名称的Set<String>
,然后简单地使用contains
作为该集合的谓词:
Set<String> carNames =
listCarName.stream()
.map(DataCarName::getName)
.collect(Collectors.toSet());
List<DataCar> listOutput =
listCar.stream()
.filter(e -> carNames.contains(e.getName()))
.collect(Collectors.toList());
【讨论】:
感谢您抽出宝贵时间并真诚地感谢您的指导。【参考方案2】:在您的 DataCar 类型中,getName()
是否返回 String 或 DataCarName 枚举类型?如果是枚举,您可能会遵循 Alexis C 的方法,但不是使用 Collectors.toSet() 构建一个 HashSet,而是构建一个 EnumSet,它提供 O(1) 性能。修改 Alexis 的建议,结果如下:
Set<DataCarName> carNames =
listCarName.stream()
.collect(Collectors.toCollection(
()-> EnumSet.noneOf(DataCarName.class)));
List<DataCar> listOutput =
listCar.stream()
.filter(car -> carNames.contains(car.getName()))
.collect(Collectors.toList());
【讨论】:
感谢您的意见,我会检查您的建议是否对我的特定需求有所帮助。【参考方案3】:@Alexis'a 的答案很好,但我有另一种方法可以利用Map
的性能并改进你为每个项目所做的listCarName.stream().map(DataCarName::getName).anyMatch(name -> name.equals(e.getName()))
部分,首先我从listCar
制作地图并制作我要比较的字段的键,在这种情况下是汽车的 name
并在我将 list1 映射为 CarData
时过滤掉空值。
所以应该是这样的:
final Map<String, CarData> allCarsMap = listCar // Your List2
.stream().collect(Collectors.toMap(CarData::getName, o -> o));
final List<CarData> listOutput = // Your expected result
listCarName // Your List1
.stream()
.map(allCarsMap::get) // will map each name with a value in the map
.filter(Objects::nonNull) // filter any null value for any car name that does not exist in the map
.collect(Collectors.toList());
我希望这会有所帮助,也许在某些情况下性能会更好一些?
【讨论】:
以上是关于基于另一个列表的 1 个列表的流过滤器的主要内容,如果未能解决你的问题,请参考以下文章