Adhyatmik
Adhyatmik

Reputation: 1118

How to filter directory listing by using a property from the resultant list itself in Java?

I have a directory called /home/ftp which contains sub-directories like this:

dir1
dir2
dir3
dir1.ok (this is just an empty file)
dir3.ok (this is just an empty file)

The code should only process the files under directories which have a corresponding ".ok" file. So for eg. in this example, the code should pick those files for processing which are found under dir1 and dir3 but not dir2.

I could do this the "normal" way:

List<String> list = Arrays.asList(new File("/home/ftp").list());
Set<String> set = new HashSet<>();      
List<String> dirToProcess = new ArrayList<>();

for (String name : list){
    name = name.contains(".ok") ? name.substring(0, name.indexOf(".ok")) : name;
    if (!set.add(name)){
        dirToProcess.add(name);
    }       
}
// now dirToProcess contains the directory names which should be processed

But I really want to do this using functional Java (I'm using Java8) using Streams. How can i refactor this code to achieve it?

Upvotes: 2

Views: 617

Answers (5)

Luk
Luk

Reputation: 2246

You can split this in two steps.

@Test
public void should_return_dirs_with_corresponding_ok_file() {
    List<String> list = Arrays.asList("dir1.ok", "dir1", "dir2", "dir3.ok");

    //find all files ending with .ok and extract prefix
    List<String> okEndingFilesPrefix = list.stream()
        .filter(s -> s.endsWith(".ok"))
        //save those dirs prefix
        .map(s -> s.replace(".ok", ""))
        .collect(Collectors.toList());

    //filter existing dirs if they have corresponding file ending with ok, from previous step.
    List<String> dirsWithCorrespondingOkEndingFile = list.stream()
        .filter(s -> okEndingFilesPrefix.contains(s))
        .collect(Collectors.toList());

    assertEquals(dirsWithCorrespondingOkEndingFile, Lists.newArrayList("dir1"));
}

If you want to do this as oneliner, you can use grouping functions. In my opinion this does not look good in Java.

@Test
public void should_return_dirs_with_corresponding_ok_file_with_grouping() {
    List<String> list = Arrays.asList("dir1.ok", "dir1", "dir2", "dir3.ok");


    List<String> dirsWithCorrespondingOkEndingFile = list.stream()
        //create "touple" with dir name/file name without .ok suffix as key.
        .map(s -> Pair.of(s.replaceAll(".ok", ""), s))
        // group by key
        .collect(
            Collectors.groupingBy(
                Pair::getKey, Collectors.toSet()
            ))
        //once again to stream
        .entrySet()
        .stream()
        // filter elements that has dir and file with .ok suffix
        .filter(entry -> entry.getValue().size() == 2)
        .map(Map.Entry::getKey)
        .collect(Collectors.toList());

    assertEquals(dirsWithCorrespondingOkEndingFile, Lists.newArrayList("dir1"));
}

Upvotes: 0

Naman
Naman

Reputation: 31878

I think what you're expecting as an output is:

List<String> dirToProcess = list.stream()
                .filter(name -> name.contains(".ok") && list.contains(name.substring(0, name.indexOf(".ok"))))
                .map(name -> name.substring(0, name.indexOf(".ok")))
                .collect(Collectors.toList());

Upvotes: 2

Hadi
Hadi

Reputation: 17289

The other way is using of pattern regex.

Pattern hasOk = Pattern.compile("\\b.ok");
List<String> list = Arrays.asList("dir1.ok (this is just an empty file)","dir1.o","dir3");
List<String> result = list.stream()
            .filter(hasOk.asPredicate()) //or  .map(name -> hasOk.matcher(name).find()?name.substring(0,name.indexOf(".ok")):name)
            .collect(Collectors.toList());

Upvotes: 2

Ousmane D.
Ousmane D.

Reputation: 56433

if you want to convert your current approach using streams, you could do:

List<String> result = 
          Arrays.stream(new File("/home/ftp").list())
                .map(name -> name.endsWith(".ok") ? name.substring(0, name.indexOf(".ok")) : name)
                //.distinct()
                .collect(toList());

On another note, there's probably a better way to do this using java.nio.file API, some few examples here and here etc..

Upvotes: 0

SamHoque
SamHoque

Reputation: 3154

You are making this harder on your self. If you know the file is gonna contain .ok just filter it by that and then collect it to a new list and then remove the extension when trying to get the file without .ok

List<String> filteredList = list.stream()
        .filter(s -> s.endsWith(".ok"))
        .collect(Collectors.toList());

    filteredList.forEach(s -> {
        String name = s.substring(0, s.indexOf(".ok"));
        //You can handle it here
    });

Upvotes: 1

Related Questions