5

I have a Java lambda stream that parses a file and stores the results into a collection, based on some basic filtering.

I'm just learning lambdas so bear with me here if this is ridiculously bad. But please feel free to point out my mistakes.

For a given file:

#ignored
this
is
#ignored
working
fine

The code:

List<String> matches;

Stream<String> g = Files.lines(Paths.get(givenFile));

matches = g.filter(line -> !line.startsWith("#"))
           .collect(Collectors.toList());

["this", "is", "working", "fine"]

Now, how would I go about collecting the ignored lines into a second list within this same stream? Something like:

List<String> matches;
List<String> ignored; // to store lines that start with #

Stream<String> g = Files.lines(Paths.get(exclusionFile.toURI()));

matches = g.filter(line -> !line.startsWith("#"))
           // how can I add a condition to throw these
           // non-matching lines into the ignored collection?
           .collect(Collectors.toList());

I realize it would be pretty trivial to open a new stream, alter the logic a bit, and .collect() the ignored lines easily enough. But I don't want to have to loop through this file twice if I can do it all in one stream.

2
  • it must be something like g.filter(..).map(t->t::toString)).collect(..toList()) Commented Oct 13, 2016 at 3:40
  • See the final example in the Oracle docs: docs.oracle.com/javase/8/docs/api/java/util/stream/… Commented Oct 13, 2016 at 3:53

2 Answers 2

13

Instead of two streams you can use partitioningBy in Collector

List<String> strings = Arrays.asList("#ignored", "this", "is", "#ignored", "working", "fine");
Map<Boolean, List<String>> map = strings.stream().collect(Collectors.partitioningBy(s -> s.startsWith("#")));
System.out.println(map);

output

{false=[this, is, working, fine], true=[#ignored, #ignored]}

here I used key as Boolean but you can change it to a meaningful string or enum

EDIT

If the strings can starts with some other special characters you could use groupingBy

    List<String> strings = Arrays.asList("#ignored", "this", "is", "#ignored", "working", "fine", "!Someother", "*star");
    Function<String, String> classifier = s -> {
        if (s.matches("^[!@#$%^&*]{1}.*")) {
            return Character.toString(s.charAt(0));
        } else {
            return "others";
        }
    };
    Map<String, List<String>> maps = strings.stream().collect(Collectors.groupingBy(classifier));
    System.out.println(maps);

Output

{!=[!Someother], #=[#ignored, #ignored], *=[*star], others=[this, is, working, fine]}

also you can nest groupingBy and partitioningBy

Sign up to request clarification or add additional context in comments.

Comments

1

I think the closest you could come to a generic approach for this would be something like peek:

g.peek(line -> if (line.startsWith("#")) {
   ignored.add(line);
 })
 .filter(line -> !line.startsWith("#"))
// how can I add a condition to throw these
// non-matching lines into the ignored collection?
 .collect(Collectors.toList());

I mention it because unlike with the partitioning Collector you could, at least in theory, change together however many peeks you want--but, as you can see, you have to duplicate logic, so it's not ideal.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.