集合对象获取笛卡尔积
实际业务场景说明
应用:根据导入的属性名称以及属性值(两者都为“/”划分的字符串),基于属性及属性值组合生成所有的商品规格(规格值组合);需要补全导入文件中未填入的数据。
本人作为记录和大家学习使用。
数据示例:
属性名称1:属性值1-1,属性值1-2,属性值1-3
属性名称2:属性值2-1,属性值2-2,属性值2-3
结果:理论可得组合应为3*3=9种;
由于本人使用的环境版本较低,无法直接使用高版本自带的笛卡尔积获取方法;故问度娘后,抄了一位大佬的方法,实测可行。
实现
最容易想到的就是递归。
递归
介绍:对程序语言来说,递归就是间接或者直接调用自身的一种方法。
/**
* 递归实现
* 原理:从原始list的0开始依次遍历到最后
*
* @param originalList 原始list
* @param position 当前递归在原始list的position 起始为0
* @param returnList 返回结果
* @param cacheList 临时保存的list
*/
private static <T> void descartesRecursive(List<List<T>> originalList, int position, List<List<T>> returnList, List<T> cacheList) {
List<T> originalItemList = originalList.get(position);
for (int i = 0; i < originalItemList.size(); i++) {
//最后一个复用cacheList,节省内存
List<T> childCacheList = (i == originalItemList.size() - 1) ? cacheList : new ArrayList<>(cacheList);
childCacheList.add(originalItemList.get(i));
if (position == originalList.size() - 1) {//遍历到最后退出递归
returnList.add(childCacheList);
continue;
}
descartesRecursive(originalList, position + 1, returnList, childCacheList);
}
}
经过大佬改良的代码,如下:
其实就是省去了传参,使用了stream的集合合并
private static <T> List<List<T>> descartesRecursive(List<List<T>> originalList) {
List<List<T>> data = new ArrayList<>();
for (List<T> list : originalList) {
List<List<T>> temp = new ArrayList<>();
if(CollectionUtils.isEmpty(data)){
for(T t : list){
temp.add(Stream.of(t).collect(Collectors.toList()));
}
data = temp;
continue;
}
for(T t : list){
for (List<T> re : data) {
temp.add(Stream.concat(re.stream(), Stream.of(t)).collect(Collectors.toList()));
}
}
data = temp;
}
return data;
}
基于guava工具包的解法
缺点是已知集合数量,没研究,不知道有没有不确定集合数的。
List<List<String>> cartesianProductList = Lists.cartesianProduct(Lists.newArrayList("A", "B", "C"), Lists.newArrayList("a1", "b1", "c1"));
因为数据在组装后需要根据名称进行Id绑定,所以数据结构其实是是双层的集合List<List>。
问题:实际上这种应用场景在真实需求中非常鸡肋,因为会极大增加系统的负担,
本人也是在拿到生产数据后测试得知,因为笛卡尔积这一需求,对导入文件的数据限制失去意义,因为属性及属性值的增加会导致笛卡尔积结果的成倍增加。可能20条导入数据就能产生几百种组合对应的写入数据。
最后,方法网上找的,不是本人原创,侵联删
最后,整几篇过程中研究过的帖子,没看懂,用了stream,大家可以看看:
https://www.codenong.com/38623759/