在 Foreach Lambda 中使用上一个元素的 Java 流

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39289213/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 04:14:52  来源:igfitidea点击:

Java Stream Using Previous Element in Foreach Lambda

javalambda

提问by Panciz

I have a list of numbers with some 0sinside. Since 0means invalid measure in my situation, I need change the 0valued element with the first non 0 element that I can find in the previous positions.

我有一个数字列表,0s里面有一些。由于0在我的情况下意味着无效的度量,我需要0使用我可以在以前的位置找到的第一个非 0 元素更改值元素。

For example the list

例如列表

45 55 0 0 46 0 39 0 0 0

must become

必须成为

45 55 55 55 46 46 39 39 39 39

This is the implementation using the classic for each

这是使用经典的实现 for each

      int lastNonZeroVal = 0;
        for (MntrRoastDVO vo : res) {
            if (vo.getValColor() > 0) {
                lastNonZeroVal = vo.getValColor();
            } else {
                vo.setValColor(lastNonZeroVal);
            }
        }

Is there a way to implement this with the Java Streams and Lambda Functions?

有没有办法用 Java Streams 和 Lambda 函数来实现这一点?

Since I know that I must not change the source of the stream in the foreach lambda, actually the list is a list of object and I do not change the element of the list but I just assign new values.

因为我知道我不能在 foreach lambda 中更改流的源,实际上列表是一个对象列表,我不更改列表的元素,但我只是分配新值。

This was my first solution

这是我的第一个解决方案

int lastNonZeroVal = 1;
resutl.stream().forEach(vo -> {
        if(vo.getValColor()>0){
            lastNonZeroVal=vo.getValColor();
        }else{
            vo.setValColor(lastNonZeroVal);
        }
});

But I also read here

但我也在这里阅读

It's best if the lambdas passed to stream operations are entirely side effect free. that is, that they don't mutate any heapbased state or perform any I/O during their execution.

最好是传递给流操作的 lambda 表达式完全没有副作用。也就是说,它们在执行期间不会改变任何基于堆的状态或执行任何 I/O。

This is what is worryng me

这就是我所担心的

the data is partitioned, there's no guarantee that when a given element is processed, all elements preceding that element were already processed.

数据已分区,无法保证在处理给定元素时,该元素之前的所有元素都已处理。

Can this solution produce invalid results, maybe when the number of elements in the list are high? ? Event if I do not use parallelStream()?

这个解决方案是否会产生无效的结果,也许当列表中的元素数量很高时?? 如果我不使用事件parallelStream()

采纳答案by Sergei Rybalkin

You can modify state of objects inside stream. But you can not modify data source state.

您可以修改流内对象的状态。但是您不能修改数据源状态。

The problem is the same as in classic iterating.

问题与经典迭代中的问题相同。

Use forEachOrdered()to perform

使用forEachOrdered()执行

an action for each element of this stream, in the encounter order of the stream if the stream has a defined encounter order

此流的每个元素的操作,如果流具有定义的遇到顺序,则按照流的遇到顺序

If you call

如果你打电话

result.stream.forEachOrdered(...)

all elements will be processed sequentially in order.

所有元素将按顺序依次处理。

For sequential streams forEachseems to respect the order.

对于顺序流forEach似乎尊重顺序。

回答by Journeycorner

It's best if the lambdas passed to stream operations are entirely side effect free. that is, that they don't mutate any heapbased state or perform any I/O during their execution.

最好是传递给流操作的 lambda 表达式完全没有副作用。也就是说,它们在执行期间不会改变任何基于堆的状态或执行任何 I/O。

Your solution does infact have a side effect, it changes your source list to a resource list. To avoid that, you need the map operator and transform your stream to a Collection. Because you can not access the previous element the state must be stored outside in a final field. For reasons of brevity I used Integer instead of your object:

您的解决方案确实有副作用,它将您的源列表更改为资源列表。为了避免这种情况,您需要 map 运算符并将您的流转换为集合。因为您无法访问前一个元素,所以状态必须存储在最终字段中。为简洁起见,我使用 Integer 而不是您的对象:

List<Integer> sourceList = Arrays.asList(45, 55, 0, 0, 46, 0, 39, 0, 0, 0);

final Integer[] lastNonZero = new Integer[1]; // stream has no state, so we need a final field to store it
List<Integer> resultList = sourceList.stream()
             .peek(integer -> {
                 if (integer != 0) {
                     lastNonZero[0] = integer;
                 }
             })
             .map(integer -> lastNonZero[0])
             .collect(Collectors.toList());

System.out.println(sourceList); // still the same
System.out.println(resultList); // prints [45, 55, 55, 55, 46, 46, 39, 39, 39, 39]

Using a stream for your problem is not the best solution, unless you need some additional operations like filter, other map operations or sort.

