Java8流混合两个元素
我在数组列表中有许多类型为Slot的对象 插槽类如下图所示-Java8流混合两个元素,java,java-8,java-stream,Java,Java 8,Java Stream,我在数组列表中有许多类型为Slot的对象 插槽类如下图所示- Slot{ int start; int end; } 让类型列表list被称为slots。根据开始时间对插槽进行排序。一个插槽的结束时间可能等于下一个插槽的开始时间,但它们永远不会重叠 有没有任何可能的方法可以让我使用Java 8 streams迭代这个列表,如果一个列表的结束时间与下一个列表的开始时间匹配,那么我可以组合两个插槽,并将它们输出到一个数组列表中?您可以使用U作为另一个列表的方法来完成这个任务,但它比仅仅
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解决方案是可以的,尽管它们终止了流。你的一个可能在“最佳滥用规则”类别中获胜:-)你让我感到羞耻。。。