Eliminar el primer elemento (con índice cero) de la secuencia condicionalmente

10

Tengo el siguiente código:

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

Si la cadena del primer puño es igual "email", quiero eliminar la primera cadena del flujo. Para otras cadenas de la secuencia, no necesito esta verificación.

¿Cómo podría lograrlo?

PD

Claro que puedo transformarlo en la lista, luego usar la vieja escuela para el bucle, pero además necesito transmitir nuevamente.

gstackoverflow
fuente
3
Y si el segundo elemento es "correo electrónico", ¿no quieres dejarlo?
michalk
@michalk tienes razón
gstackoverflow
Hmm ... hay skip(long n)que omite los primeros nelementos, pero no sé si puede condicionarlo de alguna manera ...
deHaar
44
@gstackoverflow si es poco probable que las dos primeras cadenas de la secuencia sean iguales a "correo electrónico", lo que creo que es el caso si está hablando del encabezado del archivo csv, puede usarlostream.dropWhile(s -> s.equals("email"));
Eritrean
1
@Eritrean solo lo publicará;)
YCF_L

Respuestas:

6

Si bien el lector estará en un estado no especificado después de que construyó una secuencia de líneas a partir de él, está en un estado bien definido antes de hacerlo.

Entonces puedes hacer

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

Cuál es la solución más limpia en mi opinión. Tenga en cuenta que esto no es lo mismo que Java 9 dropWhile, que dejaría caer más de una línea si coinciden.

Holger
fuente
De acuerdo, esta solución es mejor, pero a la vez, es el segundo lugar. Desafortunadamente, el autor decidió no publicar esta idea como una respuesta por separado
gstackoverflow
3

Si no puede tener la lista y debe hacerlo solo con a Stream, puede hacerlo con una variable.

La cuestión es que solo puede usar una variable si es "final" o "efectivamente final", por lo que no puede usar un booleano literal. Aún puedes hacerlo con un 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: No me gusta usar "variables externas" Streamporque los efectos secundarios son una mala práctica en el paradigma de programación funcional. Mejores opciones son bienvenidas.

Arnaud Denoyelle
fuente
usar el compareAndSetes una forma muy elegante de configurar y obtener la bandera al mismo tiempo, agradable.
f1sh
Podría hacer stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))aunque discutible.
Joop Eggen el
1
predicatedebe ser apátrida
ZhekaKozlov
3
Si el uso de AtomicBooleanaquí es para atender flujos paralelos (como compareAndSetsugiere el uso de ), entonces esto es completamente incorrecto, ya que solo funcionará si el flujo es secuencial. Sin embargo, si utiliza la técnica con stream.sequential().filter(...), estoy de acuerdo en que funcionará.
daniel
3
@daniel tiene razón, este enfoque está pagando los gastos de una construcción segura para subprocesos (para cada elemento) sin admitir el procesamiento paralelo. La razón por la que tantos ejemplos con filtros con estado usan estas clases es que no hay equivalente sin seguridad de subprocesos y las personas evitan la complejidad de crear una clase dedicada en sus respuestas ...
Holger
1

Para evitar verificar la condición en cada línea del archivo, simplemente leía y verificaba la primera línea por separado, luego ejecutaba la tubería en el resto de las líneas sin verificar la condición:

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

Más simple en Java 9+:

Stream<String> firstLines = Optional.of(first)
        .filter(s -> !"email".equals(s))
        .stream();

Stream<String> lines = Stream.concat(firstLines, reader.lines());
ernest_k
fuente
44
Java 9 es aún más simple:Stream.ofNullable(first).filter(not("email"::equals))
Holger
1

La respuesta de @Arnouds es correcta. Puede crear una secuencia para la primera línea y luego compararla a continuación,

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
fuente
incluso si usa un filtro, se calcularía para cada línea. En caso de archivo enorme, puede causar rendimiento. En caso de límite, se compararía solo una vez.
Code_Mode
Además de no trabajar para un lector, limit(0)seguramente debería ser limit(1)...
Holger
He editado la respuesta al límite 0 después de testimg.
Code_Mode
1
Veo que lo cambiaste, pero .limit(0).filter(line -> !line.startsWith("email"))no tiene sentido. El predicado nunca será evaluado. Para una fuente de flujo capaz de reproducir flujos, la combinación de limit(1)en el primero y skip(1)en el segundo sería correcta. Para una fuente de transmisión como un lector, limit(1)ni limit(0)funcionará. Acabas de cambiar qué línea se traga incondicionalmente. Incluso si encuentra una combinación que resulta hacer lo deseado, sería sobre la base de un comportamiento no especificado, dependiente de la implementación.
Holger
0

Para filtrar elementos en función de su índice, puede usarlo AtomicIntegerpara almacenar e incrementar el índice mientras procesa 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]
}

Este enfoque permite filtrar elementos en cualquier índice, no solo en el primero.

Evgeniy Khyst
fuente
0

Un poco más enrevesado, obteniendo un poco de inspiración de este fragmento .

Puede crear un Stream<Integer>que represente índices y "comprimirlo" con su Stream<String>para crear unStream<Pair<String, Integer>>

Luego filtre usando el índice y vuelva a asignarlo a un 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();
    }
}
Bentaye
fuente
0

Aquí hay un ejemplo con Collectors.reducing. Pero al final crea una lista de todos modos.

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);
lczapski
fuente
0

Prueba esto:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
user_3380739
fuente