【问题标题】:Filtering a list based on a fluctuating number of conditions根据波动数量的条件过滤列表
【发布时间】:2019-06-23 03:54:45
【问题描述】:

我想编写能够使用多个条件过滤对象列表的代码,但我希望条件的数量可以波动(有时过滤 1 个字段,有时过滤所有 3 个字段)。

在我的情况下,我希望用户可以选择为某个字段选择“任何”,但仍然对其他字段应用过滤器。 在以下情况下,用户可以通过字段x, y & z 过滤列表,但随后他们也可以将其中一个过滤器更改为“任何”,从而在实际过滤列表时不需要该过滤器。 getFilteredList 方法从每个组合框中获取 String 值并使用每个 String 过滤列表,除非 String 是“任何”,那么它将接受来自该字段的任何 String

我在下面编写了代码,这些代码适用于这种情况,但在将来添加更多过滤器时会变得越来越低效。 (2 个过滤器产生 4 个返回语句,3 个过滤器产生 8 个等等......)。

private static ArrayList<Model> getFilteredList(ArrayList<Model> originalList, String x, String y, String z ){

    if(x.equals("any") && y.equals("any") && z.equals("any")) {
        return originalList;
    }
    if(x.equals("any") && y.equals("any") && !z.equals("any")) {
        return originalList.stream().filter(item -> item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new));
    }
    if(x.equals("any") && !y.equals("any") && z.equals("any")) {
        return originalList.stream().filter(item -> item.getY().equals(y)).collect(Collectors.toCollection(ArrayList::new));
    }
    if(x.equals("any") && !y.equals("any") && !z.equals("any")) {
        return originalList.stream().filter(item -> item.getY().equals(y)&& item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new));
    }
    if(!x.equals("any") && y.equals("any") && z.equals("any")) {
        return originalList.stream().filter(item -> item.getX().equals(x)).collect(Collectors.toCollection(ArrayList::new));
    }
    if(!x.equals("any") && y.equals("any") && !z.equals("any")) {
        return originalList.stream().filter(item -> item.getX().equals(x)&& item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new));
    }
    if(!x.equals("any") && !y.equals("any") && z.equals("any")) {
        return originalList.stream().filter(item -> item.getX().equals(x)&& item.getY().equals(y)).collect(Collectors.toCollection(ArrayList::new));
    }
    if(!x.equals("any") && !y.equals("any") && !z.equals("any")) {
        return originalList.stream().filter(item -> item.getX().equals(x)&& item.getY().equals(y)&& item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new));
    }
    return originalList;
}

Model 的外观如下:

public class Model {
    private String x;
    private String y;
    private String z;

    public String getX() {
        return x;
    }
    public String getY() {
        return y;
    }
    public String getZ() {
        return z;
    }
}

下面的代码将返回一个列表,其中x 字段等于“red”,y 字段等于“medium”,z 字段可以是任何值。

private static ArrayList<Model> items = new ArrayList<>();

public static void main(String[] args) {
    ArrayList<Model> filteredList = getFilteredList(items, "red", "medium", "any");
}

我不确定是否有更简单的方法来执行我的getFilteredList 方法,我已经搜索了很多但找不到任何更简单/更有效的实现。在未来,我希望允许 10 多个过滤器始终可以选择“任何”,但我目前的解决方案太长而且根本不会优雅。如果有人可以提供有关如何优雅地过滤具有不同数量过滤器的列表的任何答案,将不胜感激。

谢谢。

【问题讨论】:

  • 至少不是==,而是equals
  • 创建您自己的equalsOrAny() 方法,您将不需要您的ifs..
  • @PM77-1 你能详细说明一下,你建议我应该在哪里使用equalsOrAny() 方法?
  • 您可以一次只应用一个过滤器吗?如果不是“any”,则首先在 X 上过滤整个列表,如果不是“any”,则在 Y 上过滤结果列表,如果不是“any”,则在 Z 上过滤该列表。无需不必要地增加过滤器的组合。
  • @DavidRTribble 是的,他可以链接过滤器。我已经编写了一些测试用例,并且 Kes 对 getFilteredList 的实现产生了与我的答案相同的输出。

标签: java arraylist filter java-stream


【解决方案1】:

您可以为每个要过滤的String 及其对应的Model 属性在流上链接过滤器调用。

stringFilter BiFunction 确保如果该属性的过滤器为 "any" 或过滤器等于相应 Model 字段中的值,则模型将包含在结果流中。
由于每个过滤器都对最后一个过滤器的输出进行操作,因此我们可以确保返回的列表中不会包含不满足所有所需条件的 Model

public List<Model> getFilteredList(List<Model> originalList, String x, String y, String z) {

    final BiPredicate<String, Supplier<String>> stringFilter = (filter, stringSupplier) ->
            filter.equals("any") || filter.equals(stringSupplier.get());

    return originalList.stream()
            .filter(model -> stringFilter.test(x, model::getX))
            .filter(model -> stringFilter.test(y, model::getY))
            .filter(model -> stringFilter.test(z, model::getZ))
            .collect(Collectors.toList());
}

