java 是否可以获取流中的下一个元素?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31650157/
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
Is it possible to get next element in the Stream?
提问by sidgate
I am trying to converting a for loop to functional code. I need to look ahead one value and also look behind one value. Is it possible using streams? The following code is to convert the Roman text to numeric value. Not sure if reduce method with two/three arguments can help here.
我正在尝试将 for 循环转换为功能代码。我需要向前看一个值,也需要向后看一个值。是否可以使用流?以下代码是将罗马文本转换为数值。不确定带有两个/三个参数的 reduce 方法在这里是否有帮助。
int previousCharValue = 0;
int total = 0;
for (int i = 0; i < input.length(); i++) {
char current = input.charAt(i);
RomanNumeral romanNum = RomanNumeral.valueOf(Character.toString(current));
if (previousCharValue > 0) {
total += (romanNum.getNumericValue() - previousCharValue);
previousCharValue = 0;
} else {
if (i < input.length() - 1) {
char next = input.charAt(i + 1);
RomanNumeral nextNum = RomanNumeral.valueOf(Character.toString(next));
if (romanNum.getNumericValue() < nextNum.getNumericValue()) {
previousCharValue = romanNum.getNumericValue();
}
}
if (previousCharValue == 0) {
total += romanNum.getNumericValue();
}
}
}
采纳答案by Hoopje
No, this is not possible using streams, at least not easily. The stream API abstracts away from the order in which the elements are processed: the stream might be processed in parallel, or in reverse order. So "the next element" and "previous element" do not exist in the stream abstraction.
不,使用流是不可能的,至少不容易。流 API 从处理元素的顺序中抽象出来:流可以并行处理,也可以以相反的顺序处理。因此,流抽象中不存在“下一个元素”和“上一个元素”。
You should use the API best suited for the job: stream are excellent if you need to apply some operation to allelements of a collection and you are not interested in the order. If you need to process the elements in a certain order, you have to use iterators or maybe access the list elements through indices.
您应该使用最适合这项工作的 API:如果您需要对集合的所有元素应用一些操作并且您对顺序不感兴趣,那么流是非常好的。如果您需要按特定顺序处理元素,则必须使用迭代器或通过索引访问列表元素。
回答by Mati
I haven't see such use case with streams, so I can not say if it is possible or not. But when I need to use streams with index, I choose IntStream#range(0, table.length)
, and then in lambdas I get the value from this table/list.
我还没有看到流的这种用例,所以我不能说它是否可能。但是当我需要使用带索引的流时,我选择IntStream#range(0, table.length)
,然后在 lambdas 中我从这个表/列表中获取值。
For example
例如
int[] arr = {1,2,3,4};
int result = IntStream.range(0, arr.length)
.map(idx->idx>0 ? arr[idx] + arr[idx-1]:arr[idx])
.sum();
回答by walkeros
By the nature of the stream you don't know the nextelement unless you read it. Therefore directly obtaining the nextelement is not possible when processing current element. However since you are reading currentelement you obiously know what was read before, so to achieve such goal as "accesing previouselement" and "accessing nextelement", you can rely on the history of elements which were already processed.
根据流的性质,除非您阅读它,否则您不知道下一个元素。因此在处理当前元素时无法直接获取下一个元素。但是,由于您正在阅读当前元素,您显然知道之前阅读过什么,因此要实现“访问前一个元素”和“访问下一个元素”这样的目标,您可以依靠已经处理过的元素的历史记录。
Following two solutions are possible for your problem:
您的问题可能有以下两种解决方案:
- Get access to previously read elements. This way you know the currentelement and defined number of previously read elements
- Assume that at the moment of stream processing you read nextelement and that currentelement was read in previous iteration. In other words you consider previously read element as "current" and currently processed element as next(see below).
- 访问以前读取的元素。通过这种方式,您可以了解当前元素和先前读取的元素的定义数量
- 假设在流处理时您读取了下一个元素,而当前元素是在前一次迭代中读取的。换句话说,您将先前读取的元素视为“当前”,将当前处理的元素视为下一个(见下文)。
Solution 1- implemenation
解决方案 1- 实施
First we need a data structure which will allow keeping track of data flowing through the stream. Good choice could be an instance of Queuebecause queues by their nature allows data flowing through them. We only need to bound the queue to the number of last elements we want to know (that would be 3 elements for your use case). For this we create a "bounded" queue keeping history like this:
首先,我们需要一个数据结构,它允许跟踪流过流的数据。一个不错的选择可能是Queue的实例, 因为队列本质上允许数据流过它们。我们只需要将队列绑定到我们想知道的最后一个元素的数量(对于您的用例,这将是 3 个元素)。为此,我们创建了一个“有界”队列,保存历史记录,如下所示:
public class StreamHistory<T> {
private final int numberOfElementsToRemember;
private LinkedList<T> queue = new LinkedList<T>(); // queue will store at most numberOfElementsToRemember
public StreamHistory(int numberOfElementsToRemember) {
this.numberOfElementsToRemember = numberOfElementsToRemember;
}
public StreamHistory save(T curElem) {
if (queue.size() == numberOfElementsToRemember) {
queue.pollLast(); // remove last to keep only requested number of elements
}
queue.offerFirst(curElem);
return this;
}
public LinkedList<T> getLastElements() {
return queue; // or return immutable copy or immutable view on the queue. Depends on what you want.
}
}
The generic parameter T is the type of actual elements of the stream. Method savereturns reference to instance of current StreamHistory for better integration with java Stream api (see below) and it is not really required.
通用参数 T 是流的实际元素的类型。方法save返回对当前 StreamHistory 实例的引用,以便更好地与 java Stream api(见下文)集成,这并不是真正必需的。
Now the only thing to do is to convert the stream of elements to the stream of instances of StreamHistory (where each next element of the stream will hold last ninstances of actual objects going through the stream).
现在唯一要做的就是将元素流转换为 StreamHistory 的实例流(流的每个下一个元素将保存通过流的实际对象的最后n 个实例)。
public class StreamHistoryTest {
public static void main(String[] args) {
Stream<Character> charactersStream = IntStream.range(97, 123).mapToObj(code -> (char) code); // original stream
StreamHistory<Character> streamHistory = new StreamHistory<>(3); // instance of StreamHistory which will store last 3 elements
charactersStream.map(character -> streamHistory.save(character)).forEach(history -> {
history.getLastElements().forEach(System.out::print);
System.out.println();
});
}
}
In above example we first create a stream of all letters in alphabet. Than we create instance of StreamHistory which will be pushed to each iteration of map() call on original stream. Via call to map() we convert to stream containing references to our instance of StreamHistory.
在上面的例子中,我们首先创建了一个由字母表中的所有字母组成的流。然后我们创建 StreamHistory 的实例,它将被推送到原始流上的 map() 调用的每次迭代。通过调用 map() 我们转换为包含对我们的 StreamHistory 实例的引用的流。
Note that each time the data flows through original stream the call to streamHistory.save(character) updates the content of the streamHistory object to reflect current state of the stream.
请注意,每次数据流过原始流时,对 streamHistory.save(character) 的调用都会更新 streamHistory 对象的内容以反映流的当前状态。
Finally in each iteration we print last 3 saved characters. The output of this method is following:
最后在每次迭代中,我们打印最后保存的 3 个字符。该方法的输出如下:
a
ba
cba
dcb
edc
fed
gfe
hgf
ihg
jih
kji
lkj
mlk
nml
onm
pon
qpo
rqp
srq
tsr
uts
vut
wvu
xwv
yxw
zyx
Solution 2- implementation
解决方案 2- 实施
While solution 1 will in most cases do the job and is fairly easy to follow, there are use cases were the possibility to inspect next element and previous is really convenient. In such scenario we are only interested in three element tuples (pevious, current, next) and having only one element does not matter (for simple example consider following riddle: "given a stream of numbers return a tupple of three subsequent numbers which gives the highest sum"). To solve such use cases we might want to have more convenient api than StreamHistory class.
虽然解决方案 1 在大多数情况下可以完成这项工作并且相当容易遵循,但也有一些用例可以检查下一个元素,而前一个元素非常方便。在这种情况下,我们只对三个元素元组(前一个、当前、下一个)感兴趣,并且只有一个元素无关紧要(例如,考虑以下谜语:“给定一个数字流,返回一个由三个后续数字组成的元组,它给出了最高金额”)。为了解决此类用例,我们可能需要比 StreamHistory 类更方便的 api。
For this scenario we introduce a new variation of StreamHistory class (which we call StreamNeighbours). The class will allow to inspect the previousand the nextelement directly. Processing will be done in time "T-1" (that is: the currently processed original element is considered as nextelement, and previously processed original element is considered to be currentelement). This way we, in some sense, inspect one element ahead.
对于这个场景,我们引入了 StreamHistory 类的一个新变体(我们称之为 StreamNeighbours)。该类将允许直接检查上一个和下一个元素。处理将在时间“T-1”完成(即:当前处理的原始元素被认为是下一个元素,之前处理过的原始元素被认为是当前元素)。这样,在某种意义上,我们可以检查前面的一个元素。
The modified class is following:
修改后的类如下:
public class StreamNeighbours<T> {
private LinkedList<T> queue = new LinkedList(); // queue will store one element before current and one after
private boolean threeElementsRead; // at least three items were added - only if we have three items we can inspect "next" and "previous" element
/**
* Allows to handle situation when only one element was read, so technically this instance of StreamNeighbours is not
* yet ready to return next element
*/
public boolean isFirst() {
return queue.size() == 1;
}
/**
* Allows to read first element in case less than tree elements were read, so technically this instance of StreamNeighbours is
* not yet ready to return both next and previous element
* @return
*/
public T getFirst() {
if (isFirst()) {
return queue.getFirst();
} else if (isSecond()) {
return queue.get(1);
} else {
throw new IllegalStateException("Call to getFirst() only possible when one or two elements were added. Call to getCurrent() instead. To inspect the number of elements call to isFirst() or isSecond().");
}
}
/**
* Allows to handle situation when only two element were read, so technically this instance of StreamNeighbours is not
* yet ready to return next element (because we always need 3 elements to have previos and next element)
*/
public boolean isSecond() {
return queue.size() == 2;
}
public T getSecond() {
if (!isSecond()) {
throw new IllegalStateException("Call to getSecond() only possible when one two elements were added. Call to getFirst() or getCurrent() instead.");
}
return queue.getFirst();
}
/**
* Allows to check that this instance of StreamNeighbours is ready to return both next and previous element.
* @return
*/
public boolean areThreeElementsRead() {
return threeElementsRead;
}
public StreamNeighbours<T> addNext(T nextElem) {
if (queue.size() == 3) {
queue.pollLast(); // remove last to keep only three
}
queue.offerFirst(nextElem);
if (!areThreeElementsRead() && queue.size() == 3) {
threeElementsRead = true;
}
return this;
}
public T getCurrent() {
ensureReadyForReading();
return queue.get(1); // current element is always in the middle when three elements were read
}
public T getPrevious() {
if (!isFirst()) {
return queue.getLast();
} else {
throw new IllegalStateException("Unable to read previous element of first element. Call to isFirst() to know if it first element or not.");
}
}
public T getNext() {
ensureReadyForReading();
return queue.getFirst();
}
private void ensureReadyForReading() {
if (!areThreeElementsRead()) {
throw new IllegalStateException("Queue is not threeElementsRead for reading (less than two elements were added). Call to areThreeElementsRead() to know if it's ok to call to getCurrent()");
}
}
}
Now, assuming that three elements were already read, we can directly access currentelement (which is the element going through the stream at time T-1), we can access nextelement (which is the element going at the moment through the stream) and previous(which is the element going through the stream at time T-2):
现在,假设已经读取了三个元素,我们可以直接访问当前元素(即 T-1时刻通过流的元素),我们可以访问下一个元素(即当前通过流的元素)和前一个(这是在时间 T-2 时通过流的元素):
public class StreamTest {
public static void main(String[] args) {
Stream<Character> charactersStream = IntStream.range(97, 123).mapToObj(code -> (char) code);
StreamNeighbours<Character> streamNeighbours = new StreamNeighbours<Character>();
charactersStream.map(character -> streamNeighbours.addNext(character)).forEach(neighbours -> {
// NOTE: if you want to have access the values before instance of StreamNeighbours is ready to serve three elements
// you can use belows methods like isFirst() -> getFirst(), isSecond() -> getSecond()
//
// if (curNeighbours.isFirst()) {
// Character currentChar = curNeighbours.getFirst();
// System.out.println("???" + " " + currentChar + " " + "???");
// } else if (curNeighbours.isSecond()) {
// Character currentChar = curNeighbours.getSecond();
// System.out.println(String.valueOf(curNeighbours.getFirst()) + " " + currentChar + " " + "???");
//
// }
//
// OTHERWISE: you are only interested in tupples consisting of three elements, so three elements needed to be read
if (neighbours.areThreeElementsRead()) {
System.out.println(neighbours.getPrevious() + " " + neighbours.getCurrent() + " " + neighbours.getNext());
}
});
}
}
The output of this is following:
其输出如下:
a b c
b c d
c d e
d e f
e f g
f g h
g h i
h i j
i j k
j k l
k l m
l m n
m n o
n o p
o p q
p q r
q r s
r s t
s t u
t u v
u v w
v w x
w x y
x y z
By StreamNeighbours class it is easier to track the previous/next element (because we have method with appropriate names), while in StreamHistory class this is more cumbersome since we need to manually "reverse" the order of the queue to achieve this.
通过 StreamNeighbours 类更容易跟踪上一个/下一个元素(因为我们有具有适当名称的方法),而在 StreamHistory 类中这更麻烦,因为我们需要手动“反转”队列的顺序来实现这一点。
回答by sidgate
Not exactly a Java solution, but possible to get next element in Kotlin with zip
functions. Here is what I came up with functions. Looks much cleaner
不完全是 Java 解决方案,但可以使用zip
函数在 Kotlin 中获取下一个元素。这是我想出的功能。看起来干净多了
enum class RomanNumeral(private val value: Int) {
I(1), V(5), X(10), L(50), C(100), D(500), M(1000);
operator fun minus(other: RomanNumeral): Int = value - other.value
operator fun plus(num: Int): Int = num + value
companion object {
fun toRoman(ch: Char): RomanNumeral = valueOf(ch.toString())
}
}
fun toNumber(roman: String): Int {
return roman.map { RomanNumeral.toRoman(it) }
.zipWithNext()
.foldIndexed(0) { i, currentVal, (num1, num2) ->
when {
num1 < num2 -> num2 - num1 + currentVal
i == roman.length - 2 -> num1 + (num2 + currentVal)
else -> num1 + currentVal
}
}
}