Remover primeiro elemento (com índice zero) do fluxo condicionalmente


10

Eu tenho o seguinte código:

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

Se a primeira corda for igual "email", quero remover a primeira do fluxo. Para outras seqüências de caracteres do fluxo, não preciso dessa verificação.

Como eu consegui isso?

PS

Claro que posso transformá-lo na lista e, em seguida, usar o loop for old school, mas ainda preciso transmitir novamente.


3
E se o segundo elemento for "email", você não deseja descartá-lo?
Michalk

@michalk você está correto
gstackoverflow

Hmm ... há skip(long n)que ignora os primeiros nelementos, mas eu não sei se você pode condicioná-lo de alguma forma ...
deHaar

4
@gstackoverflow se for improvável que as duas primeiras cordas no fluxo igual a "e-mail" que eu acho que é o caso se você está falando de cabeçalho do arquivo CSV, você pode usarstream.dropWhile(s -> s.equals("email"));
Eritreia

11
@Eritrean ela só vai publicá-lo;)
YCF_L

Respostas:


6

Embora o leitor esteja em um estado não especificado depois que você construiu um fluxo de linhas a partir dele, ele está em um estado bem definido antes de você fazer isso.

Então você pode fazer

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

Qual é a solução mais limpa na minha opinião. Observe que isso não é o mesmo que o Java 9 dropWhile, que eliminaria mais de uma linha se corresponderem.


Concordo - esta solução melhor, mas dropWhile - é o segundo lugar. Infelizmente autor decidiu não publicar essa idéia como uma resposta separada
gstackoverflow

3

Se você não pode ter a lista e deve fazê-lo apenas com a Stream, pode fazê-lo com uma variável.

O problema é que você só pode usar uma variável se for "final" ou "efetivamente final", para que você não possa usar um booleano literal. Você ainda pode fazer isso com um 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);

Nota: Não gosto de usar "variáveis ​​externas" Streamporque os efeitos colaterais são uma má prática no paradigma de programação funcional. Melhores opções são bem-vindas.


usando o compareAndSeté uma maneira muito elegante de definir e receber a bandeira ao mesmo tempo, legal.
F1sh 25/11/19

Poderia fazer stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))embora discutível.
Joop Eggen

11
predicatedeve ser apátrida
ZhekaKozlov 25/11/19

3
Se o uso AtomicBooleanaqui for para atender fluxos paralelos (como compareAndSetsugere o uso de ), isso estará completamente errado, pois só funcionará se o fluxo for seqüencial. Se você usar a técnica no stream.sequential().filter(...)entanto, eu concordo que funcionará.
Daniel

3
@daniel, você está certo, essa abordagem está pagando as despesas de uma construção segura de thread (para cada elemento) sem oferecer suporte ao processamento paralelo. A razão pela qual tantos exemplos com filtros de estado usam essas classes é que não há equivalente sem segurança de encadeamento e as pessoas evitam a complexidade de criar uma classe dedicada em suas respostas ...
Holger

1

Para evitar verificar a condição em cada linha do arquivo, basta ler e verificar a primeira linha separadamente e, em seguida, executar o pipeline no restante das linhas sem verificar a condição:

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

Mais simples em 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 é ainda mais simples:Stream.ofNullable(first).filter(not("email"::equals))
Holger

1

A resposta do @Arnouds está correta. Você pode criar um fluxo para a primeira linha e comparar como abaixo,

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

mesmo se você usar um filtro, ele será calculado para cada linha. No caso de um arquivo grande, pode causar desempenho. Em caso de limite, seria comparado apenas uma vez.
Code_Mode 25/11/19

Além de não trabalhar por um leitor, limit(0)certamente deve ser limit(1)...
Holger

Eu editei a resposta para limitar 0 após o depoimento.
Code_Mode 25/11/19

11
Vejo que você mudou, mas .limit(0).filter(line -> !line.startsWith("email"))não faz sentido. O predicado nunca será avaliado. Para uma fonte de fluxo capaz de reproduzir fluxos, a combinação de limit(1)no primeiro e skip(1)no segundo estaria correta. Para uma fonte de fluxo como um leitor, limit(1)nem limit(0)funcionará. Você acabou de alterar qual linha é engolida incondicionalmente. Mesmo se você encontrar uma combinação que faz a coisa desejada, ela seria baseada no comportamento não especificado e dependente da implementação.
Holger

0

Para filtrar elementos com base em seu índice, você pode usar AtomicIntegerpara armazenar e incrementar o índice ao processar um 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]
}

Essa abordagem permite filtrar elementos em qualquer índice, não apenas no primeiro.


0

Um pouco mais complicado, obtendo alguma inspiração deste trecho .

Você pode criar um Stream<Integer>que represente índices e "compactá-lo" com o seu Stream<String>para criar umStream<Pair<String, Integer>>

Em seguida, filtre usando o índice e mapeie-o de volta para um 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

Aqui está um exemplo com Collectors.reducing. Mas no final cria uma lista de qualquer maneira.

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

Tente o seguinte:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.