Java8流混合两个元素

Java8流混合两个元素,java,java-8,java-stream,Java,Java 8,Java Stream,我在数组列表中有许多类型为Slot的对象 插槽类如下图所示- Slot{ int start; int end; } 让类型列表list被称为slots。根据开始时间对插槽进行排序。一个插槽的结束时间可能等于下一个插槽的开始时间,但它们永远不会重叠 有没有任何可能的方法可以让我使用Java 8 streams迭代这个列表,如果一个列表的结束时间与下一个列表的开始时间匹配,那么我可以组合两个插槽,并将它们输出到一个数组列表中?您可以使用U作为另一个列表的方法来完成这个任务,但它比仅仅

我在数组列表中有许多类型为Slot的对象

插槽类如下图所示-

Slot{
   int start;
   int end;
}
让类型列表
list
被称为
slots
。根据开始时间对插槽进行排序。一个插槽的结束时间可能等于下一个插槽的开始时间,但它们永远不会重叠

有没有任何可能的方法可以让我使用Java 8 streams迭代这个列表,如果一个列表的结束时间与下一个列表的开始时间匹配,那么我可以组合两个插槽,并将它们输出到一个
数组列表中

您可以使用
U
作为另一个
列表的方法来完成这个任务,但它比仅仅在
for
循环中执行要复杂得多,除非需要并行处理

有关测试设置,请参见答案末尾

以下是循环实现的

List<Slot> mixed = new ArrayList<>();
Slot last = null;
for (Slot slot : slots)
    if (last == null || last.end != slot.start)
        mixed.add(last = slot);
    else
        mixed.set(mixed.size() - 1, last = new Slot(last.start, slot.end));
List<Slot> mixed = slots.stream().reduce((List<Slot>)null, (list, slot) -> {
    System.out.println("accumulator.apply(" + list + ", " + slot + ")");
    if (list == null) {
        List<Slot> newList = new ArrayList<>();
        newList.add(slot);
        return newList;
    }
    Slot last = list.get(list.size() - 1);
    if (last.end != slot.start)
        list.add(slot);
    else
        list.set(list.size() - 1, new Slot(last.start, slot.end));
    return list;
}, (list1, list2) -> {
    System.out.println("combiner.apply(" + list1 + ", " + list2 + ")");
    if (list1 == null)
        return list2;
    if (list2 == null)
        return list1;
    Slot lastOf1 = list1.get(list1.size() - 1);
    Slot firstOf2 = list2.get(0);
    if (lastOf1.end != firstOf2.start)
        list1.addAll(list2);
    else {
        list1.set(list1.size() - 1, new Slot(lastOf1.start, firstOf2.end));
        list1.addAll(list2.subList(1, list2.size()));
    }
    return list1;
});
以下是stream reduce实现:

List<Slot> mixed = new ArrayList<>();
Slot last = null;
for (Slot slot : slots)
    if (last == null || last.end != slot.start)
        mixed.add(last = slot);
    else
        mixed.set(mixed.size() - 1, last = new Slot(last.start, slot.end));
List<Slot> mixed = slots.stream().reduce((List<Slot>)null, (list, slot) -> {
    System.out.println("accumulator.apply(" + list + ", " + slot + ")");
    if (list == null) {
        List<Slot> newList = new ArrayList<>();
        newList.add(slot);
        return newList;
    }
    Slot last = list.get(list.size() - 1);
    if (last.end != slot.start)
        list.add(slot);
    else
        list.set(list.size() - 1, new Slot(last.start, slot.end));
    return list;
}, (list1, list2) -> {
    System.out.println("combiner.apply(" + list1 + ", " + list2 + ")");
    if (list1 == null)
        return list2;
    if (list2 == null)
        return list1;
    Slot lastOf1 = list1.get(list1.size() - 1);
    Slot firstOf2 = list2.get(0);
    if (lastOf1.end != firstOf2.start)
        list1.addAll(list2);
    else {
        list1.set(list1.size() - 1, new Slot(lastOf1.start, firstOf2.end));
        list1.addAll(list2.subList(1, list2.size()));
    }
    return list1;
});
将其更改为并行(多线程)处理:

