[英]Java 8 divide n size collection to m lists of unknown size
一般问题我不知道如何将排序后的列表划分为较小的排序后的列表,但是不像Guava Lists.partition(list,size)
那样进行Lists.partition(list,size)
-因此,不能将指定大小的较小列表Lists.partition(list,size)
为固定数量的列表大小相似。
例如,具有一个源列表:1,2,3,4我希望有3个列表作为结果(3个是结果列表的固定数量)。 我应该得到List<List<Long>>
的结果:ListOne:1,ListTwo:2,ListThree:3,4(请记住要保留排序)。 当源列表小于目标列表的数量时,然后确定,我可以获得较小数量的列表。 因此,如果源列表为1,2,而我想拥有3个列表,则算法应返回两个列表:List1 1,List2:2。
源列表的大小是未知的,但是有成千上万的元素必须分成10个列表,因为那时有10个线程准备使用这些元素执行一些更复杂的操作。
下面的算法是完全错误的,在源列表上有14个元素,并通过GRD_SIZE=10
它返回2个元素的7个列表。 它应该返回GRD_SIZE=10
个相似大小的列表。 可能我也不应使用Guava Lists.partition方法...但是如何执行此任务?
List<List<Long>> partitions = partition(sourceList, GRD_SIZE);
public static <T> List<List<T>> partition(List<T> ascSortedItems, int size)
{
int threadSize = (int) Math.ceil(
new BigDecimal(ascSortedItems.size()).divide(
new BigDecimal(
ascSortedItems.size() >= size ? size : ascSortedItems.size()
)
).doubleValue()
);
final AtomicInteger counter = new AtomicInteger(0);
return ascSortedItems.stream()
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / threadSize))
.values()
.stream()
.collect(Collectors.toList());
}
首先,您不应该像尝试的解决方案那样使用可变计数器。 一个规范的替代方案将类似于以下答案 ,该方案在适应您的问题后将如下所示:
return IntStream.range(0, (ascSortedItems.size()+threadSize-1)/threadSize) .mapToObj(i -> ascSortedItems .subList(i*threadSize, Math.min(ascSortedItems.size(), (i+1)*threadSize))) .collect(Collectors.toList()); }
threadSize
的计算不需要怪异的BigDecimal
弯路。 您可以将其计算为
int threadSize = Math.max(1, ascSortedItems.size()/size);
四舍五入时
int threadSize = Math.max(1, (ascSortedItems.size()+size-1)/size);
四舍五入。
但两者都无法按预期方式工作。 与您的示例保持一致,四舍五入将创建14个大小为1的列表,四舍五入将创建7个大小为2的列表。
真正的解决方案只能通过不首先计算批处理大小来完成:
public static <T> List<List<T>> partition(List<T> ascSortedItems, int numLists) {
if(numLists < 2) {
if(numLists < 1) throw new IllegalArgumentException();
return Collections.singletonList(ascSortedItems);
}
int listSize = ascSortedItems.size();
if(listSize <= numLists) {
return ascSortedItems.stream()
.map(Collections::singletonList)
.collect(Collectors.toList());
}
return IntStream.range(0, numLists)
.mapToObj(i -> ascSortedItems.subList(i*listSize/numLists, (i+1)*listSize/numLists))
.collect(Collectors.toList());
}
第一部分检查numLists
参数的有效性,并处理一个列表的特殊情况。
如果源列表小于请求的列表数,则中间部分处理您返回较少列表的要求(否则,结果将始终具有请求的列表数,可能包含空列表)。
最后一部分完成实际工作。 如您所见,初始IntStream.range(0, numLists)
始终会产生numLists
元素,然后将其映射到子列表,从而将舍入时间推迟到可能的最新点。 对于您的[ 1, 2, 3, 4]
和三个请求列表的示例,它将产生
[1]
[2]
[3, 4]
对于14个元素并请求十个列表,它会产生
[1]
[2]
[3, 4]
[5]
[6, 7]
[8]
[9]
[10, 11]
[12]
[13, 14]
不可避免地要有一些大小为1的列表和一些大小为2的列表来满足具有恰好十个列表的请求。 这是第一个基于大小目标的解决方案的根本区别,在该解决方案中,一个列表最多具有与其他列表不同的大小。
n
是原始列表中的元素数
z
是网格大小
s = z/n
(整数除法)给出每个数组应容纳的基本项数
r
是整数除法的余数
对第一个zr
数组运行循环,以正确的顺序附加每个s
项目
运行最后一个r
数组的第二个循环,每个数组以正确的顺序附加s + 1
项。
假设:以下答案仅适用于有序和顺序流。 该问题要求将排序后的列表(有序的)分成较小的排序后的列表。
如果流是无序的,则必须通过Stream.sorted()之类的方式对流进行排序。
让我们构造一个包含17个元素的列表。
List<Long> sourceList = LongStream.range(0,17).collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
要将17个元素分为10个列表的相等集合,我们将必须制作7个2个元素的列表和3个1个元素的列表。
换句话说,可以使用最少1个元素创建10个相等的列表。 并且可以在前7个列表中添加7个额外的元素。
int minElementinEachList = sourceList.size() /10; //1
int extraElements = sourceList.size() %10; //7
直到创建了7( extraElements
)个列表,我们才能在列表中添加一个额外的元素。 使用以下代码:
AtomicInteger counter = new AtomicInteger(0);
Map<Number,List<Number>> map = sourceList.stream().collect(
Collectors.groupingBy(it -> {
int key = counter.getAndIncrement() / (minElementinEachList + 1);
if(key >= extraElements && (counter.get() + 1) % (minElementinEachList +1 ) == 0){
counter.getAndIncrement();
}
return key;
}));
System.out.println(map);
输出:
{0=[0, 1], 1=[2, 3], 2=[4, 5], 3=[6, 7], 4=[8, 9], 5=[10, 11], 6=[12, 13], 7=[14], 8=[15], 9=[16]}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.