Java 8将n大小集合划分为m个未知大小的列表

时间:2018-07-20 11:59:40

标签: java java-8

一般问题 我不知道如何将排序后的列表划分为较小的排序后的列表,但是不像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());
}

3 个答案:

答案 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]}