Условно удалить первый (с нулевым индексом) элемент из потока


10

У меня есть следующий код:

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

Если первая строка равна, "email"я хочу удалить первую строку из потока. Для других строк из потока мне не нужна эта проверка.

Как я мог заболеть?

PS

Конечно, я могу преобразовать его в список, затем использовать old school for loop, но в дальнейшем мне снова нужен поток.


3
А если вторым элементом является «электронная почта», вы не хотите его отбрасывать?
Михалк

@ michalk вы правы
gstackoverflow

Хм ... есть skip(long n)то, что пропускает первые nэлементы, но я не знаю, можете ли вы как-то это обусловить ...
deHaar

4
@gstackoverflow, если маловероятно, что первые две строки в потоке равны «электронной почте», что, как я думаю, имеет место, если вы говорите о заголовке файла CSV, вы можете использоватьstream.dropWhile(s -> s.equals("email"));
Эритрейская

1
@Eritrean это просто отправит это;)
YCF_L

Ответы:


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, из-за которого было бы больше одной строки, если они совпадают


Согласитесь - это решение лучше, но 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"))хоть и спорным.
Joop Eggen

1
predicateдолжны быть лицами без гражданства
Жека Козлов

3
Если AtomicBooleanздесь используется для обслуживания параллельных потоков (как compareAndSetпредполагает использование ), то это совершенно неправильно, так как это будет работать, только если поток является последовательным. Если вы используете технику, stream.sequential().filter(...)однако, я согласен, что она будет работать.
Даниэль

3
@daniel Вы правы, этот подход оплачивает расходы на потокобезопасную конструкцию (для каждого элемента), не поддерживая параллельную обработку. Причина, по которой во многих примерах с фильтрами с состоянием используются эти классы, заключается в том, что нет эквивалента без безопасности потоков, и люди избегают сложности создания выделенного класса в своих ответах…
Хольгер,

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))
Хольгер

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)...
Хольгер

Я отредактировал ответ, чтобы ограничить 0 после testimg.
Code_Mode

1
Я вижу, что вы изменили это, но .limit(0).filter(line -> !line.startsWith("email"))не имеет смысла. Предикат никогда не будет оценен. Для источника потока, способного воспроизводить потоки, комбинация limit(1)первого и skip(1)второго будет правильной. Для потокового источника, такого как читатель, ни работать , ни работать limit(1)не limit(0)будет. Вы только что изменили, какая линия безоговорочно проглатывается. Даже если вы нашли комбинацию, которая, как оказалось, выполняет желаемое действие, она будет основана на неуказанном, зависящем от реализации поведении.
Хольгер

0

Чтобы отфильтровать элементы по их индексу, вы можете использовать AtomicIntegerдля хранения и увеличения индекса при обработке 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))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.