根据优先级从列表中过滤值

时间:2019-05-08 07:17:55

标签: java java-8

我有一个类型的有效值列表:

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具有TypeATypeB,我希望对象具有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 个答案:

答案 0 :(得分:4)

按类型过滤列表,按类型排序,收集到列表,然后只获取第一个元素

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));

答案 1 :(得分:2)

您可以使用自定义比较器,例如:

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进行排序。

答案 2 :(得分:1)

我不喜欢使用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;
    }

}

答案 3 :(得分:1)

当然可以定义两个列表,一个具有所有有效类型,而一个具有首选类型。

但是,这是另一种方法。定义一个列表,或者实际上是一个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,则此流操作会短路。

该选项的缺点是它会产生副作用which is discouraged

使用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>实例。 CarrierMap.Entry<K, V>,仅实现equals的{​​{1}}和hashCode方法; key无关紧要。这对于下一步是必要的,
  3. value,它丢弃所有重复的元素。这样,只能找到一种首选类型,而只有一种有效类型。
  4. 我们限制流包含2个元素,每个布尔值一个。这是因为在找到两个布尔值之后,惰性流将停止评估。
  5. 最后,我们将Carrier元素收集到Map中。

地图现在包含以下元素:

  • distinct() => Boolean.TRUE(首选类型)
  • A => Boolean.FALSE具有有效类型

使用检索适当的元素

A

答案 4 :(得分:0)

那么您必须要考虑到排序是稳定,即相等的元素将以与原始来源相同的顺序出现-您需要先正确获得满足您要求的df['string'] = df['string'].str.split(' ').map(lambda x: ' '.join(sorted(x)))中的元素,因此:

List<A>

答案 5 :(得分:0)

如果您在其他集合中有首选的有效类型,则可以遵循此代码。

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);

答案 6 :(得分:0)

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()));
    }
}