Java 8按包含前面元素的条件将列表划分为多个组

Java 8按包含前面元素的条件将列表划分为多个组,java,functional-programming,java-8,Java,Functional Programming,Java 8,假设我有一个间隔列表(按开始排序),我想把它们分开,这样我就有了一个重叠间隔组的列表。因此,例如,使用Interval作为: public class Interval { private final int start; private final int end; public Interval(int start,int end){ this.start = start; this.end = end; } pub

假设我有一个间隔列表(按开始排序),我想把它们分开,这样我就有了一个重叠间隔组的列表。因此,例如,使用
Interval
作为:

public class Interval {
    private final int start;
    private final int end;

    public Interval(int start,int end){
        this.start = start;
        this.end = end;
    }

    public int getStart(){return start;}
    public int getEnd(){return end;}

    public String toString(){ return "("+start+","+end+")"; }
}
还有一个
列表
,如:

[(0,4),(1,7),(6,10),(13,17),(20,100),(22,31),(60,65)]
我想要一个
列表的输出:

我可以编写这些代码,但我真的很喜欢Java8更具功能的方法,我想知道是否有类似于使用Java8流的惯用方法

我已经查看了所提供的“分组”样式,但它们似乎不适用,因为我不是由一个分类器来真正分组的,你不能只根据每个元素的属性来计算组,所以你必须考虑到每个元素的属性与已经计算的组有关。


当然,在函数式语言中有一种非疯狂的方法可以做到这一点(尽管我所说的不是真正的函数式程序员:-)。在Java8中如何使用流呢?

您不能。溪流不适合这种问题;流没有“以前的元素”的概念,允许以任意顺序在元素上运行。当然,您可以用Java实现,也可以用函数式语言实现,但这并不意味着流的工作方式与您习惯的函数式语言数据结构相同。

您在研究
groupingBy
收集器时,找到了正确的位置,但是你也对了,他们不会为合并区间提供必要的逻辑。但它们在概念上是将元素合并到先前元素创建的状态中。您必须自己实现一个类似的收集器

根据您的规范,即元素已经通过其开始索引进行了预排序,您可以按照以下方式进行操作:

Comparator<Interval> byStart = Comparator.comparingInt(Interval::getStart);
Comparator<Interval> byEnd   = Comparator.comparingInt(Interval::getEnd);
Collection<List<Interval>> merged = intervalList.stream().collect(
        () -> new TreeMap<Interval,List<Interval>>(byStart),
        (map,i) -> {
            Map.Entry<Interval,List<Interval>> e=map.floorEntry(i);
            if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart())
                e.getValue().add(i);
            else map.computeIfAbsent(i, x->new ArrayList<>()).add(i);
        },
        (m1,m2) -> m2.forEach((i,list) -> {
            Map.Entry<Interval,List<Interval>> e=m1.floorEntry(i);
            if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart())
                e.getValue().addAll(list);
            else m1.put(i, list);
        })
    ).values();
如果您打算将结果保留更长的时间而不是立即处理它,那么您应该明确地这样做,因为收集器返回的
集合
是一个
树状图的视图
包含的资源比需要的多


我想,在大多数情况下,您最好使用基于循环的解决方案。

也有同样的问题。也许你们能做的就是通过过滤器在每个组间隔内创建多个流,然后将所有分离的流连接在一起。如果您想创建3个组,那么您可以为每个组创建3个单独的流,然后加入到一个组中。

我猜,
this.start=end不是您想要的。但是,使用
final
变量是一件好事,因此编译器可以立即发现错误。顺便问一下,当输入为以下内容时,输出应该是什么:
[(60,65)、(22,31)、(20,100)]
?这三个时间间隔应该合并在一起吗?换句话说,输入间隔的顺序会改变结果吗?@Tagir Valeev:问题的前提(在第一句中)是元素按其起点排序。通过为任何解决方案预先设置排序步骤,可以轻松满足这一要求。并非所有操作都允许以任意顺序处理元素。允许任意顺序会使实现变得非常困难,例如,
Collector.toList()
,但是toList确实允许元素以任意顺序,它只是以有序的方式将累加器合并在一起。好的
toList()
Collector.of(ArrayList::new,List::add,(left,right)->{left.addAll(right)基本相同;return left;})
,如果有定义的相遇顺序,它显然依赖于流实现以正确的顺序调用这些函数。表示为
List::add
的函数接收所有先前元素和下一个元素的列表。很明显,您可以根据相同的保证编写自己的收集器。很好的解决方案!(1) 我认为,如果累加器连续地运行在每个流元素中,那么组合器是无用的(如果这个解决方案要起作用的话,必须是这种情况)。(2) 同意:在这种情况下,循环可能更透明、更高效。@codeCogs:合并器不会用于顺序流,但始终提供一个有效的合并器函数以避免将来出现意外是一种很好的编码方式。从形式上讲,不这样做甚至违反了API合同。该解决方案的组合器工作正常,但为了实现这一点付出了巨大的努力,这可能会吞噬并行处理的任何好处(如果有任何好处的话)…
Comparator<Interval> byStart = Comparator.comparingInt(Interval::getStart);
Comparator<Interval> byEnd   = Comparator.comparingInt(Interval::getEnd);
Collection<List<Interval>> merged = intervalList.stream().collect(
        () -> new TreeMap<Interval,List<Interval>>(byStart),
        (map,i) -> {
            Map.Entry<Interval,List<Interval>> e=map.floorEntry(i);
            if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart())
                e.getValue().add(i);
            else map.computeIfAbsent(i, x->new ArrayList<>()).add(i);
        },
        (m1,m2) -> m2.forEach((i,list) -> {
            Map.Entry<Interval,List<Interval>> e=m1.floorEntry(i);
            if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart())
                e.getValue().addAll(list);
            else m1.put(i, list);
        })
    ).values();
List<List<Interval>> list = new ArrayList<>(merged);