9

I have following code:

Stream<String> lines = reader.lines();

If fist string equals "email" I want to remove first string from the Stream. For other strings from the stream I don't need this check.

How could I acheve it?

P.S.

Sure I can transform it to the list, then use old school for loop but further I need stream again.

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710

8 Answers8

5

While the reader will be in an unspecified state after you constructed a stream of lines from it, it is in a well defined state before you do it.

So you can do

String firstLine = reader.readLine();
Stream<String> lines = reader.lines();
if(firstLine != null && !"email".equals(firstLine))
    lines = Stream.concat(Stream.of(firstLine), lines);

Which is the cleanest solution in my opinion. Note that this is not the same as Java 9’s dropWhile, which would drop more than one line if they match.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Agree - this solution better but dropWhile - is the second place. Unfortunately author decided not to post this idea as a separated answer – gstackoverflow Nov 25 '19 at 19:58
3

If you cannot have the list and must do it with only a Stream, you can do it with a variable.

The thing is that you can only use a variable if it is "final" or "effectively final" so you cannot use a literal boolean. You can still do it with an AtomicBoolean :

Stream<String> stream  = Arrays.asList("test", "email", "foo").stream();

AtomicBoolean first = new AtomicBoolean(true);
stream.filter(s -> {
    if (first.compareAndSet(true, false)) {
        return !s.equals("email");
    }
    return true;
})
// Then here, do whatever you need
.forEach(System.out::println);

Note : I don't like using "external variables" in a Stream because side effects are a bad practice in the functional programming paradigm. Better options are welcome.

Arnaud Denoyelle
  • 29,980
  • 16
  • 92
  • 148
  • using the `compareAndSet` is a very elegant way of setting and getting the flag at the same time, nice. – f1sh Nov 25 '19 at 10:46
  • Could do `stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))` though debatable. – Joop Eggen Nov 25 '19 at 10:50
  • From my view - it looks ugly but I don't know better solution. – gstackoverflow Nov 25 '19 at 11:14
  • 1
    `predicate` must be stateless – ZhekaKozlov Nov 25 '19 at 12:58
  • 3
    If the use of `AtomicBoolean` here is to cater for parallel streams (as the use of `compareAndSet` suggests) then this is completely wrong as it will only work if the stream is sequential. If you use the technique with `stream.sequential().filter(...)` however then I agree it will work. – daniel Nov 25 '19 at 16:21
  • 3
    @daniel you are right, this approach is paying the expenses of a thread safe construct (for every element) while not supporting parallel processing. The reason why so many examples with stateful filters use these classes is that there is no equivalent without thread safety and people avoid the complexity of creating a dedicated class in their answers… – Holger Nov 25 '19 at 16:24
  • @daniel I use `AtomicBoolean` just because I cannot use a literal `boolean` : as I stated in the answer, the variable must be `final` or "effectively `final`". A custom wrapper class would have worked too but everyone knows `AtomicBoolean` so I used it for the sake of clarity. – Arnaud Denoyelle Nov 25 '19 at 16:40
1

To avoid checking the condition on each line of the file, I'd simply read and check the first line separately, then run the pipeline on rest of lines without checking the condition:

String first = reader.readLine();
Stream<String> firstLines = Optional.of(first)
        .filter(s -> !"email".equals(s))
        .map(s -> Stream.of(s))
        .orElseGet(() -> Stream.empty());

Stream<String> lines = Stream.concat(firstLines, reader.lines());

Simpler on Java 9+:

Stream<String> firstLines = Optional.of(first)
        .filter(s -> !"email".equals(s))
        .stream();

Stream<String> lines = Stream.concat(firstLines, reader.lines());
ernest_k
  • 44,416
  • 5
  • 53
  • 99
0

To filter elements based on their index, you can use AtomicInteger to store and increment index while processing a Stream:

