Xóa phần tử đầu tiên (có chỉ số 0) khỏi luồng một cách có điều kiện


10

Tôi có đoạn mã sau:

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

Nếu chuỗi nắm tay bằng "email"tôi muốn xóa chuỗi đầu tiên khỏi Luồng. Đối với các chuỗi khác từ luồng tôi không cần kiểm tra này.

Làm thế nào tôi có thể đạt được nó?

PS

Chắc chắn tôi có thể chuyển đổi nó thành danh sách, sau đó sử dụng trường cũ cho vòng lặp nhưng hơn nữa tôi cần stream lại.


3
Và nếu yếu tố thứ hai là "email" bạn không muốn bỏ nó?
michalk

@michalk bạn đúng
gstackoverflow 25/11/19

Hmm ... có skip(long n)bỏ qua các nyếu tố đầu tiên , nhưng tôi không biết liệu bạn có thể điều kiện bằng cách nào đó không ...
deHaar

4
@gstackoverflow nếu không chắc là hai chuỗi đầu tiên trong luồng bằng "email" như tôi nghĩ là trường hợp nếu bạn đang nói về tiêu đề của tệp csv, bạn có thể sử dụngstream.dropWhile(s -> s.equals("email"));
Eritrean

1
@Eritrean nó sẽ chỉ đăng nó;)
YCF_L

Câu trả lời:


6

Mặc dù trình đọc sẽ ở trạng thái không xác định sau khi bạn tạo một dòng các dòng từ nó, nhưng nó ở trạng thái được xác định rõ trước khi bạn thực hiện.

Vì vậy bạn có thể làm

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

Đó là giải pháp sạch nhất theo ý kiến ​​của tôi. Lưu ý rằng điều này không giống với Java 9 dropWhile, sẽ giảm nhiều hơn một dòng nếu chúng khớp.


Đồng ý - giải pháp này tốt hơn nhưng dropWhile - là vị trí thứ hai. Thật không may, tác giả đã quyết định không đăng ý tưởng này như một câu trả lời riêng biệt
gstackoverflow 25/11/19

3

Nếu bạn không thể có danh sách và phải làm điều đó chỉ với một Stream, bạn có thể làm điều đó với một biến.

Vấn đề là bạn chỉ có thể sử dụng một biến nếu nó là "cuối cùng" hoặc "cuối cùng có hiệu quả" để bạn không thể sử dụng một boolean theo nghĩa đen. Bạn vẫn có thể làm điều đó với một 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);

Lưu ý: Tôi không thích sử dụng "các biến ngoài" trong một Streamvì các tác dụng phụ là một thực tiễn xấu trong mô hình lập trình chức năng. Lựa chọn tốt hơn được chào đón.


sử dụng compareAndSetlà một cách rất thanh lịch để thiết lập và nhận cờ cùng một lúc, tốt đẹp.
F1sh

Có thể làm stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))mặc dù tranh cãi.
Eggen

1
predicatephải không quốc tịch
ZhekaKozlov

3
Nếu việc sử dụng AtomicBooleanở đây là để phục vụ cho các luồng song song (như việc sử dụng các compareAndSetgợi ý) thì điều này hoàn toàn sai vì nó sẽ chỉ hoạt động nếu luồng là tuần tự. Nếu bạn sử dụng kỹ thuật này stream.sequential().filter(...)tuy nhiên tôi đồng ý nó sẽ hoạt động.
daniel

3
@daniel bạn nói đúng, phương pháp này đang trả chi phí cho một cấu trúc an toàn của luồng (cho mọi phần tử) trong khi không hỗ trợ xử lý song song. Lý do tại sao rất nhiều ví dụ với các bộ lọc trạng thái sử dụng các lớp này là vì không có sự tương đương nếu không có sự an toàn của luồng và mọi người tránh sự phức tạp của việc tạo một lớp chuyên dụng trong câu trả lời của họ
giật

1

Để tránh kiểm tra điều kiện trên từng dòng của tệp, tôi chỉ cần đọc và kiểm tra riêng dòng đầu tiên, sau đó chạy đường ống trên các dòng còn lại mà không kiểm tra điều kiệ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());

Đơn giản hơn trên 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 thậm chí còn đơn giản hơn:Stream.ofNullable(first).filter(not("email"::equals))
Holger

1

Câu trả lời @Arnouds là đúng. Bạn có thể tạo một luồng cho dòng đầu tiên và sau đó so sánh như dưới đây,

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

ngay cả khi bạn sử dụng bộ lọc, nó sẽ được tính toán cho từng dòng. Trong trường hợp tập tin lớn, nó có thể gây ra hiệu suất. Trong trường hợp giới hạn, nó sẽ chỉ được so sánh một lần.
Code_Mode

Bên cạnh đó không làm việc cho một người đọc, limit(0)chắc chắn nên được limit(1)...
Holger

Tôi đã chỉnh sửa câu trả lời để giới hạn 0 sau testimg.
Code_Mode

1
Tôi thấy rằng bạn đã thay đổi nó, nhưng .limit(0).filter(line -> !line.startsWith("email"))không có ý nghĩa. Vị ngữ sẽ không bao giờ được đánh giá. Đối với nguồn phát có khả năng tái tạo luồng, sự kết hợp giữa limit(1)đầu tiên và skip(1)thứ hai sẽ là chính xác. Đối với một nguồn phát như người đọc, cả hai đều limit(1)không limit(0)hoạt động. Bạn vừa thay đổi dòng nào bị nuốt vô điều kiện. Ngay cả khi bạn tìm thấy một sự kết hợp xảy ra để làm điều mong muốn, nó sẽ nằm trên nền tảng của hành vi phụ thuộc thực hiện, không xác định.
Holger

0

Để lọc các thành phần dựa trên chỉ mục của chúng, bạn có thể sử dụng AtomicIntegerđể lưu trữ và tăng chỉ mục trong khi xử lý 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]
}

Cách tiếp cận này cho phép lọc các phần tử ở bất kỳ chỉ mục nào, không chỉ phần đầu tiên.


0

Một chút phức tạp hơn, nhận được một số cảm hứng từ đoạn trích này .

Bạn có thể tạo một Stream<Integer>cái sẽ đại diện cho các chỉ mục và "nén" nó với Stream<String>để tạoStream<Pair<String, Integer>>

Sau đó, lọc sử dụng chỉ mục và ánh xạ trở lại 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

Dưới đây là ví dụ với Collectors.reducing. Nhưng cuối cùng vẫn tạo ra một danh sách.

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

Thử cái này:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.