How to avoid the last new line in when writing to a File using the Stream API and Printwriter?

Viewed 459

I am studying the Lambdas and Stream API that comes with Java 8. I wanted to create a file something like this using Stream API just for exercising purposes:

b
bb
bbb
bbbb
bbbbb
bbbb
bbb
bb
b

and this is the implementation I came up with:

final File file = new File("/Users/kt/sample.data");
final PrintWriter printWriter = new PrintWriter(file);

Stream.concat(IntStream.iterate(1, i -> i + 1).limit(6).boxed(), 
              IntStream.iterate(5, i -> i - 1).limit(5).boxed())
      .map(counter -> {
          StringBuilder stringBuilder = new StringBuilder();
          IntStream.range(1, counter).forEach(c -> stringBuilder.append("b"));
          return stringBuilder.toString();
      }).filter(s -> s.length() != 0).forEach(s -> printWriter.println(s));

printWriter.flush();
printWriter.close();

Now the only problem is, I end up with an empty last line in the file, since printwriter is printing the last 'b' together with a new line.

Obviously I can pass 4 instead of 5 to 2nd IntStream.iterate I have and before flushing the printWriter I can do: printWriter.print("b");, but my goal is to harness the Stream API.

How can I achieve this more Streamy?

3 Answers

You can do you job much simpler as

Files.write(Paths.get("/Users/kt/sample.data"),
    () -> IntStream.range(1, 10)
        .<CharSequence>mapToObj(i -> "bbbbb".substring(Math.abs(5-i)))
        .iterator());

Not to speak of

Files.write(Paths.get("C:\\Users\\pietsch\\AppData\\Local\\Temp\\output"),
    Collections.singletonList("b\nbb\nbbb\nbbbb\nbbbbb\nbbbb\nbbb\nbb\nb"));

If it feels too much like cheating due to hardcoded invariants, change the code to allow to specify an arbitrary maximum string size:

int size = 5;

int[] codepoints = IntStream.range(0, size).map(i -> 'b').toArray();
Files.write(Paths.get("/Users/kt/sample.data"),
    () -> IntStream.range(1, size*2)
        .<CharSequence>mapToObj(i -> new String(codepoints, 0, size-Math.abs(size-i)))
        .iterator());

I recommend to use a variable to store the size, as there is several places that requires it : maxSize


  1. You can join your elements with a return line, and then print, and as a tip you can simplify your use of StringBuilder with a map operation
int maxSize = 5;
String str =
        IntStream.concat(IntStream.iterate(2, i -> i + 1).limit(maxSize),
                         IntStream.iterate(maxSize, i -> i - 1).limit(maxSize - 1))
                 .mapToObj(count -> IntStream.range(1, count).mapToObj(c -> "b").collect(Collectors.joining()))
                 .collect(Collectors.joining("\n"));

printWriter.print(str);
  1. A second option, to avoid the String variable, would be add a \n between each block, then limit to remove the last one and print all, but this is not the more efficient between both :
int maxSize = 5;
IntStream.concat(IntStream.iterate(2, i -> i + 1).limit(maxSize),
                 IntStream.iterate(maxSize, i -> i - 1).limit(maxSize - 1))
         .mapToObj(count -> IntStream.range(1, count).mapToObj(c -> "b").collect(Collectors.joining()))
         .flatMap(b -> Stream.of(b, "\n"))
         .limit((maxSize * 2 - 1) * 2 - 1)
         .forEach(printWriter::print);

Do not know exactly what the Streamy way means, but how about this:

    int[] arr = new int[max * 2 - 1];
    arr[(max * 2) / 2] = max;

    IntStream.range(0, max)
             .forEachOrdered(x -> {
                 arr[x] = x + 1;
                 arr[x + max - 1] = max - x;
             });

    String result = Arrays.stream(arr)
                          .mapToObj(x -> IntStream.range(0, x)
                                                  .mapToObj(i -> "b")
                                                  .collect(Collectors.joining()))
                          .collect(Collectors.joining("\n"));
Related