Фильтровать значения из списка на основе приоритета - PullRequest
11 голосов
/ 08 мая 2019

У меня есть список допустимых значений для типа:

Set<String> validTypes = ImmutableSet.of("TypeA", "TypeB", "TypeC");

Из данного списка я хочу извлечь первое значение, имеющее допустимый тип. В этом сценарии я бы написал что-то вроде этого:

public class A{
    private String type;
    private String member;
}

List<A> classAList;
classAList.stream()
    .filter(a -> validTypes.contains(a.getType()))
    .findFirst();

Однако я бы хотел отдать предпочтение TypeA, т. Е. Если classAList имеет TypeA и TypeB, я хочу объект, который имеет typeA. Для этого у меня есть один подход:

Set<String> preferredValidTypes = ImmutableSet.of("TypeA");
classAList.stream()
    .filter(a -> preferredValidTypes.contains(a.getType()))
    .findFirst()
    .orElseGet(() -> {
        return classAList.stream()
        .filter(a -> validTypes.contains(a.getType()))
        .findFirst();
    }

Есть ли лучший подход?

Ответы [ 7 ]

4 голосов
/ 08 мая 2019

фильтровать список по типу, упорядочивать по типу, собирать в список, а затем просто получить первый элемент

List<A> collect = classAList.stream()
                  .filter(a -> validTypes.contains(a.getType()))
                  .sorted(Comparator.comparing(A::getType))
                  .collect(Collectors.toList());
System.out.println(collect.get(0));
2 голосов
/ 08 мая 2019

Вы можете использовать собственный компаратор, например:

Comparator<A> comparator = (o1, o2) -> {
    if (preferredValidTypes.contains(o1.getType()) && !preferredValidTypes.contains(o2.getType())) {
        return 1;
    } else if (!preferredValidTypes.contains(o1.getType()) && preferredValidTypes.contains(o2.getType())) {
        return -1;
    } else {
        return 0;
    }
};

, чтобы отсортировать список, а затем findFirst из этого списка с вашим условием.

1 голос
/ 08 мая 2019

Можно, конечно, определить два списка, один со всеми допустимыми типами и один с предпочтительными типами.

Однако, здесь есть другой подход. Определите один список, или фактически, Map, с ключами, являющимися допустимыми типами, и логическими значениями, определяющими, является ли тип предпочтительным.

Map<String, Boolean> validTypes = ImmutableMap.of(
    "TypeA", false,
    "TypeB", false,
    "TypeC", true
);

Использование AtomicReference

Один из следующих вариантов:

AtomicReference<A> ref = new AtomicReference<>();
listOfAs.stream()
    .filter(t -> validTypes.containsKey(t.getType()))
    .anyMatch(t -> validTypes.get(ref.updateAndGet(u -> t).getType()));

AtomicReference теперь содержит предпочтительный A, если доступен, или другой допустимый A, или, если поток пустой, то он содержит null. Эта потоковая операция замыкается, если найден A с предпочтительным типом.

Недостатком этой опции является то, что она создает побочные эффекты , , которые не поощряются .

Использование distinct()

Еще одно предложение будет следующим. Он использует ту же структуру карты, используя логическое значение, чтобы указать, какие значения являются предпочтительными. Однако не создает побочных эффектов .

Map<Boolean, A> map = listOfAs.stream()
    .filter(t -> validTypes.containsKey(t.getType()))
    .map(t -> new Carrier<>(validTypes.get(t.getType()), t))
    .distinct()
    .limit(2)
    .collect(Collectors.toMap(Carrier::getKey, Carrier::getValue));

Работает следующим образом.

  1. filter удаляет любой элемент, который не является допустимым типом.
  2. Затем каждый элемент сопоставляется с Carrier<Boolean, A> экземпляром. Carrier - это Map.Entry<K, V>, который реализует свои методы equals и hashCode, относящиеся только к key; value не имеет значения. Это необходимо для следующего шага,
  3. distinct(), который отбрасывает любой дублирующий элемент. Таким образом, будет найден только один предпочтительный тип и только один допустимый тип.
  4. Мы ограничиваем поток двумя элементами, по одному для каждого логического значения. Это потому, что поток, который является ленивым, перестает оценивать после того, как оба логических значения найдены.
  5. Наконец, мы собираем элементы Carrier в карту.

Карта теперь содержит следующие элементы:

  • Boolean.TRUE => A с предпочтительным типом
  • Boolean.FALSE => A с допустимым типом

Получить соответствующий элемент, используя

A a = map.getOrDefault(true, map.get(false)); // null if not found
1 голос
/ 08 мая 2019

Мне не нравятся уже даные ответы, в которых используется Comparator.Сортировка - дорогая операция.Вы можете сделать это с одной прогулки по списку.Как только вы найдете предпочтительное значение, вы можете выйти из него, в противном случае вы продолжите до конца, чтобы найти действительное значение.

В этом случае anyMatch может предоставить возможность выйти из потока обработки:

MyVerifier verifier=new MyVerifier(validTypes,preferredValidTypes);
classAList.stream()
    .anyMatch(verifier);
System.out.println("Preferred found:"+verifier.preferred);
System.out.println("Valid found:"+verifier.valid);

public static class MyVerifier implements Predicate<A> {
    private Set<String> validTypes;
    private Set<String> preferredValidTypes;
    A preferred=null;
    A valid=null;

    public MyVerifier(Set<String> validTypes, Set<String> preferredValidTypes) {
        super();
        this.validTypes = validTypes;
        this.preferredValidTypes = preferredValidTypes;
    }

    @Override
    public boolean test(A a) {
        if(preferred==null && preferredValidTypes.contains(a.getType())) {
            preferred=a;
            // we can stop because we found the first preferred
            return true;
        } else if(valid==null && validTypes.contains(a.getType())) {
            valid=a;
        }
        return false;
    }

}
0 голосов
/ 08 мая 2019

В Java8 вы можете использовать потоки:

public static Carnet findByCodeIsIn(Collection<Carnet> listCarnet, String codeIsIn) {
    return listCarnet.stream().filter(carnet -> codeIsIn.equals(carnet.getCodeIsin())).findFirst().orElse(null);
}

Кроме того, если у вас много разных объектов (не только Carnet) или вы хотите найти его по разным свойствам (не только по cideIsin), вы можете создать служебный класс, чтобы заключить в него эту логику :

public final class FindUtils {
    public static <T> T findByProperty(Collection<T> col, Predicate<T> filter) {
        return col.stream().filter(filter).findFirst().orElse(null);
    }
}

public final class CarnetUtils {
    public static Carnet findByCodeTitre(Collection<Carnet> listCarnet, String codeTitre) {
        return FindUtils.findByProperty(listCarnet, carnet -> codeTitre.equals(carnet.getCodeTitre()));
    }

    public static Carnet findByNomTitre(Collection<Carnet> listCarnet, String nomTitre) {
        return FindUtils.findByProperty(listCarnet, carnet -> nomTitre.equals(carnet.getNomTitre()));
    }

    public static Carnet findByCodeIsIn(Collection<Carnet> listCarnet, String codeIsin) {
        return FindUtils.findByProperty(listCarnet, carnet -> codeIsin.equals(carnet.getCodeIsin()));
    }
}
0 голосов
/ 08 мая 2019

Если у вас есть предпочтительные допустимые типы в другой коллекции, вы можете следовать этому коду.

Map<String,A> groupByType = classAList
           .stream()
            /* additional filter to grouping by valid types.*/
          //.filter(a->validTypes.contains(a.getType())) 
           .collect(Collectors.toMap(A::getType, Function.identity(),(v1, v2)->v1));

затем используйте:

A result = preferredValidTypes
            .stream()
            .map(groupByType::get)
            .findFirst()
            .orElseThrow(RuntimeException::new);

или просто группировать по предпочтительным допустимым типам

 A result2 = classAList
            .stream()
            .filter(a -> preferredValidTypes.contains(a.getType()))
            .collect(Collectors.toMap(A::getType, Function.identity(), (v1, v2) -> v1))
            .entrySet()
            .stream()
            .findFirst()
            .map(Map.Entry::getValue)
            .orElseThrow(RuntimeException::new);
0 голосов
/ 08 мая 2019

Хорошо, вы должны позаботиться о том, чтобы сортировка была стабильной , то есть равные элементы будут появляться в том же порядке, что и исходный источник - и вам нужно это, чтобы правильно получить first элемент из этого List<A>, который удовлетворит ваше требование, таким образом:

String priorityType = "TypeB";

Stream.of(new A("TypeA", "A"),
          new A("TypeB", "B"),
          new A("TypeC", "C"))
      .sorted(Comparator.comparing(A::getType, Comparator.comparing(priorityType::equals)).reversed())
      .filter(x -> validTypes.contains(priorityType))
      .findFirst()
      .orElseThrow(RuntimeException::new);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...