psi_
psi_

Reputation: 65

Nested filters using lambda

I'm trying to code a multilevel filter. I have folders and files.

public class Folder {
  String name;
  List<Folder> folders;
  List<File> files;
  ...
}

public class File {
  String name;
  String type;
  ...
}

I need a filter who looks for some property e.g. name, which can be a folder/file name. I wrote a piece of code that works for one level, but I don't how could I do that looking for two level loops.

The final result would be a list of folders: the ones which names match the 'name filter' or folders which file names match the filter.

List<Folder> result = folders.stream()
  .filter(folder -> folder.getFiles().stream().anyMatch(file -> file.getName().contains(filter)))
  .collect(Collectors.toList());

Upvotes: 2

Views: 898

Answers (2)

thomas77
thomas77

Reputation: 1150

A running test which filters by filename across folders (without any helper method).

The predicate in your filter method is what makes the difference alongside flatMap as @Ernest refers to that flattens all the File elements into one list.

private static final String FILE_NAME_FILTER = "1";
private static final String HOME_FOLDER_FILTER = "home";
private static final String VAR_FOLDER_FILTER = "var";
private static final String BIN_FOLDER_FILTER = "bin";

@Test
public void test() {
    File f1 = new File("1", "txt");
    File f2 = new File("1", "json");
    File f3 = new File("1", "yml");
    File f4 = new File("4", "doc");

    Folder folder1 = new Folder(HOME_FOLDER_FILTER, asList(f1, f2));
    Folder folder2 = new Folder(VAR_FOLDER_FILTER, asList(f2, f3));
    Folder folder3 = new Folder(BIN_FOLDER_FILTER, asList(f3, f4));
    List<Folder> folders = asList(folder1, folder2, folder3);

    List<File> files = folders.stream()
            .filter(folder -> folder.getName().equals(HOME_FOLDER_FILTER))
            .map(Folder::getFiles) // no helper method
            .flatMap(List::stream)
            .filter(file -> file.getName().equals(FILE_NAME_FILTER))
            .collect(Collectors.toList());
    Assert.assertEquals(2, files.size());
}

Pretty much along the line of what @Oleksandr suggests.

Edit: I see i missed out on the fact that a folder can contain a folder. Should I delete this answer?

Upvotes: 0

Oleksandr Pyrohov
Oleksandr Pyrohov

Reputation: 16226

I think it will be better to add a helper method to the Folder class that will flatten nested folders into a Stream recursively:

class Folder {
    String name;
    List<Folder> folders;
    List<File> files;

    Stream<Folder> flatten() {
        return Stream.concat(Stream.of(this), folders.stream().flatMap(Folder::flatten));
    }
}

After that, you can get the final result using flatMap(...)

List<Folder> result = folders.stream()
    .flatMap(Folder::flatten)
    .filter(folder -> folder.getFiles()
        .stream()
        .anyMatch(file -> file.getName().contains(filter)))
    .collect(Collectors.toList());

Upvotes: 3

Related Questions