Reputation: 36984
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?
Sure I can transform it to the list, then use old school for loop but further I need stream again.
Upvotes: 9
Views: 729
Reputation: 1254
Try this:
MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
Upvotes: 0
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
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
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
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
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
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
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