使用流来解决您的问题并不是最好的解决方案,除非您需要一些额外的操作,例如过滤器、其他映射操作或排序。

回答by bsyk

There is a way to do this with just stream functions, although it's not particularly clean in my opinion. You could create your own collector to default to the last entry in the list if the current entry is zero. Something like this:

有一种方法可以仅使用流函数来做到这一点,尽管在我看来它并不是特别干净。如果当前条目为零,您可以创建自己的收集器以默认为列表中的最后一个条目。像这样的东西:

void AddOrLast(List<Integer> list, Integer value) {
    Integer toAdd = 0;
    if (value != 0) {
        toAdd = value;
    } else {
        if (!list.isEmpty()) {
            toAdd = list.get(list.size() - 1);
        }
    }
    list.add(toAdd);
}

@Test
public void name() {
    List<Integer> nums = Arrays.asList(45, 55, 0, 0, 46, 0, 39, 0, 0, 0);

    Collector<Integer, ArrayList<Integer>, List<Integer>> nonZeroRepeatCollector =
            Collector.of(
                    ArrayList::new,
                    this::AddOrLast,
                    (list1, list2) -> { list1.addAll(list2); return list1; },
                    (x) -> x);

    List<Integer> collect = nums.stream().collect(nonZeroRepeatCollector);
    System.out.println(collect);
    // OUT: [45, 55, 55, 55, 46, 46, 39, 39, 39, 39]
}

The AddOrLastmethod will add the current value if non-zero, otherwise the last entry from the array we're building.

AddOrLast如果非零,该方法将添加当前值,否则添加我们正在构建的数组中的最后一个条目。

The nonZeroRepeatCollectoruses the supplier, accumulator, combiner, finisher pattern.

nonZeroRepeatCollector使用的供应商,累加器,组合器,装订模式。

  • The supplier initialized the returned object. (Our ArrayList)
  • The accumulator updated the returned object with the new value. (list.add)
  • The combiner is used in the case where the stream was split and needs to be rejoined, in parallel stream for example. (This won't be called in our case)
  • The finished is a final action to complete the collection. In our case, just return the ArrayList.
  • 供应商初始化了返回的对象。(我们的 ArrayList)
  • 累加器用新值更新返回的对象。(list.add)
  • 合并器用于流被拆分并需要重新加入的情况,例如在并行流中。(在我们的例子中不会被调用)
  • 完成是完成集合的最后一个动作。在我们的例子中,只返回 ArrayList。

回答by Renan Paul Blanco

First off, you shouldn't be mutating state within a lambda. That said, you could use a custom list extending ArrayListand override the iterator()and spliterator()methods.

首先,您不应该在 lambda 中改变状态。也就是说,您可以使用自定义列表扩展ArrayList和覆盖iterator()spliterator()方法。

Note that this is using a Pairclass which I've omitted here for brevity.

请注意,这是使用一个类,为简洁起见,我在此处省略Pair该类。

public class MemoList extends ArrayList<Pair<Integer,MntrRoastDVO>> {
    private static final long serialVersionUID = -2816896625889263498L;

    private final List<MntrRoastDVO> list;

    private MemoList(List<MntrRoastDVO> list) {
        this.list = list;
    }

    public static MemoList of(List<MntrRoastDVO> list) {
        return new MemoList(Objects.requireNonNull(list));
    }

    @Override
    public Iterator<Pair<Integer,MntrRoastDVO>> iterator() {
        Iterator<MntrRoastDVO> it = list.iterator();

        return new Iterator<Pair<Integer,MntrRoastDVO>>() {
            private Integer previous = null;

            @Override
            public boolean hasNext() {
                return it.hasNext();
            }

            @Override
            public Pair<Integer,MntrRoastDVO> next() {
                MntrRoastDVO next = it.next();
                Pair<Integer,MntrRoastDVO> pair = new Pair<>(previous, next);

                if (next.getValColor() > 0) {
                    previous = next.getValColor();
                }

                return pair;
            }

        };
    }

    @Override
    public Spliterator<Pair<Integer,MntrRoastDVO>> spliterator() {
        return Spliterators.spliterator(iterator(), list.size(), Spliterator.SIZED);
    }
}

I would then use it like this.

然后我会像这样使用它。

public void doWork(List<MntrRoastDVO> res)
{
    MemoList.of(res).stream().forEach(this::setData);
}

private void setData(Pair<Integer,MntrRoastDVO> pair)
{
    MntrRoastDVO data = pair.two();

    if (data.getValColor() <= 0)
    {
        data.setValColor(pair.one());
    }
}

Note that this is not tested using a parallel stream. In fact, I'm almost certain it wouldn't work in parallel.

请注意,这不是使用并行流测试的。事实上,我几乎可以肯定它不会并行工作。