Supplier&lt;T&gt; 类型是在java.util.function 中定义的功能接口(只有1 个方法的接口),当调用其get() 方法时将返回T 类型的实例。

当我们过滤流时,我们将要过滤的字符串之一传递给我们的 BiPredicate。 getter 通过引用传递,并将作为我们的Supplier 的字符串源。

【讨论】:

  • 既然它是一个谓词,那么BiPredicate&lt;String, Supplier&lt;String&gt;&gt; 可能比BiFunction&lt;String, Supplier&lt;String&gt;, Boolean&gt; 更合适吗?
  • 好收获!不知道 BiPredicate ?
  • 我将不得不对 java 中的Predicate 进行一些研究,以便在测试所提供的任何解决方案之前完全理解它们。感谢您的帮助,我稍后再回复。
  • 谢谢你的回答,它激励我提高我在这方面的知识,现在我的眼睛已经打开了函数式接口和 lambda 表达式的力量。至于你的回答@DanielW。它的可读性很强,并且在许多人都可以理解的水平上进行了解释,再次感谢!
【解决方案2】:

你可以使用

private static List<Model> getFilteredList(
    ArrayList<Model> originalList, String x, String y, String z ){

    if(Stream.of(x, y, z).allMatch("any"::equals)) return originalList;

    Stream<Model> s = originalList.stream();
    if(!"any".equals(x)) s = s.filter(m -> m.getX().equals(x));
    if(!"any".equals(y)) s = s.filter(m -> m.getY().equals(y));
    if(!"any".equals(z)) s = s.filter(m -> m.getZ().equals(z));
    return s.collect(Collectors.toList());
}

但是您应该对返回类型做出决定。 ArrayList 的显式返回类型表明调用者接收到一个可变列表,但是当列表有时是原始列表而有时不是时,根据过滤条件,修改具有未定义的语义,因为它们可能会影响原始列表或不,那将是灾难性的。

所以你必须决定是否

  • 返回的列表不应该被修改(不要将其声明为ArrayList,也不要强制创建ArrayList),这就是上面的示例所做的,或者

  • 返回的列表应该是可变列表而不影响源,然后去掉all-"any"的返回原始列表的优化,或者

  • 原始列表在此之后应该不再使用,或者

  • 对返回列表的修改应始终影响原始列表

对于 c),合同无法在方法中执行,对于 d),使用 Stream API 是不可能的。所以对于 c) 或 d),最好还是修改原来的列表,例如

private static void filterList(ArrayList<Model> list, String x, String y, String z) {
    Predicate<Model> any = m -> false, effective = any;
    if(!"any".equals(x)) effective = m -> !m.getX().equals(x);
    if(!"any".equals(y)) effective = effective.or(m -> !m.getY().equals(y));
    if(!"any".equals(z)) effective = effective.or(m -> !m.getZ().equals(z));
    if(effective != any) list.removeIf(effective);
}

这不会对语义产生任何疑问。

【讨论】:

    【解决方案3】:

    您可以使用辅助方法使用给定的字段和值构造谓词,然后合并这些谓词并将其应用于流:

    private static ArrayList<Model> getFilteredList(ArrayList<Model> originalList, String x, String y, String z ){
        Predicate<Model> filter = Stream.of(compare(Model::getX, x), compare(Model::getY, y), compare(Model::getZ, z))
                .filter(Objects::nonNull)
                .reduce(Predicate::and)
                .orElse($ -> true);
    
        return originalList.stream()
                .filter(filter)
                .collect(Collectors.toCollection(ArrayList::new));
    }
    
    private static Predicate<Model> compare(Function<Model, String> field, String value) {
        return value.equals("any") ? null : m -> field.apply(m).equals(value);
    }
    

    【讨论】:

      【解决方案4】:

      当增加过滤条件的数量时,这是一种线性增长,而不是指数增长的方式:

      private static ArrayList<Model> getFilteredList(ArrayList<Model> originalList, String x, String y, String z){
          Predicate<Model> valueCheck, modelCheck = null;
          if ((valueCheck = (x.equals("any") ? null : item -> x.equals(item.getX()))) != null)
              modelCheck = valueCheck;
          if ((valueCheck = (y.equals("any") ? null : item -> y.equals(item.getY()))) != null)
              modelCheck = (modelCheck != null ? modelCheck.and(valueCheck) : valueCheck);
          if ((valueCheck = (z.equals("any") ? null : item -> z.equals(item.getZ()))) != null)
              modelCheck = (modelCheck != null ? modelCheck.and(valueCheck) : valueCheck);
          if (modelCheck == null)
              return originalList; // No filtering needed
          return originalList.stream().filter(modelCheck).collect(Collectors.toCollection(ArrayList::new));
      }
      

      它依赖于使用 and(...) 辅助方法构建复合过滤器,即 Predicate

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-10-07
        • 2021-07-21
        • 2022-12-22
        • 2019-08-03
        • 1970-01-01
        • 2019-10-23
        相关资源
        最近更新 更多