Using Java streams with composed collections

I'm looking to use Java streams to solve this type of problem:

I have a List of Library objects.

The Library class contains a Map of Sections. The Sections contains a List of BookShelf objects. The BookShelf contains a Map of Book objects.

class Library {
    Map<String, Section> sections = new HashMap<String, Section>();
    String name;
    public String toString() {
        return name;
    }
}

class Section {
    List<BookShelf> bookShelves = new ArrayList<BookShelf>();
}

class BookShelf {
    Map<String, Book> books = new HashMap<String, Book>();
}

class Book {
    String name;
    Book(String name) {
       this.name = name;
    }
}

What I want to find out is all Library objects that have the Book called "Java 8". I think that this is a query that could be answered using streams, however, coding various stream calls I get a collection of Book objects, not the root Library objects.

Are streams suitable for using embedded objects and collections? How would I achieve this?

Below is a simple code, that uses traditional loops to find the answer:

List<Library> libraries = new ArrayList<Library>();

Library lib = new Library();
lib.name = "Houston Central";
libraries.add(lib);

Section section = new Section();
lib.sections.put("Reference", section);

BookShelf shelf = new BookShelf();
section.bookShelves.add(shelf);

Book book = new Book("Java 8");
shelf.books.put("Java 8", book);

book = new Book("Java 7");
shelf.books.put("Java 7", book);


/*
 * Search for a Library containing a specific book
 * using traditional loops.
 */
for(Library library : libraries) {
    for (Section sec : library.sections.values()) {
        for (BookShelf bookShelf : sec.bookShelves) {
            for (Book b : bookShelf.books.values()) {
                if (b.name.equals("Java 8")) {
                    System.out.println("Book is contained in Library " + library);
                }
            }
        }
    }
}

Whether streams are suitable or not may be a matter of taste and readability preference. Stream code and lambdas don't make for the most readable code in my opinion, but maybe its something you learn to do. In any case, the basic approach is to filter a stream of libraries based on the presence or absence of a book with the title "Java 8" and return those libraries that fulfill the filter predicate as a collection. Filtering itself requires that each library be turned into a stream of books, which entails two flatmap operations. I then use anyMatch to match on the book title and terminate the stream for the given library. You can parallelize the process I presume, but I have not done a lot with parallel streams.

The example provided below externalizes the search process. A more suitable object oriented approach would include a book search function in the Section and BookShelf classes, which would go a long way to improving readability.

Bear in mind that you are searching sequentially for a book and sequential searches are slow. Sequential search is O(n) whereas a HashMap index search is O(1). I would include an index in the BookShelf class. I provide an example of a sequential search algorithm below (not tested).

public Collection<Library> hasBook(Collection<Library> libraries, String title) {
    return libraries.stream()
        .filter(x->x.sections()
                .stream()
                .flatMap(y->y.bookShelves()
                        .stream()
                        .flatMap(z->z.books().stream()))
                .anyMatch(b->b.title().equals(title)))
        .collect(Collectors.toList());
}

Using the solution of John Morris with a tweak to support Maps solves the problem:

/*
 * Using Streams
 */
public static Collection<Library> hasBook(Collection<Library> libraries, String title) {
    return libraries.stream()
        .filter(x->x.sections.values()
                .stream()
                .flatMap(y->y.bookShelves
                        .stream()
                        .flatMap(z->z.books.values().stream()))
                .anyMatch(b->b.name.equals(title)))
        .collect(Collectors.toList());
}

Calling code:

/*
 * Search using streams
 */
Collection<Library> libs = hasBook(libraries, "Java 8");
for (Library l : libs) {
    System.out.println("Book is contained in Library (via streams) " + l);
}
链接地址: http://www.djcxy.com/p/16346.html

上一篇: 哈希表如何与哈希表不同?

下一篇: 使用具有组合集合的Java流