簡體   English   中英

如何使用lambda流迭代嵌套列表?

[英]How to iterate nested lists with lambda streams?

我正在嘗試使用`stream將以下代碼重構為lambda表達式,尤其是嵌套的foreach循環:

public static Result match (Response rsp) {
    Exception lastex = null;

    for (FirstNode firstNode : rsp.getFirstNodes()) {
        for (SndNode sndNode : firstNode.getSndNodes()) {
            try {
                if (sndNode.isValid())
                return parse(sndNode); //return the first match, retry if fails with ParseException
            } catch (ParseException e) {
                lastex = e;
            }
        }
    }

    //throw the exception if all elements failed
    if (lastex != null) {
        throw lastex;
    }

    return null;
}

我開始:

rsp.getFirstNodes().forEach().?? // how to iterate the nested 2ndNodes?

看看flatMap:

flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
返回一個流,該流包含將此流的每個元素替換為通過將提供的映射函數應用於每個元素而生成的映射流的內容的結果。

代碼示例假設isValid()不拋出

Optional<SndNode> sndNode = rsp.getFirstNodes()
  .stream()
  .flatMap(firstNode -> firstNode.getSndNodes().stream())  //This is the key line for merging the nested streams
  .filter(sndNode -> sndNode.isValid())
  .findFirst();

if (sndNode.isPresent()) {
    try {
        parse(sndNode.get());
    } catch (ParseException e) {
        lastex = e;
    }
}

我擔心使用溪流和lambdas,你的表現可能會受到影響。 您當前的解決方案返回第一個有效且可解析的節點,但是無法中斷流上的操作,例如for-each( )。

此外,因為您可以有兩個不同的輸出(返回結果或拋出異常),所以不可能使用單行表達式執行此操作。

這就是我想出的。 它可能會給你一些想法:

public static Result match(Response rsp) throws Exception {
    Map<Boolean, List<Object>> collect = rsp.getFirstNodes().stream()
            .flatMap(firstNode -> firstNode.getSndNodes().stream()) // create stream of SndNodes
            .filter(SndNode::isValid) // filter so we only have valid nodes
            .map(node -> {
                // try to parse each node and return either the result or the exception
                try {
                    return parse(node);
                } catch (ParseException e) {
                    return e;
                }
            }) // at this point we have stream of objects which may be either Result or ParseException
            .collect(Collectors.partitioningBy(o -> o instanceof Result)); // split the stream into two lists - one containing Results, the other containing ParseExceptions

    if (!collect.get(true).isEmpty()) {
        return (Result) collect.get(true).get(0);
    }
    if (!collect.get(false).isEmpty()) {
        throw (Exception) collect.get(false).get(0); // throws first exception instead of last!
    }
    return null;
}

正如開頭所提到的,可能存在性能問題,因為這會嘗試解析每個有效節點


編輯:

為了避免解析所有節點,你可以使用reduce ,但它更復雜和丑陋(需要額外的類)。 這也顯示了所有ParseException而不是最后一個。

private static class IntermediateResult {

    private final SndNode node;
    private final Result result;
    private final List<ParseException> exceptions;

    private IntermediateResult(SndNode node, Result result, List<ParseException> exceptions) {
        this.node = node;
        this.result = result;
        this.exceptions = exceptions;
    }

    private Result getResult() throws ParseException {
        if (result != null) {
            return result;
        }
        if (exceptions.isEmpty()) {
            return null;
        }
        // this will show all ParseExceptions instead of just last one
        ParseException exception = new ParseException(String.format("None of %s valid nodes could be parsed", exceptions.size()));
        exceptions.stream().forEach(exception::addSuppressed);
        throw exception;
    }

}

public static Result match(Response rsp) throws Exception {
    return Stream.concat(
                    Arrays.stream(new SndNode[] {null}), // adding null at the beginning of the stream to get an empty "aggregatedResult" at the beginning of the stream
                    rsp.getFirstNodes().stream()
                            .flatMap(firstNode -> firstNode.getSndNodes().stream())
                            .filter(SndNode::isValid)
            )
            .map(node -> new IntermediateResult(node, null, Collections.<ParseException>emptyList()))
            .reduce((aggregatedResult, next) -> {
                if (aggregatedResult.result != null) {
                    return aggregatedResult;
                }

                try {
                    return new IntermediateResult(null, parse(next.node), null);
                } catch (ParseException e) {
                    List<ParseException> exceptions = new ArrayList<>(aggregatedResult.exceptions);
                    exceptions.add(e);
                    return new IntermediateResult(null, null, Collections.unmodifiableList(exceptions));
                }
            })
            .get() // aggregatedResult after going through the whole stream, there will always be at least one because we added one at the beginning
            .getResult(); // return Result, null (if no valid nodes) or throw ParseException
}

EDIT2:

通常,在使用諸如findFirst()類的終端運算符時,也可以使用延遲求值。 因此,通過稍微改變需求(即返回null而不是拋出異常),應該可以執行類似下面的操作。 但是,使用findFirst flatMap不使用延遲評估( ),因此此代碼嘗試解析所有節點。

private static class ParsedNode {
    private final Result result;

    private ParsedNode(Result result) {
        this.result = result;
    }
}

public static Result match(Response rsp) throws Exception {
    return rsp.getFirstNodes().stream()
            .flatMap(firstNode -> firstNode.getSndNodes().stream())
            .filter(SndNode::isValid)
            .map(node -> {
                try {
                    // will parse all nodes because of flatMap
                    return new ParsedNode(parse(node));
                } catch (ParseException e ) {
                    return new ParsedNode(null);
                }
            })
            .filter(parsedNode -> parsedNode.result != null)
            .findFirst().orElse(new ParsedNode(null)).result;
}

嘗試使用轉換原始源的map

   rsp.getFirstNodes().stream().map(FirstNode::getSndNodes)
               .filter(sndNode-> sndNode.isValid())
               .forEach(sndNode->{
   // No do the sndNode parsing operation Here.
   })

您可以迭代嵌套循環,如下所示

allAssessmentsForJob.getBody().stream().forEach(assessment -> {
        jobAssessments.stream().forEach(jobAssessment -> {
            if (assessment.getId() == jobAssessment.getAssessmentId()) {
                jobAssessment.setAssessment(assessment);
            }
        });
    });

有點晚了,但這是一個可讀的方法:

   Result = rsp.getFirstNodes()
        .stream()
        .flatMap(firstNode -> firstNode.getSndNodes.stream())
        .filter(secondNode::isValid))
        .findFirst()
        .map(node -> this.parseNode(node)).orElse(null);

說明 :獲取所有firstNodesstream()它們。 輸出每個firstNode,你帶來n個SndNodes 您檢查每個SndNodes以查找找到一個有效的 SndNodes 如果沒有有效的SndNode,那么我們將得到一個null。 如果有,它將被解析為Result

parseMethod()不會改變原始:

public Result parseNode(SndNode node){
        try {
        ...
        ... // attempt to parsed node 
    } catch (ParseException e) {
        throw new ParseException;
    }   
} 

您可以使用這一事實, StreamSupport提供stream方法,需要一個SpliteratorIterablespliterator方法。

然后,您只需要一種機制將您的結構展平為Iterable - 就像這樣。

class IterableIterable<T> implements Iterable<T> {

    private final Iterable<? extends Iterable<T>> i;

    public IterableIterable(Iterable<? extends Iterable<T>> i) {
        this.i = i;
    }

    @Override
    public Iterator<T> iterator() {
        return new IIT();
    }

    private class IIT implements Iterator<T> {

        // Pull an iterator.
        final Iterator<? extends Iterable<T>> iit = i.iterator();
        // The current Iterator<T>
        Iterator<T> it = null;
        // The current T.
        T next = null;

        @Override
        public boolean hasNext() {
            boolean finished = false;
            while (next == null && !finished) {
                if (it == null || !it.hasNext()) {
                    if (iit.hasNext()) {
                        it = iit.next().iterator();
                    } else {
                        finished = true;
                    }
                }
                if (it != null && it.hasNext()) {
                    next = it.next();
                }
            }
            return next != null;
        }

        @Override
        public T next() {
            T n = next;
            next = null;
            return n;
        }
    }

}

public void test() {
    List<List<String>> list = new ArrayList<>();
    List<String> first = new ArrayList<>();
    first.add("First One");
    first.add("First Two");
    List<String> second = new ArrayList<>();
    second.add("Second One");
    second.add("Second Two");
    list.add(first);
    list.add(second);
    // Check it works.
    IterableIterable<String> l = new IterableIterable<>(list);
    for (String s : l) {
        System.out.println(s);
    }
    // Stream it like this.
    Stream<String> stream = StreamSupport.stream(l.spliterator(), false);
}

您現在可以直接從您的Iterable流式傳輸。

最初的研究表明,這應該用flatMap完成,但無論如何。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM