有条件地从流中删除第一个(索引为零)元素


10

我有以下代码:

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

如果第一个字符串等于"email"我要从流中删除第一个字符串。对于流中的其他字符串,我不需要此检查。

我怎么能做到?

聚苯乙烯

当然,我可以将其转换为列表,然后使用旧式进行循环,但是进一步,我需要再次播放流。


3
如果第二个元素是“电子邮件”,您是否不想删除它?
michalk

@michalk您是正确的
gstackoverflow

嗯... skip(long n)跳过了第一个n元素,但我不知道您是否可以以某种方式进行调节……
deHaar

4
@gstackoverflow如果流中的前两个字符串不太可能等于“电子邮件”,我认为如果您正在谈论csv文件的标头,可以使用stream.dropWhile(s -> s.equals("email"));
厄立特里亚,

1
@厄立特里亚,它只会发布它;)
YCF_L

Answers:


6

尽管在从阅读器构造出一行流之后,阅读器将处于未指定状态,但在执行此操作之前,阅读器处于良好定义的状态。

所以你可以做

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,如果Java 9相匹配,它们将掉落多行。


同意-此解决方案更好,但dropWhile-第二。不幸的是,作者决定不将这个想法作为单独的答案发布
gstackoverflow

3

如果您没有列表,而只能使用a进行操作Stream,则可以使用变量。

问题是,只有在变量为“最终”或“有效地为最终”时,才可以使用该变量,因此您不能使用文字布尔值。您仍然可以使用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因为副作用在函数式编程范例中是一种不好的做法。欢迎有更好的选择。


使用,compareAndSet是同时设置和获取标志的一种非常优雅的方式,很好。
f1sh

可以这样做stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))虽然值得商榷。
乔普·艾根

1
predicate必须是无国籍的
ZhekaKozlov

3
如果使用AtomicBooleanhere来满足并行流(如compareAndSet建议的使用),那么这是完全错误的,因为它仅在流是顺序的时才起作用。如果您使用该技术,stream.sequential().filter(...)那么我同意它将起作用。
丹尼尔(Daniel)

3
@daniel,您是对的,这种方法在不支持并行处理的情况下(为每个元素)支付了线程安全构造的开销。如此多的带有状态过滤器的示例使用这些类的原因是,没有线程安全就没有等效的方法,而且人们避免了在答案中创建专用类的复杂性……
Holger

1

为了避免检查文件每一行的条件,我只需要分别阅读并检查第一行,然后在其余行上运行管道而不检查条件:

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

4
Java 9甚至更简单:Stream.ofNullable(first).filter(not("email"::equals))
Holger

1

@Arnouds答案是正确的。您可以为第一行创建一个流,然后进行如下比较,

Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);

即使您使用过滤器,也会为每行计算一次。如果文件很大,可能会导致性能下降。如果有限制,则只能比较一次。
Code_Mode

除了不为读者工作外,limit(0)当然还应该limit(1)……
Holger

我已经在testimg之后将答案限制为0。
Code_Mode

1
我看到您已更改它,但.limit(0).filter(line -> !line.startsWith("email"))没有任何意义。该谓词将永远不会得到评估。对于能够再现流的流源,limit(1)第一个和skip(1)第二个上的组合将是正确的。对于像读者流源,既不limit(1)limit(0)将工作。您只是更改了无条件吞入的那一行。即使您发现碰巧可以完成所需操作的组合,也将基于未指定的,依赖于实现的行为。
Holger

0

要根据元素的索引过滤元素,可以AtomicInteger在处理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]
}

这种方法允许过滤任何索引的元素,而不仅仅是第一个。


0

更加令人费解,从此片段中获得了一些启发。

您可以创建一个Stream<Integer>代表索引的文件,然后将其“压缩” 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();
    }
}

0

这是带有的示例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);

0

尝试这个:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.