繁体   English   中英

groupingBy 后排序列表

[英]sorting Lists after groupingBy

我想知道,流(或收集器)中是否已经实现了将列表作为值排序的功能。 例如,以下代码都生成按年龄排序的按性别分组的人员列表。 第一个解决方案有一些开销排序(看起来有点邋遢)。 第二种解决方案需要对每个人看两次,但以一种漂亮的方式完成工作。

首先排序然后在一个流中分组:

Map<Gender, List<Person>> sortedListsByGender = (List<Person>) roster
        .stream()
        .sorted(Person::compareByAge)
        .collect(Collectors.groupingBy(Person::getGender));

首先分组,然后对每个值进行排序:

Map<Gender, List<Person>> sortedListsByGender = (List<Person>) roster
        .stream()
        .collect(Collectors.groupingBy(Person::getGender));
sortedListsByGender.values()
        .forEach(list -> Collections.sort(list, Person::compareByAge));

我只是想知道,是否已经实现了一些东西,它可以一次性完成,比如groupingBySorted

collect操作之前在流上使用sorted(comparator)时,流必须缓冲整个流内容才能对其进行排序,与排序较小的列表相比,排序可能涉及该缓冲区内更多的数据移动之后分组。 因此,性能不如对单个组进行排序,尽管如果启用了并行处理,实现将使用多个内核。

但请注意,使用sortedListsByGender.values().forEach(…)不是可并行化的操作,即使使用sortedListsByGender.values().parallelStream().forEach(…)也只允许并行处理组,而每个排序操作仍然是顺序的.

在收集器中执行排序操作时

static <T> Collector<T,?,List<T>> toSortedList(Comparator<? super T> c) {
    return Collectors.collectingAndThen(
        Collectors.toCollection(ArrayList::new), l->{ l.sort(c); return l; } );
}

Map<Gender, List<Person>> sortedListsByGender = roster.stream()
    .collect(Collectors.groupingBy(Person::getGender, toSortedList(Person::compareByAge)));

排序操作的行为相同(感谢 Tagir Valeev 纠正我),但您可以轻松检查插入时排序策略的执行情况。 只需将收集器实现更改为:

static <T> Collector<T,?,List<T>> toSortedList(Comparator<? super T> c) {
    return Collectors.collectingAndThen(
        Collectors.toCollection(()->new TreeSet<>(c)), ArrayList::new);
}

为完整起见,如果您希望收集器首先将插入排序到ArrayList中以避免最后的复制步骤,您可以使用更详细的收集器,如下所示:

static <T> Collector<T,?,List<T>> toSortedList(Comparator<? super T> c) {
    return Collector.of(ArrayList::new,
        (l,t) -> {
            int ix=Collections.binarySearch(l, t, c);
            l.add(ix<0? ~ix: ix, t);
        },
        (list1,list2) -> {
            final int s1=list1.size();
            if(list1.isEmpty()) return list2;
            if(!list2.isEmpty()) {
                list1.addAll(list2);
                if(c.compare(list1.get(s1-1), list2.get(0))>0)
                    list1.sort(c);
            }
            return list1;
        });
}

它对于顺序使用是有效的,但它的合并功能不是最佳的。 底层排序算法将受益于预先排序的范围,但必须首先找到这些范围,尽管我们的合并函数实际上知道这些范围。 不幸的是,JRE 中没有公共 API 允许我们利用这些信息(有效地;我们可以将subList传递给binarySearch但为list2每个元素创建一个新的子列表可能会变得太昂贵)。 如果我们想进一步提高并行执行的性能,我们必须重新实现排序算法的合并部分:

static <T> Collector<T,?,List<T>> toSortedList(Comparator<? super T> c) {
    return Collector.of(ArrayList::new,
        (l,t) -> l.add(insertPos(l, 0, l.size(), t, c), t),
        (list1,list2) -> merge(list1, list2, c));
}
static <T> List<T> merge(List<T> list1, List<T> list2, Comparator<? super T> c) {
    if(list1.isEmpty()) return list2;
    for(int ix1=0, ix2=0, num1=list1.size(), num2=list2.size(); ix2<num2; ix2++, num1++) {
        final T element = list2.get(ix2);
        ix1=insertPos(list1, ix1, num1, element, c);
        list1.add(ix1, element);
        if(ix1==num1) {
            while(++ix2<num2) list1.add(list2.get(ix2));
            return list1;
        }
    }
    return list1;
}
static <T> int insertPos(
    List<? extends T> list, int low, int high, T t, Comparator<? super T> c) {
    high--;
    while(low <= high) {
        int mid = (low+high)>>>1, cmp = c.compare(list.get(mid), t);
        if(cmp < 0) low = mid + 1;
        else if(cmp > 0) high = mid - 1;
        else {
            mid++;
            while(mid<=high && c.compare(list.get(mid), t)==0) mid++;
            return mid;
        }
    }
    return low;
}

请注意,与简单的基于binarySearch的插入不同,这最后一个解决方案是一种稳定的排序实现,即在您的情况下,如果源流具有定义的相遇顺序,则具有相同年龄和Gender Person不会改变它们的相对顺序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM