Java Stream:有没有办法一次迭代两个元素而不是一个?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34086461/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Stream: is there a way to iterate taking two elements a time instead of one?
提问by Luigi Cortese
Let's say we have this stream
假设我们有这个流
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j");
and I want to save in a map the couples of adjacent strings in which the first one starts with "err".
我想在地图中保存一对相邻的字符串,其中第一个字符串以“err”开头。
What I thought of is something like this
我想到的是这样的
Map<String, String> map = new HashMap<>();
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.reduce((acc, next) -> {
if (acc.startsWith("err"))
map.put(acc,next);
if (next.startsWith("err"))
return next;
else
return "";
});
But I'm not totally satisfied with it for two main reasons
但我对它并不完全满意,主要有两个原因
- I'm "misusing"
reduce
function. In Stream API every function has its clear, well defined purpose:max
is supposed to calcuate max value,filter
is supposed to filter based on a condition,reduce
is supposed to produce an incrementally accumulated value and so on. - Doing that prevents me from using Streams powerful mechanisms: what if I wanted to limit my search to the first two results?
- 我在“滥用”
reduce
功能。在 Stream API 中,每个函数都有明确的、定义明确的目的:max
应该计算最大值,filter
应该根据条件进行过滤,reduce
应该产生增量累加值等等。 - 这样做会阻止我使用 Streams 强大的机制:如果我想将搜索限制为前两个结果怎么办?
Here I used reduce
because (as far as I know) it's the only function that lets you compare couple of values that you can, somehow, lead back to something similar to "current value" and "next value" concepts.
我在这里使用reduce
是因为(据我所知)它是唯一可以让您比较几个值的函数,您可以以某种方式返回类似于“当前值”和“下一个值”概念的东西。
Is there a more straightforward way? Something that allows you to iterate the stream considering more than one value for each iteration?
有没有更直接的方法?允许您在每次迭代中考虑多个值来迭代流的东西?
EDIT
编辑
What I'm thinking about is some mechanism that, given the current element, allows you to define a "window of elements" to consider, for each iteration.
我正在考虑的是某种机制,在给定当前元素的情况下,允许您为每次迭代定义一个要考虑的“元素窗口”。
Something like
就像是
<R> Stream<R> mapMoreThanOne(
int elementsBeforeCurrent,
int elementsAfterCurrent,
Function<List<? super T>, ? extends R> mapper);
instead of
代替
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
That would be a powerful "upgrade" to current API.
这将是对当前 API 的强大“升级”。
EDIT2
编辑2
I appreciate the effort of people proposing their solution, but the issue is not the algorithm per se. There are different ways to achieve my goal by putting together streams, indexes, temp variables to store previous values... but I was wondering if there was some method in Stream API that was designed for the task of dealing with elements other than the current without breaking the "stream paradigm". Something like this
我感谢人们提出解决方案的努力,但问题不在于算法本身。通过将流、索引、临时变量放在一起来存储以前的值,有不同的方法可以实现我的目标……但我想知道 Stream API 中是否有一些方法是为处理当前元素以外的元素而设计的不打破“流范式”。像这样的东西
List<String> list =
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.filterFunctionImWonderingIfExist(/*filters couples of elements*/)
.limit(2)
.collect(Collectors.toList());
Given the answers, I think there's no "clear and quick" solution, unless using StreamEx library
鉴于答案,我认为没有“清晰快捷”的解决方案,除非使用 StreamEx 库
采纳答案by Tunaki
You can build a custom Collector for this task.
您可以为此任务构建自定义收集器。
Map<String, String> map =
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.collect(MappingErrors.collector());
with:
和:
private static final class MappingErrors {
private Map<String, String> map = new HashMap<>();
private String first, second;
public void accept(String str) {
first = second;
second = str;
if (first != null && first.startsWith("err")) {
map.put(first, second);
}
}
public MappingErrors combine(MappingErrors other) {
throw new UnsupportedOperationException("Parallel Stream not supported");
}
public Map<String, String> finish() {
return map;
}
public static Collector<String, ?, Map<String, String>> collector() {
return Collector.of(MappingErrors::new, MappingErrors::accept, MappingErrors::combine, MappingErrors::finish);
}
}
In this collector, two running elements are kept. Each time a String
is accepted, they are updated and if the first starts with "err"
, the two elements are added to a map.
在这个收集器中,保留了两个运行元素。每次String
接受 a 时,它们都会更新,如果第一个以 开头"err"
,则将两个元素添加到映射中。
Another solution is to use the StreamExlibrary which provides a pairMap
method that applies a given function to the every adjacent pair of elements of this stream. In the following code, the operation returns a String array consisting of the first and second element of the pair if the first element starts with "err"
, null
otherwise. null
elements are then filtered out and the Stream is collected into a map.
另一种解决方案是使用StreamEx库,该库提供了pairMap
一种将给定函数应用于此流的每个相邻元素对的方法。在以下代码中,如果第一个元素以 开头"err"
,null
则该操作返回一个字符串数组,该数组由该对的第一个和第二个元素组成,否则。null
然后过滤掉元素,并将 Stream 收集到映射中。
Map<String, String> map =
StreamEx.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.pairMap((s1, s2) -> s1.startsWith("err") ? new String[] { s1, s2 } : null)
.nonNull()
.toMap(a -> a[0], a -> a[1]);
System.out.println(map);
回答by Tagir Valeev
Things would be easier if your input is located in the random-access list. This way you can utilize good old List.subList
method like this:
如果您的输入位于随机访问列表中,事情会更容易。通过这种方式,您可以使用这样的旧List.subList
方法:
List<String> list = Arrays.asList("a", "b", "err1", "c", "d", "err2", "e",
"f", "g", "h", "err3", "i", "j");
Map<String, String> map = IntStream.range(0, list.size()-1)
.mapToObj(i -> list.subList(i, i+2))
.filter(l -> l.get(0).startsWith("err"))
.collect(Collectors.toMap(l -> l.get(0), l -> l.get(1)));
The same thing could be done with already mentioned StreamEx library (written by me) in a little bit shorter manner:
同样的事情可以用已经提到的 StreamEx 库(由我编写)以更短的方式完成:
List<String> list = Arrays.asList("a", "b", "err1", "c", "d", "err2", "e",
"f", "g", "h", "err3", "i", "j");
Map<String, String> map = StreamEx.ofSubLists(list, 2, 1)
.mapToEntry(l -> l.get(0), l -> l.get(1))
.filterKeys(key -> key.startsWith("err"))
.toMap();
Though if you don't want third-party dependency, the poor Stream API solution looks also not very bad.
虽然如果你不想要第三方依赖,糟糕的 Stream API 解决方案看起来也不是很糟糕。
回答by Misha
You can write a custom collector, or use the much simpler approach of streaming over the list's indexes:
您可以编写自定义收集器,或使用更简单的方法来流式传输列表的索引:
Map<String, String> result = IntStream.range(0, data.size() - 1)
.filter(i -> data.get(i).startsWith("err"))
.boxed()
.collect(toMap(data::get, i -> data.get(i+1)));
This assumes that your data is in a random access friendly list or that you can temporarily dump it into one.
这假设您的数据在一个随机访问友好列表中,或者您可以暂时将其转储到一个列表中。
If you cannot randomly access the data or load it into a list or array for processing, you can always make a custom pairing
collector so you can write
如果无法随机访问数据或将其加载到列表或数组中进行处理,您可以随时制作自定义pairing
收集器,以便您可以编写
Map<String, String> result = data.stream()
.collect(pairing(
(a, b) -> a.startsWith("err"),
AbstractMap.SimpleImmutableEntry::new,
toMap(Map.Entry::getKey, Map.Entry::getValue)
));
Here's the source for the collector. It's parallel-friendly and might come in handy in other situations:
这是收集器的来源。它是并行友好的,在其他情况下可能会派上用场:
public static <T, V, A, R> Collector<T, ?, R> pairing(BiPredicate<T, T> filter, BiFunction<T, T, V> map, Collector<? super V, A, R> downstream) {
class Pairing {
T left, right;
A middle = downstream.supplier().get();
boolean empty = true;
void add(T t) {
if (empty) {
left = t;
empty = false;
} else if (filter.test(right, t)) {
downstream.accumulator().accept(middle, map.apply(right, t));
}
right = t;
}
Pairing combine(Pairing other) {
if (!other.empty) {
this.add(other.left);
this.middle = downstream.combiner().apply(this.middle, other.middle);
this.right = other.right;
}
return this;
}
R finish() {
return downstream.finisher().apply(middle);
}
}
return Collector.of(Pairing::new, Pairing::add, Pairing::combine, Pairing::finish);
}
回答by lczapski
Other approach with Collector.of
and List<List<String>>
as structure to collect pairs.
First collect to List<List<String>>
:
使用Collector.of
和List<List<String>>
作为结构来收集对的其他方法。首先收集到List<List<String>>
:
List<List<String>> collect = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.collect(
Collector.of(
LinkedList::new,
(a, b) -> {
if (b.startsWith("err"))
a.add(new ArrayList<>(List.of(b)));
else if (!a.isEmpty() && a.getLast().size() == 1)
a.getLast().add(b);
},
(a, b) -> { throw new UnsupportedOperationException(); }
)
);
Then it can be transform to map
然后它可以转换为映射
Map<String, String> toMap = collect.stream().filter(l -> l.size() == 2)
.collect(Collectors.toMap(
e -> e.get(0),
e -> e.get(1))
);
Or all in one with Collectors.collectingAndThen
或与 Collectors.collectingAndThen
Map<String, String> toMap = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
.collect(Collectors.collectingAndThen(
Collector.of(
LinkedList<List<String>>::new,
(a, b) -> {
if (b.startsWith("err"))
a.add(new ArrayList<>(List.of(b)));
else if (!a.isEmpty() && a.getLast().size() == 1)
a.getLast().add(b);
},
(a, b) -> { throw new UnsupportedOperationException(); }
), (x) -> x.stream().filter(l -> l.size() == 2)
.collect(Collectors.toMap(
e -> e.get(0),
e -> e.get(1))
)
));
回答by Bohemian
Here's a simple one liner using an off-the-shelf collector:
这是一个使用现成收集器的简单衬里:
Stream<String> stream = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j");
Map<String, String> map = Arrays.stream(stream
.collect(Collectors.joining(",")).split(",(?=(([^,]*,){2})*[^,]*$)"))
.filter(s -> s.startsWith("err"))
.map(s -> s.split(","))
.collect(Collectors.toMap(a -> a[0], a -> a[1]));
The "trick" here is to first join all terms together into a single String, then split it into Strings of pairs, eg "a,b"
, "err1,c"
, etc. Once you have a stream of pairs, processing is straightforward.
该“绝招”这里是先加入所有条款在一起成一个字符串,然后将其拆分为对字符串,如"a,b"
,"err1,c"
等一旦你有对的流处理很简单。