首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java :有没有一种方法可以每次迭代两个元素而不是一个元素?

Java :有没有一种方法可以每次迭代两个元素而不是一个元素?
EN

Stack Overflow用户
提问于 2015-12-04 10:43:47
回答 4查看 33.5K关注 0票数 28

假设我们有这条小溪

代码语言:javascript
复制
Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j");

我想在一个映射中保存第一个字符串以"err“开头的相邻字符串的配对。

我想的是这样的事情

代码语言:javascript
复制
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 "";
});

但我对此并不完全满意有两个主要原因

  1. 我在“滥用”reduce函数。在Stream中,每个函数都有其明确、明确的目的:max要计算最大值,filter要根据条件进行过滤,reduce要产生增量累加值等等。
  2. 这样做可以防止我使用流强大的机制:如果我想将搜索限制在前两个结果上怎么办?

在这里,我使用了reduce,因为(据我所知)它是唯一允许您比较两个值的函数,您可以以某种方式返回类似于“当前值”和“下一个值”概念的值。

有没有更直截了当的方法?允许您在每次迭代中考虑多个值的情况下迭代流?

编辑

我想的是一些机制,给定当前的元素,允许您为每次迭代定义一个“元素窗口”。

有点像

代码语言:javascript
复制
<R> Stream<R> mapMoreThanOne(
    int elementsBeforeCurrent,
    int elementsAfterCurrent,
    Function<List<? super T>, ? extends R> mapper);

而不是

代码语言:javascript
复制
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

这将是对当前API的强大“升级”。

EDIT2

我赞赏人们提出解决方案的努力,但问题不在于算法本身。通过将流、索引、临时变量放在一起来存储以前的值,有不同的方法来实现我的目标。但是我想知道在Stream中是否有一些方法是为处理当前以外的元素而设计的,而不破坏“流范式”。就像这样

代码语言:javascript
复制
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());

给出答案,我认为没有“清晰和快速”的解决方案,除非使用StreamEx库

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-12-04 11:07:44

您可以为此任务构建自定义Collector

代码语言:javascript
复制
Map<String, String> map = 
    Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j")
          .collect(MappingErrors.collector());

通过以下方式:

代码语言:javascript
复制
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);
    }

}

在此收集器中,保留两个正在运行的元素。每次接受String时,都会更新它们,如果第一个元素以"err"开头,则将这两个元素添加到映射中。

另一种解决方案是使用StreamEx库,它提供了一个pairMap方法,该方法将给定的函数应用于该流的每一对相邻元素。在下面的代码中,如果第一个元素以"err"开头,则操作返回由对的第一个和第二个元素组成的字符串数组,否则为null。然后过滤掉null元素,并将流收集到一个映射中。

代码语言:javascript
复制
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);
票数 22
EN

Stack Overflow用户

发布于 2015-12-04 12:21:08

如果您的输入位于随机访问列表中,情况会更容易。这样,您就可以利用好的老List.subList方法:

代码语言:javascript
复制
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)));

同样的事情也可以用前面提到的StreamEx库(由我编写)以更短的方式完成:

代码语言:javascript
复制
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();

虽然如果您不希望第三方依赖,但糟糕的Stream解决方案看起来也不是很糟糕。

票数 7
EN

Stack Overflow用户

发布于 2019-11-26 11:38:50

另一种以Collector.ofList<List<String>>为结构来采集对的方法。先收集到List<List<String>>

代码语言:javascript
复制
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(); }
                )
        );

然后它可以被转换成地图。

代码语言:javascript
复制
Map<String, String> toMap = collect.stream().filter(l -> l.size() == 2)
        .collect(Collectors.toMap(
                e -> e.get(0),
                e -> e.get(1))
        );

或者与Collectors.collectingAndThen融为一体

代码语言:javascript
复制
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))
                        )
        ));
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34086461

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档