简体   繁体   中英

Using Java 8 Streams to accomplish removing loops

I have a list of documents objects that need to be mapped based on certain criteria. There is a utility function that takes any 2 document types and determines if they match on a number of criteria, like genre of document, whether they share any authors etc. The code works but I';d like to use Java Streams to solve it if possible.

I currently solve this by using the following code:

  class Document{
     private String genre;
     private List<Author> authors;
     private String isbn;
     private boolean paperBack;
     ...
  }

I also use a library utility that has a function that returns true given a series of matching criteria and a pair of documents. It simply returns a boolean.

   boolean matchesOnCriteria(Document doc1, Document doc2, Criteria criteria){
       ...
   }

Here is the matching code for finding the books that match on the provided criteria

     DocumentUtils utils = DocumentUitls.instance();
     Criteria userCriteria = ...
     List<Pair> matches = new ArrayList<>();

    List<Document> documents = entityManager.findBy(.....);

   for(Document doc1 : documents){
      for(Documents doc2 : documents){
         if(!doc1.equals(doc2){
             if (utils.matchesOnCriteria(doc1,doc2, userCriteria)) {
              Pair<Document> p = new Pair(doc1,doc2);
               if(!matches.contains(p)){
                 matches.add(p);
               }
             }
           }
          } 
        }  
     }

How can I do this using Streams?

The idea of the following solution using Steam::reduce is simple:

  1. Group the qualified pairs of documents to Map<Document, List<Document>> having all possible acceptable combinations. Let's say odd and even documents are in pairs:

     D1=[D3, D5], D2=[D4], D3=[D1, D5], D4=[D2], D5[D1, D3] // dont mind the duplicates
  2. Using Stream::reduce you can achieve the following steps:

    • Transform entries to Pair<> ,

       D1-D3, D1-D5, D2-D4, D3-D1, D1-D5, D4-D2, D5-D1, D5-D3
    • Save these items to Set guaranteeing the equal pairs occur once ( D1-D3 = D3-D1 ). The condition the Pair must override both Object::equals and Object:hashCode and implements equality based on the both documents present.

       D1-D3, D1-D5, D3-D5, D2-D4
    • Reducing (merging) the particular sets into a single collection Set<Pair<Document>> .

Map<Document, List<Document>> map = documents.stream()
    .collect(Collectors.toMap(                                // Collected to Map<Document, List<Document>>
        Function.identity(),                                  // Document is the key
        d1 -> documents.stream()                              // Value are the qualified documents
            .filter(d2 -> !d1.equals(d2) &&            
                utils.matchesOnCriteria(d1,d2, userCriteria)
            .collect(Collectors.toList())));                  // ... as List<Document>

Set<Pair<Document>> matches = map.entrySet().stream().reduce( // Reduce the Entry<Dokument, List<Document>>
    new HashSet<>(),                                          // ... to Set<Pair<>>
    (set, e) -> {
        set.addAll(e.getValue().stream()                      // ... where is
            .map(v -> new Pair<Document>(e.getKey(), v))      // ... the Pair of qualified documents
            .collect(Collectors.toSet()));                   
        return set;
    },
    (left, right) -> { left.addAll(right); return left; });   // Merge operation

The condition !matches.contains(p) is redundant, there are better ways to assure distinct values. Either use Stream::distinct or collect the stream to Set which is an unordered distinct collection.

Read more at Baeldung's: remove all duplicates .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM