gstackoverflow
gstackoverflow

Reputation: 36984

Remove first(with zero index) element from stream conditionally

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.

Upvotes: 9

Views: 729

Answers (8)

user_3380739
user_3380739

Reputation: 1254

Try this:

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

Upvotes: 0

lczapski
lczapski

Reputation: 4120

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);

Upvotes: 0

Vikas
Vikas

Reputation: 7175

@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);

Upvotes: 0

Holger
Holger

Reputation: 298183

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.

Upvotes: 5

Bentaye
Bentaye

Reputation: 9756

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();
    }
}

Upvotes: 0

ernest_k
ernest_k

Reputation: 45319

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());

Upvotes: 1

Arnaud Denoyelle
Arnaud Denoyelle

Reputation: 31225

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.

Upvotes: 3

Eugene Khyst
Eugene Khyst

Reputation: 10315

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.

Upvotes: 0

Related Questions