private static void filter(Stream<String> stream) {
  AtomicInteger index = new AtomicInteger();
  List<String> result = stream
      .filter(el -> {
        int i = index.getAndIncrement();
        return i > 0 || (i == 0 && !"email".equals(el));
      })
      .collect(toList());
  System.out.println(result);
}

public static void main(String[] args) {
  filter(Stream.of("email", "test1", "test2", "test3")); 
  //[test1, test2, test3]

  filter(Stream.of("test1", "email", "test2", "test3")); 
  //[test1, email, test2, test3]

  filter(Stream.of("test1", "test2", "test3")); 
  //[test1, test2, test3]
}

This approach allows to filter elements at any index, not only the first one.

Eugene Khyst
  • 9,236
  • 7
  • 38
  • 65
0

@Arnouds answer is correct. You can create one stream for first line and then compare as below,

Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);
Vikas
  • 6,868
  • 4
  • 27
  • 41
  • even if you use a filter, it would be computed for each line. In case of huge file, it may cause performance. In case of limit, it would be compared only once. – Vikas Nov 25 '19 at 11:09
  • Besides not working for a reader, `limit(0)` surely ought to be `limit(1)`… – Holger Nov 25 '19 at 16:21
  • 1
    I see that you changed it, but `.limit(0).filter(line -> !line.startsWith("email"))` makes no sense. The predicate will never get evaluated. For a stream source capable of reproducing streams, the combination of `limit(1)` on the first and `skip(1)` on the second would be correct. For a stream source like a reader, neither `limit(1)` nor `limit(0)` will work. You just changed which line gets unconditionally swallowed. Even if you found a combination that happens to do the desired thing, it would be on the ground of unspecified, implementation dependent behavior. – Holger Nov 26 '19 at 07:45
0

A little more convoluted, getting some inspiration from this snippet.

You can create a Stream<Integer> that will represent indexes and "zip" it with your Stream<String> to create a Stream<Pair<String, Integer>>

Then filter using the index and map it back to a Stream<String>

public static void main(String[] args) {
    Stream<String> s = reader.lines();
    Stream<Integer> indexes = Stream.iterate(0, i -> i + 1);

    zip(s, indexes)
        .filter(pair -> !(pair.getKey().equals("email") && pair.getValue() == 0))
        .map(Pair::getKey)
        .forEach(System.out::println);
}

private static Stream<Pair<String,Integer>> zip(Stream<String> stringStream, Stream<Integer> indexesStream){
    Iterable<Pair<String,Integer>> iterable = () -> new ZippedWithIndexIterator(stringStream.iterator(), indexesStream.iterator());
    return StreamSupport.stream(iterable.spliterator(), false);
}

static class ZippedWithIndexIterator implements Iterator<Pair<String, Integer>> {
    private final Iterator<String> stringIterator;
    private final Iterator<Integer> integerIterator;

    ZippedWithIndexIterator(Iterator<String> stringIterator, Iterator<Integer> integerIterator) {
        this.stringIterator = stringIterator;
        this.integerIterator = integerIterator;
    }
    @Override
    public Pair<String, Integer> next() {
        return new Pair<>(stringIterator.next(), integerIterator.next());
    }
    @Override
    public boolean hasNext() {
        return stringIterator.hasNext() && integerIterator.hasNext();
    }
}
Bentaye
  • 9,403
  • 5
  • 32
  • 45
0

Here is example with Collectors.reducing. But in the end creates a list anyway.

Stream<String> lines = Arrays.asList("email", "aaa", "bbb", "ccc")
        .stream();

List reduceList = (List) lines
        .collect(Collectors.reducing( new ArrayList<String>(), (a, v) -> {
                    List list = (List) a;
                    if (!(list.isEmpty() && v.equals("email"))) {
                        list.add(v);
                    }
                    return a;
                }));

reduceList.forEach(System.out::println);
lczapski
  • 4,026
  • 3
  • 16
  • 32
0

Try this:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
user_3380739
  • 1
  • 14
  • 14