Видаліть перший (з нульовим індексом) елемент з потоку умовно


10

У мене є такий код:

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

Якщо рядок кулака дорівнює, "email"я хочу видалити перший рядок із потоку. Для інших рядків з потоку мені ця перевірка не потрібна.

Як я міг це зробити?

PS

Звичайно, я можу перетворити його до списку, а потім використовувати стару школу для циклу, але далі мені знову потрібен потік.


3
І якщо другим елементом є "електронна пошта", ви не хочете його кидати?
michalk

@michalk ви праві
gstackoverflow

Хм ... є skip(long n)те, що пропускає перші nелементи, але я не знаю, чи можна це якось
обумовити

4
@gstackoverflow, якщо малоймовірно, що перші два рядки в потоці відповідають рівню "електронної пошти", що, на мій погляд, випадок, якщо ви говорите про заголовок файлу csv, ви можете використовуватиstream.dropWhile(s -> s.equals("email"));
Eritrean

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


Погодьтеся, - це рішення краще, але поки що, хоча це - друге місце. На жаль, автор вирішив не публікувати цю ідею як окрему відповідь
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 Ви маєте рацію, такий підхід сплачує витрати на безпечну конструкцію (для кожного елемента), не підтримуючи паралельну обробку. Причина, за якою так багато прикладів із потужними фільтрами використовують ці класи, полягає в тому, що без безпеки потоку немає еквівалента, і люди уникають складності створення виділеного класу у своїх відповідях…
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))
Холгер

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.