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

sid*_*ate 17 java java-8

我正在尝试将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();
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

Hoo*_*pje 16

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

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


wal*_*ros 6

根据流的性质,除非您阅读它,否则您不知道下一个元素。因此在处理当前元素时无法直接获取下一个元素。但是,由于您正在阅读当前元素,您显然知道之前阅读了什么,因此要实现“访问前一个元素”和“访问下一个元素”等目标,您可以依赖已处理元素的历史记录。

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

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

解决方案 1 - 实施

首先,我们需要一个数据结构,它允许跟踪流过流的数据。一个不错的选择可能是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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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

现在唯一要做的就是将元素流转换为 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();
    });

  }

}
Run Code Online (Sandbox Code Playgroud)

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

请注意,每次数据流过原始流时,对 streamHistory.save(character) 的调用都会更新 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
Run Code Online (Sandbox Code Playgroud)

解决方案 2 - 实施

虽然解决方案 1 在大多数情况下可以完成这项工作并且相当容易遵循,但也有一些用例可以检查下一个元素,而前一个元素非常方便。在这种情况下,我们只对三个元素元组(前一个、当前、下一个)感兴趣,并且只有一个元素无关紧要(例如,考虑以下谜语:“给定一串数字,返回三个后续数字的元组,这给出了最高金额”)。为了解决此类用例,我们可能需要比 StreamHistory 类更方便的 api。

对于这个场景,我们引入了 StreamHistory 类的一个新变体(我们称之为 StreamNeighbours)。该类将允许直接检查上一个下一个元素。处理将在时间“T-1”完成(即:当前处理的原始元素被视为下一个元素,而先前处理的原始元素被视为当前元素)。这样,在某种意义上,我们可以检查前面的一个元素。

修改后的类如下:

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

}
Run Code Online (Sandbox Code Playgroud)

现在,假设已经读取了三个元素,我们可以直接访问当前元素(即 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());
      }
    });

  }

}
Run Code Online (Sandbox Code Playgroud)

其输出如下:

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
Run Code Online (Sandbox Code Playgroud)

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


Mat*_*ati 5

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

例如

    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();
Run Code Online (Sandbox Code Playgroud)