List<Slot> mixed = slots.stream().parallel().reduce(...
警告

如果
slots
是一个空列表,则
for
循环版本将导致一个空列表,而streams版本结果是一个
null


测试设置

以上所有代码都使用了以下
插槽
类:

class Slot {
    int start;
    int end;
    Slot(int start, int end) {
        this.start = start;
        this.end = end;
    }
    @Override
    public String toString() {
        return this.start + "-" + this.end;
    }
}
slots
变量定义为:

List<Slot> slots = Arrays.asList(new Slot(3, 5), new Slot(5, 7), new Slot(8, 10), new Slot(10, 11), new Slot(11, 13));

由于这些类型的问题经常出现,我认为编写一个收集器,通过谓词将相邻元素分组可能是一个有趣的练习

假设我们可以向
插槽
类添加组合逻辑

boolean canCombine(Slot other) {
    return this.end == other.start;
}

Slot combine(Slot other) {
    if (!canCombine(other)) {
        throw new IllegalArgumentException();
    }
    return new Slot(this.start, other.end);
}
public boolean join(Slot s) {
    if(s.start != end)
        return false;
    end = s.end;
    return true;
}
然后可以按如下方式使用
groupingAdjacent
收集器:

List<Slot> combined = slots.stream()
    .collect(groupingAdjacent(
        Slot::canCombine,         // test to determine if two adjacent elements go together
        reducing(Slot::combine),  // collector to use for combining the adjacent elements
        mapping(Optional::get, toList())  // collector to group up combined elements
    ));

我的免费库增强了标准流API,完全支持这种场景。有一个中间操作,可以将几个相邻的流元素折叠为单个元素。以下是完整的示例:

// Slot class and sample data are taken from @Andreas answer
List<Slot> slots = Arrays.asList(new Slot(3, 5), new Slot(5, 7), 
                new Slot(8, 10), new Slot(10, 11), new Slot(11, 13));

List<Slot> result = StreamEx.of(slots)
        .intervalMap((s1, s2) -> s1.end == s2.start,
                     (s1, s2) -> new Slot(s1.start, s2.end))
        .toList();
System.out.println(result);
// Output: [3-7, 8-13]
这是一个两行:

List<Slot> condensed = new LinkedList<>();
slots.stream().reduce((a,b) -> {if (a.end == b.start) return new Slot(a.start, b.end); 
  condensed.add(a); return b;}).ifPresent(condensed::add);
输出:

[3-7, 8-13]
[3-7, 8-11, 12-13]
[3-7]
[]
[[3, 7], [8, 13]]
[[3, 7], [8, 11], [12, 13]]
[[3, 7]]
[]

如果将以下方法添加到
槽中

boolean canCombine(Slot other) {
    return this.end == other.start;
}

Slot combine(Slot other) {
    if (!canCombine(other)) {
        throw new IllegalArgumentException();
    }
    return new Slot(this.start, other.end);
}
public boolean join(Slot s) {
    if(s.start != end)
        return false;
    end = s.end;
    return true;
}
您可以通过以下方式使用标准API执行整个操作

List<Slot> result = slots.stream().collect(ArrayList::new,
    (l, s)-> { if(l.isEmpty() || !l.get(l.size()-1).join(s)) l.add(s); },
    (l1, l2)-> l1.addAll(
        l1.isEmpty()||l2.isEmpty()||!l1.get(l1.size()-1).join(l2.get(0))?
        l2: l2.subList(1, l2.size()))
);
不需要任何新方法的干净(并行安全)解决方案:

List<Slot> condensed = slots.stream().collect(LinkedList::new,
  (l, s) -> l.add(l.isEmpty() || l.getLast().end != s.start ?
    s : new Slot(l.removeLast().start, s.end)),
  (l, l2) -> {if (!l.isEmpty() && !l2.isEmpty() && l.getLast().end == l2.getFirst().start) {
    l.add(new Slot(l.removeLast().start, l2.removeFirst().end));} l.addAll(l2);});
输出:

[3-7, 8-13]
[3-7, 8-11, 12-13]
[3-7]
[]
[[3, 7], [8, 13]]
[[3, 7], [8, 11], [12, 13]]
[[3, 7]]
[]

@Pshemo,“记住以前的元素”并不是一个真正适用于
Stream
s的操作。使用简单的循环几乎肯定能更好地解决这个问题。@LouisWasserman Woops您是对的。我错过了“Java 8 streams”部分(大脑放屁可能是由于缺少适当的标记引起的-现已修复)。您可能可以使用
U
作为另一个
列表的方法来完成此操作,但如果只是在
for
循环中完成此操作,可能会有点复杂,除非需要并行处理。是否需要流API?你看了吗?我没有分析它,但根据@Bohemian的说法,它应该简单得多。@Puce你是对的,Bohemian的版本小得多,但它不适用于并行处理。我的代码有一半以上是支持并行处理的。感谢您的澄清。像往常一样,应该注意的是,这种解决方案违反了
reduce
方法的约定,不利于并行处理。如果在流API中实现
foldLeft(累加器)
,这样的解决方案会更可靠…@Tagir在这种情况下,维护顺序是至关重要的,因此它永远不会是可并行的。但“违规”权衡似乎是一个很好的选择,可以将任务简化为一个相对简单的lambda。不过我喜欢你的解决方案-我以前没有注意到
intervalMap()
-很好!实际上,所有其他解决方案(不仅仅是我的)都是并行友好的。并行流仍然可以保持顺序,您只需要能够将任务分成独立的部分(如列表的前半部分,然后是后半部分),独立地处理它们,然后将结果连接在一起。我的解决方案虽然是最灵活的,因为它作为中间操作工作,但实际上有一大块代码。如果没有第三方库,Misha和Holger解决方案是可以的,尽管它们终止了流。你的一个可能在“最佳滥用规则”类别中获胜:-)你让我感到羞耻。。。