有条件地从流中删除第一个(索引为零)元素
Remove first(with zero index) element from stream conditionally
我有以下代码:
Stream<String> lines = reader.lines();
如果第一个字符串等于 "email"
我想从流中删除第一个字符串。对于流中的其他字符串,我不需要此检查。
我该如何实现?
P.S.
我当然可以将它转换为列表,然后使用老派的 for 循环,但我还需要再次流式传输。
如果您无法获得列表而必须只使用一个 Stream
,您可以使用一个变量。
问题是你只能使用 "final" 或 "effectively final" 的变量,所以你不能使用文字布尔值。您仍然可以使用 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);
注意:我不喜欢在 Stream
中使用 "external variables",因为副作用在函数式编程范例中是一种不好的做法。欢迎更好的选择。
要根据索引过滤元素,您可以在处理 Stream
时使用 AtomicInteger
来存储和递增索引:
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]
}
这种方法允许过滤任何索引处的元素,而不仅仅是第一个。
@Arnouds 回答正确。您可以为第一行创建一个流,然后如下比较,
Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);
为了避免检查文件每一行的条件,我只是单独阅读并检查第一行,然后 运行 其余行的管道而不检查条件:
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());
在 Java 9+ 上更简单:
Stream<String> firstLines = Optional.of(first)
.filter(s -> !"email".equals(s))
.stream();
Stream<String> lines = Stream.concat(firstLines, reader.lines());
有点复杂,从this snippet中得到一些启发。
您可以创建一个 Stream<Integer>
来表示索引,并 "zip" 它与您的 Stream<String>
一起创建一个 Stream<Pair<String, Integer>>
然后使用索引过滤并将其映射回 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();
}
}
虽然 reader 在您从中构建线条流后将处于未指定状态,但在您构建之前它处于明确定义的状态。
所以你可以做到
String firstLine = reader.readLine();
Stream<String> lines = reader.lines();
if(firstLine != null && !"email".equals(firstLine))
lines = Stream.concat(Stream.of(firstLine), lines);
我认为这是最干净的解决方案。请注意,这与 Java 9 的 dropWhile
不同,如果它们匹配,后者会丢弃不止一行。
这里是 Collectors.reducing
的例子。但最终还是创建了一个列表。
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);
试试这个:
MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
我有以下代码:
Stream<String> lines = reader.lines();
如果第一个字符串等于 "email"
我想从流中删除第一个字符串。对于流中的其他字符串,我不需要此检查。
我该如何实现?
P.S.
我当然可以将它转换为列表,然后使用老派的 for 循环,但我还需要再次流式传输。
如果您无法获得列表而必须只使用一个 Stream
,您可以使用一个变量。
问题是你只能使用 "final" 或 "effectively final" 的变量,所以你不能使用文字布尔值。您仍然可以使用 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);
注意:我不喜欢在 Stream
中使用 "external variables",因为副作用在函数式编程范例中是一种不好的做法。欢迎更好的选择。
要根据索引过滤元素,您可以在处理 Stream
时使用 AtomicInteger
来存储和递增索引:
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]
}
这种方法允许过滤任何索引处的元素,而不仅仅是第一个。
@Arnouds 回答正确。您可以为第一行创建一个流,然后如下比较,
Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);
为了避免检查文件每一行的条件,我只是单独阅读并检查第一行,然后 运行 其余行的管道而不检查条件:
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());
在 Java 9+ 上更简单:
Stream<String> firstLines = Optional.of(first)
.filter(s -> !"email".equals(s))
.stream();
Stream<String> lines = Stream.concat(firstLines, reader.lines());
有点复杂,从this snippet中得到一些启发。
您可以创建一个 Stream<Integer>
来表示索引,并 "zip" 它与您的 Stream<String>
一起创建一个 Stream<Pair<String, Integer>>
然后使用索引过滤并将其映射回 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();
}
}
虽然 reader 在您从中构建线条流后将处于未指定状态,但在您构建之前它处于明确定义的状态。
所以你可以做到
String firstLine = reader.readLine();
Stream<String> lines = reader.lines();
if(firstLine != null && !"email".equals(firstLine))
lines = Stream.concat(Stream.of(firstLine), lines);
我认为这是最干净的解决方案。请注意,这与 Java 9 的 dropWhile
不同,如果它们匹配,后者会丢弃不止一行。
这里是 Collectors.reducing
的例子。但最终还是创建了一个列表。
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);
试试这个:
MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))