4

I have a question about lambda expressions. I have a class Pair which should hold a String and an int.

Pair gets the String out of a file. and the int is representiv for the line number. So far I have this:

 Stream<String> lineNumbers = Files.lines(Paths.get(fileName));
    List<Integer> posStream = Stream.iterate(0, x -> x + 1).limit(lineNumbers.count()).collect(Collectors.toList());
    lineNumbers.close();
    Stream<String> line = Files.lines(Paths.get(fileName));
    List<Pair> pairs = line.map((f) -> new Pair<>(f,1))
            .collect(Collectors.toList());
    pairs.forEach(f -> System.out.println(f.toString()));
    line.close();

How can I now input the file numbers to the pairs? Is there a lambda expression which can perform this? Or do I need something else?

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Meant2Play
  • 57
  • 1
  • 2
  • 6

3 Answers3

9

There are a few ways to do this. The counter technique suggested by Saloparenator's answer could be implemented as follows, using an AtomicInteger as the mutable counter object and assuming the obvious Pair class:

List<Pair> getPairs1() throws IOException {
    AtomicInteger counter = new AtomicInteger(0);
    try (Stream<String> lines = Files.lines(Paths.get(FILENAME))) {
        return lines.parallel()
                    .map(line -> new Pair(line, counter.incrementAndGet()))
                    .collect(toList());
    }
}

The problem is that if the stream is run in parallel, the counter won't be incremented in the same order as the lines are read! This will occur if your file has several thousand lines. The Files.lines stream source will batch up bunches of lines and dispatch them to several threads, which will then number their batches in parallel, interleaving their calls to incrementAndGet(). Thus, the lines won't be numbered sequentially. It will work if you can guarantee that your stream will never run in parallel, but it's often a bad idea to write streams that are likely to return different results sequentially vs. in parallel.

Here's another approach. Since you're reading all the lines into memory no matter what, just read them all into a list. Then use a stream to number them:

static List<Pair> getPairs2() throws IOException {
    List<String> lines = Files.readAllLines(Paths.get(FILENAME));
    return IntStream.range(0, lines.size())
                    .parallel()
                    .mapToObj(i -> new Pair(lines.get(i), i+1))
                    .collect(toList());
}
Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
0

Another functional way would be to ZIP your list of stream with a integer generator

(I can see java8 dont have it yet but it is essentially merging every cell of two list in a list of pair, so it is easy to implement)

You can see example of generator in java8 here

Community
  • 1
  • 1
Saloparenator
  • 330
  • 1
  • 13
-1
int cnt = 1;
List<Pair> pairs = line.map((f) -> new Pair<>(f,cnt++))
                       .collect(Collectors.toList());

I have not tried it yet but may work.

Saloparenator
  • 330
  • 1
  • 13
  • 2
    That dont work, because a lambda expression need a final to perform it so i cant increase a value inside a lambda expression – Meant2Play Apr 24 '15 at 14:59
  • Then i think you cant perform what he want using lambda. Anyway lambda should not be used in mutable way. – Saloparenator Apr 24 '15 at 15:01
  • It's a small method in my homework which is about lambda expressions and streams xD – Meant2Play Apr 24 '15 at 15:06
  • 1
    you can build an counter object that increase value every get, declare it final and use it in your lambda. By definition lambda are idempotent, mean if you call it several time with the same param, it will return de same result every time. This is why I say increasing a value inside lambda is anti-functional. – Saloparenator Apr 24 '15 at 15:11
  • use final atomicInteger; .map(arr ->new Integer(i.getAndIncrement())).... – Nrj Jul 18 '17 at 16:04