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.