一般问题
我不知道如何将排序后的列表划分为较小的排序后的列表,但是不像Guava Lists.partition(list,size)
中那样将其划分为-而不是将指定大小的较小列表划分为固定数量的相似大小的列表。 / p>
例如,具有一个源列表: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());
}
答案 0 :(得分:2)
首先,您不应像尝试的解决方案那样使用可变计数器。规范的替代方案类似于this answer中的方案,在适应您的问题后,其外观将如下所示:
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的列表来满足要求恰好有10个列表的要求。这是第一个基于大小目标的解决方案的根本区别,在该解决方案中,一个列表最多具有与其他列表不同的大小。
答案 1 :(得分:1)
n
是原始列表中的元素数
z
是网格大小
s = z/n
(整数除法)给出每个数组应容纳的基本项数
r
是整数除法的余数
为第一个z-r
数组运行循环,并以正确的顺序将每个s
项附加到后面
对最后的r
个数组运行第二个循环,每个数组以正确的顺序附加s + 1
个项目。
答案 2 :(得分: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]}