是否有可能在Stream中获取下一个元素?

我正在尝试将for循环转换为function代码。 我需要向前看一个值,并且还要看一个值。 是否可以使用流? 以下代码是将罗马文本转换为数值。 不确定带有两个/三个参数的reduce方法是否有帮助。

int previousCharValue = 0; int total = 0; for (int i = 0; i  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(); } } } 

不,这不可能使用流,至少不容易。 流API从处理元素的顺序中抽象出来:流可以并行处理,或者以相反的顺序处理。 因此,流抽象中不存在“下一个元素”和“前一个元素”。

您应该使用最适合作业的API:如果您需要对集合的所有元素应用某些操作并且您对订单不感兴趣,则流非常好。 如果需要按特定顺序处理元素,则必须使用迭代器或通过索引访问列表元素。

我没有看到这样的用例流,所以我不能说是否可能。 但是当我需要使用带索引的流时,我选择IntStream#range(0, table.length) ,然后在lambdas中我从这个表/列表中获取值。

例如

  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(); 

根据流的性质,除非您阅读它,否则您不知道下一个元素。 因此,在处理当前元素时,不可能直接获得下一个元素。 但是,由于您正在阅读当前元素,因此您需要知道之前读过的内容,因此要实现“访问前一个元素”和“访问下一个元素”这样的目标,您可以依赖已经处理过的元素的历史记录。

以下两种解决方案可以解决您的问题:

  1. 访问以前读取的元素。 这样您就可以知道当前元素和先前读取元素的已定义数量
  2. 假设在流处理时您读取下一个元素,并且在前一次迭代中读取了当前元素。 换句话说,您认为先前读取的元素为“当前”,当前处理的元素为下一个 (见下文)。

解决方案1 – 实施

首先,我们需要一个数据结构,以便跟踪流经流的数据。 好的选择可能是Queue的一个实例,因为队列本质上允许数据流过它们。 我们只需要将队列绑定到我们想要知道的最后一个元素的数量(对于您的用例,这将是3个元素)。 为此我们创建了一个“有界”的队列,保存这样的历史记录:

 public class StreamHistory { private final int numberOfElementsToRemember; private LinkedList queue = new LinkedList(); // 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 getLastElements() { return queue; // or return immutable copy or immutable view on the queue. Depends on what you want. } } 

通用参数T是流的实际元素的类型。 方法save返回对当前StreamHistory实例的引用,以便更好地与java Stream api集成(见下文),并不是真正需要它。

现在唯一要做的就是将元素流转换为StreamHistory实例流(其中流的每个下一个元素将保存通过流的实际对象的最后n个实例)。

 public class StreamHistoryTest { public static void main(String[] args) { Stream charactersStream = IntStream.range(97, 123).mapToObj(code -> (char) code); // original stream StreamHistory 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(); }); } } 

在上面的例子中,我们首先创建一个字母表中所有字母的流。 然后我们创建StreamHistory的实例,它将被推送到原始流上的map()调用的每次迭代。 通过调用map(),我们转换为包含对StreamHistory实例的引用的流。

请注意,每次数据流经原始流时,对streamHistory.save(字符)的调用都会更新streamHistory对象的内容以反映流的当前状态。

最后,在每次迭代中,我们打印最后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 

解决方案2 – 实施

虽然解决方案1在大多数情况下都可以完成工作并且相当容易理解,但是有一些用例可以检查下一个元素,而之前的元素非常方便。 在这种情况下,我们只对三个元素元组感兴趣(pevious,current,next)并且只有一个元素无关紧要(简单的例子考虑下面的谜语:“给定一个数字流返回一个由三个后续数字组成的tupple,它给出了最高金额“)。 要解决此类用例,我们可能希望比StreamHistory类具有更方便的API。

对于这种情况,我们引入了StreamHistory类的新变体(我们称之为StreamNeighbours)。 该类将允许直接检查上一个下一个元素。 处理将在时间“T-1”中完成(即:当前处理的原始元素被认为是下一个元素,并且先前处理的原始元素被认为是当前元素)。 通过这种方式,我们可以检查前面的一个元素。

修改后的类如下:

 public class StreamNeighbours { private LinkedList 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 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()"); } } } 

现在,假设已经读取了三个元素,我们可以直接访问当前元素(这是在时间T-1流经流的元素),我们可以访问下一个元素(这是当前通过流的元素)和之前 (在时间T-2通过流的元素):

 public class StreamTest { public static void main(String[] args) { Stream charactersStream = IntStream.range(97, 123).mapToObj(code -> (char) code); StreamNeighbours streamNeighbours = new StreamNeighbours(); 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()); } }); } } 

这个输出如下:

 abc bcd cde def efg fgh ghi hij ijk jkl klm lmn mno nop opq pqr qrs rst stu tuv uvw vwx wxy xyz 

通过StreamNeighbours类,可以更容易地跟踪上一个/下一个元素(因为我们有适当名称的方法),而在StreamHistory类中这更麻烦,因为我们需要手动“反转”队列的顺序来实现这一点。