С потоками Java могу ли я фильтровать список объектов на основе переменной, которая является общей для вторичного списка объектов с той же переменной?

#java #java-8 #java-stream

#java #java-8 #java-stream

Вопрос:

Мне интересно, есть ли способ просто вставить следующий код в какой-либо поток для работы, вместо того, чтобы иметь цикл for ?

Я в основном ищу, чтобы отфильтровать определенную группу пловцов, пытаясь также убедиться, что эти пловцы еще не в «Списке участников».

 List<Swimmer> swimmingList = //List generated from db;
List<SwimmerAttending> attendingList = //List generated from db;

for(SwimmerAttending s : attendinglist)
   {
     swimmingList = swimmingList.stream()
                                  .filter(i-> i.getSwimerNumber() > 1000 amp;amp; !s.getSwimmerNumber().equals(i.getSwimmerNumber()))
                                  .collect(Collectors.toList());   
   }
  

Итак, мне любопытно, есть ли более простой способ сделать это, просто используя потоки? Спасибо вам.

Комментарии:

1. swimmingList и attendingList хранить разные типы объектов, верно?

2. Да, я отредактирую, чтобы убедиться, что это более понятно выше.

Ответ №1:

Ваш код может быть упрощен и, что более важно, может выполняться за линейное время, O (n), в отличие от квадратичного времени, O (n ^ 2), в настоящее время, поскольку у вас есть цикл внутри цикла (итерация потока остается циклом, хотя и скрытым).

Чтобы получить линейное время, вы должны преобразовать свой список посещений в набор, который затем можно проверять в постоянное время. Как таковой:

 swimmingList = //List generated from db;
attendingList = //List generated from db;

Set<Integer> attendingSet = 
   attendingList.stream()
                .map(s -> s.getSwimmerNumber())
                .collect(Collectors.toCollection(HashSet::new));

swimmingList = swimmingList.stream()
                           .filter(i -> i.getSwimmerNumber() > 1000 
                                    amp;amp; !attendingSet.contains(i.getSwimmerNumber()))
                           .collect(Collectors.toList());
  

Преобразование в набор является линейным (один цикл), и фильтр также является линейным, что создает общий линейный алгоритм.

Если ваш список плавания содержит 1000 записей, а ваш список посещений содержит 1000 записей, ваш исходный код будет выполнять итерацию по каждой записи списка посещений, и для каждого участника выполните итерацию по записи 1000 списка плавания, что составляет в общей сложности 1 000 000 итераций. Новый код будет выполнять итерацию по списку посещений один раз (1000 итераций), а затем будет выполнять итерацию по плавающему списку один раз (1000 итераций), при этом проверка принадлежности выполняется для набора, который является постоянным временем для HashSet, что делает общее количество операций только 2000.

Комментарии:

1. Большое вам спасибо за это, и приведенный пример также является хорошим моментом. Я не осознавал, что существует такая разница при использовании set vs list при выполнении действий. Значит, не похоже, что мы можем делать все в одном потоке?

2. Да, разные типы коллекций имеют разные характеристики производительности (обычно документируемые в javadoc), которые следует учитывать для получения от них наилучшей производительности. Единственный способ, который я могу придумать, чтобы вы могли отказаться от первого преобразования потока, — это если ваш код чтения базы данных может возвращать список посещений уже в виде набора.

3. Спасибо, я действительно ценю вашу помощь!

4. Этот ответ верен только случайно; в Set интерфейсе нет ничего, что требовало contains бы эффективности, и общие to[Collection] сборщики явно не дают обещаний относительно реализаций. collect(toCollection(TreeSet::new)) , напротив, обеспечит гарантированный журнал (n) для операций. (В случае, я почти уверен, что toSet() это использует HashSet , так что это сработает , но это не гарантировано.)

5. Это правда, спасибо Chrylis, я предполагаю, что a HashSet будет возвращено, Collectors.toSet() что имеет место в проверенных мной реализациях JDK. Я редактирую свой ответ, чтобы убедиться, что всегда используется HashSet .

Ответ №2:

Сначала вы можете сопоставить attendinglist их с номерами пловцов:

 List<Integer> attendingSwimmerNumbers = 
    attendingList.stream()
        .map(SwimmerAttending::getSwimmerNumber)
        .collect(Collectors.toList());
  

Затем мы можем проверить, есть ли номер каждого пловца в приведенном выше списке, используя contains :

  swimmingList = swimmingList.stream()
                              .filter(i-> i.getSwimmerNumber() > 1000 amp;amp; attendingSwimmerNumbers.contains(i.getSwimerNumber()))
                              .collect(Collectors.toList());  
  

Все это предполагает, что swimmingList и attendingList хранить разные типы объектов. Если они хранят один и тот же тип, тогда может иметь смысл переопределить equals SwimmerAttending , чтобы проверить равенство номера пловца. Затем вы можете использовать contains непосредственно на attendingList :

  swimmingList = swimmingList.stream()
                              .filter(i-> i.getSwimmerNumber() > 1000 amp;amp; attendingList.contains(i))
                              .collect(Collectors.toList()); 
  

Комментарии:

1. Большое вам спасибо за этот ответ. Я ценю добавление проверки, если оба элемента имеют один и тот же тип.