2015-03-23 16 views
16

Ich versuche, den folgenden Code zu Lambda-Ausdrücke mit `Strom Refactoring, vor allem der verschachtelten foreach-Schleifen:Wie verschachtelte Listen mit Lambda-Streams iterieren?

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; 
} 

Ich beginne mit:

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

Sie auf das erste Element direkt Rückkehr - 'Parse (sndNode);'. Nach welchen Kriterien wird mit dem nächsten Element fortgefahren? Was für ein Spiel macht es? –

+0

Ich gebe das erste Element zurück, das einer Bedingung entspricht und keinen Analysefehler aufweist. – membersound

+2

Ich würde die Liste mit 'flatMap()' abflachen, dann eine 'map()' -Operation anwenden, um das Parsing durchzuführen (null bei fehlgeschlagenem Erfolg), 'filter()' die Nullen und schließlich 'findFirst()' zurückzugeben der erste analysierte Wert Und ich würde das Werfen der letzten Ausnahme aufgeben (warum die letzte? Warum nicht die erste oder die zweite?), Da es sowieso nur Verwirrung stiftet. – biziclop

Antwort

9

Ich habe Angst, dass mit Streams und Lambdas, Ihre Leistung leiden kann. Ihre aktuelle Lösung gibt den ersten gültigen und analysierbaren Knoten zurück. Es ist jedoch nicht möglich, eine Operation im Stream wie for-each (source) zu unterbrechen.

Da Sie zwei verschiedene Ausgaben haben können (zurückgegebenes Ergebnis oder geworfene Ausnahme), ist dies nicht mit einem einzelnen Zeilenausdruck möglich.

Hier ist, was ich gefunden habe. Es kann Ihnen ein paar Ideen:

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; 
} 

Wie eingangs erwähnt, gibt es möglich, Leistungsproblem ist als dies versuchen wird alle gültigen Knoten zu analysieren.


EDIT:

Um zu vermeiden, alle Knoten Parsen, könnten Sie reduce verwenden, aber es ist ein wenig komplexer und hässlich (und Extraklasse ist erforderlich). Dies zeigt auch alle ParseException s statt nur die letzte.

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:

Im Allgemeinen ist es auch möglich faul Auswertung zu verwenden, wenn Terminalbetreiber wie findFirst() verwenden. Mit einer geringfügigen Änderung der Anforderungen (d. H. Rückgabe von null anstelle des Auslösens einer Ausnahme) sollte es möglich sein, etwas wie unten zu tun. flatMap mit findFirst verwendet jedoch keine faule Auswertung (source), daher versucht dieser Code, alle Knoten zu analysieren.

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; 
} 
+0

Das ist toll, danke! Könnte dies mit 'findFirst()' optimiert werden, um nicht jeden gültigen Knoten analysieren zu müssen? – membersound

+2

@membersound Ich habe meine Antwort bearbeitet und eine andere Lösung hinzugefügt - ein bisschen hässlicher, aber analysiert nicht alle Knoten. Wie jemand vorgeschlagen hat, ist es ein wenig unklar, warum es die letzte 'ParseException' ausgelöst hat, also habe ich es auch behoben - jetzt sehen Sie alle' ParseException', wenn es kein 'Result' gibt. –

+2

Ich denke, dies zeigt gut, warum die Verwendung von Streams nicht immer die beste Lösung ist. :) – biziclop

15

Blick auf flatMap:

flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Gibt einen Stream zurück, der aus den Ergebnissen des Ersetzens jedes Elements besteht dieses Stroms mit dem Inhalt eines abgebildeten Stroms, der von erzeugt wird, wobei die bereitgestellte Zuordnungsfunktion auf jedes Element angewendet wird.

Codebeispiel isValid() unter der Annahme, wirft nicht

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; 
    } 
} 
+4

Einige weitere Details zur Lösung wären hilfreich. – biziclop

+1

@biziclop danke für den Schnitt. Sapienti saß da. – Arkadiy

+0

Ein Codebeispiel wurde hinzugefügt. Ich bin mir sicher, dass jemand herausfinden konnte, wie man versucht, die Logik im Stream zu fangen und zurückzugeben, um die Lösung zu einer einzigen Zeile zu machen. – TealFawn

2

Try map zu verwenden, die die ursprüngliche Quelle zu verwandeln.

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

verwaltet 'stream()' die Reihenfolge der ursprünglichen 'FirstNodes' Liste? Ich muss es in der Reihenfolge durchlaufen, in der es in der "Antwort" enthalten ist, deshalb habe ich mit "element.forEach" begonnen ...? – membersound

+0

Keine Streams versichern Ihnen nicht, welche Bestellelemente verarbeitet werden –

0

Man könnte diese Tatsache nutzen, dass StreamSupport eine stream Methode bereitstellt, die eine Spliterator und Iterable hat eine spliterator Methode verwendet.

Sie brauchen dann nur einen Mechanismus, um Ihre Struktur in eine Iterable zu glätten - so etwas.

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

Sie können jetzt direkt von Ihrem Iterable streamen.

Erste Untersuchungen deuten darauf hin, dass dies mit flatMap aber was auch immer getan werden sollte.

0

können Sie Schleifen unter

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