【问题标题】:Stream through collection and filter using a priority使用优先级通过集合和过滤器进行流式传输
【发布时间】:2020-09-18 23:07:40
【问题描述】:

我正在尝试根据特定优先级过滤集合。 该集合由具有特定类型字段的实体组成。

所以,我想做以下事情: 通过集合迭代/流式传输和

  1. 找到第一个具有 getType = "type1" 的实体。如果此实体的价格 > 0,则返回此实体。否则

  2. 查找下一个具有 getType = "type2" 的实体。如果它的价格 > 0,则返回它。否则

  3. 查找下一个具有 getType = "type3" 的实体。如果它的价格 > 0,则返回它。

  4. 查找下一个具有 getType = "type4" 的实体。如果它的价格 > 0,则返回它。

  5. 查找下一个具有 getType = "type5" 的实体。如果它的价格 > 0,则返回它。

  6. 否则返回。

我正在尝试使用流来执行此操作,但过滤步骤是我无法创建这种优先级过滤类型的地方。我还尝试使用 for 遍历集合,但不确定如何实现 type1 的 findFirst 实体,然后是 type2 等。

【问题讨论】:

  • 请分享你试过的
  • 张贴您的代码,女士
  • @WJS 根据列出的规则,它应该返回找到的第一个。
  • @Andreas 是的,这将是使用问题中编写的规则的预期结果,但我仍然想从 OP 获得一些示例,因为这些条件感觉非常独特,这让我认为它们可能没有措辞正确,可能无法表达 OP 真正想要什么。
  • @Pshemo 我同意你的观点,这就是为什么我将我的评论设置为“根据列出的规则”。我实际上添加了评论,更多地展示了规则可能写得不好,因为如果这不是 OP 想要的结果,OP 应该对此做出反应。我们会看到,一旦 OP 最终重新上线。

标签: java java-8 java-stream


【解决方案1】:

如果你稍微改一下这个要求,它会使流的一系列操作的转换更加清晰 - 你应该返回第一个带有 price > 0 的项目,它具有最小的 type

MyEntity result = myList.stream()
                        .filter(e -> e.getPrice() > 0)
                        .min(Compartor.comparing(MyEntity::getType))
                        .orElse(null);

【讨论】:

  • 这可能是 OP 需要的,但这不是需求状态。找到first类型1,如果price > 0则停止搜索。否则寻找 next 类型 2,即类型 1 之后的类型 2。结论 1: 第一个之后的任何类型 1 都将被忽略。 结论2:第一个类型1之前的任何类型2都会被忽略。
  • @Andreas 这与要求有何冲突?任何答案的价格都必须 >0。然后你只取基于类型的最小对象?你能提供一个反例吗?
  • 使用myList = [type2[price=7], type1[price=0], type2[price=11]],代码返回type2[price=7]。如果您严格遵守规则: 1) “找到第一个具有 getType = "type1" 的实体。” 所以我们找到了type1[price=0]"如果该实体的价格 > 0 则返回该实体。否则" 由于价格不 > 0,否则我们这样做:2) "查找下一个具有 getType = 的实体type2"." type2 的 next 实体是type2[price=11]。 *“如果它的价格 > 0 则返回它。”所以我们必须返回type2[price=11]
  • 实际上,当有多个最小值的元素时,min() 返回的内容是不确定的。最常见的是,这样的操作返回第一个,但由于它是未定义,它可以返回type2[price=7]type2[price=11]。如果流是并行的,我相信结果确实是任意的,因为结果没有排序保证。 --- 结果还是偏离了严格的规则解释。
  • @WJS - 感谢您抽出宝贵时间回复。当我说 (Type1,null),(Type1,100),(Type2,10) 时,代码失败。它选择 Type2,10,因为 findFirst 选择了 Type1,null 而不是 Type1,100。我正在尝试过滤 findFirst 以包含价格,但它似乎无法正常工作
【解决方案2】:

原始 - 无效的缩减方法

一种选择是翻转操作顺序。例如:过滤“价格> 0”的位置,然后在遇到元素时减少流。例如:

stream.filter(elem -> elem.price > 0)
    .reduce((elem1, elem2) -> elem1.type.compareTo(elem2.type) < 0 ? elem1 : elem2)

编辑 - 更正确的缩减方法

一般来说,最好保持流操作无状态。因此,创建一个处理遍历列表并返回结果的函数可能是一种更正确的方法。使用流,可以定义一个自定义的“reducer”,它跟踪检查的先前类型以确定下一个结果是否是可能的有效匹配。一旦找到一个有效的匹配,它总是被返回。

    public static void main(String[] args)
    {
        List<Entity> data = Arrays.asList(eee(2, 6), eee(1, 0), eee(1, 10), eee(3, 7), eee(2, 0), eee(3, 5), eee(4, 0), eee(5, 0));
        System.out.println(data.stream().reduce(new Reducer()).filter(entity -> entity != Reducer.NO_MATCH));
    }

    /*Once a match is found, always use it. For a given type, only the first found entity of that type will be used*/
    public static final class Reducer implements BinaryOperator<Entity>
    {
        private int priorValidType;
        Reducer(){ this.priorValidType = 0; }

        @Override
        public Entity apply(Entity result, Entity newElem)
        {
            int nextValidType = priorValidType + 1;
            if(priorValidType > 0 && result != NO_MATCH) return result; /*Match already found, use it*/
            if(result.type == nextValidType && result.price > 0) { priorValidType = nextValidType; return result; } /*result is a match*/
            if(newElem.type == nextValidType && newElem.price > 0) { priorValidType = nextValidType; return newElem; } /*newElem is a match*/
            if(result.type == nextValidType || newElem.type == nextValidType) { priorValidType = nextValidType; }
            return NO_MATCH; /*No match has been found*/
        }

        public static final Entity NO_MATCH = new Entity(-1, -1);
    }

    public static final class Entity
    {
        private final int price, type;
        Entity(int type, int price){ this.price = price; this.type = type; }
        public String toString(){ return "(" + type + ", " + price + ")"; }
        int getPrice(){ return price; }
        int getType(){ return type; }
        public static Entity eee(int type, int price){ return new Entity(type, price); }
    }

编辑 - 使用过滤器的替代方法

可以创建一个过滤器,它执行类似于reduce 方法的操作,并且在调用“findFirst”时具有短路的好处。在下面,第一个过滤器只允许第一次遇到给定类型(按顺序)通过。第二个过滤器确认它是有效的。

    public static void main(String[] args)
    {
        List<Entity> data = Arrays.asList(eee(2, 6), eee(1, 0), eee(1, 10), eee(3, 7), eee(2, 0), eee(3, 5), eee(4, 0), eee(5, 0));
        System.out.println(data.stream().filter(new FirstTypeMatch()).filter(entity -> entity.price > 0).findFirst());
    }

    /*Filter where the element is the first of the given type*/
    public static final class FirstTypeMatch implements Predicate<Entity>
    {
        private int priorValidType = 0;
        @Override
        public boolean test(Entity nextElem)
        {
            if(nextElem.type == (priorValidType + 1)){ priorValidType++; return true; }
            return false;
        }
    }

【讨论】:

  • 该元素尚未定义为实现Comparable。即使是这样,它也没有被定义为主要由type 比较。即使是这样,reduce()min() 相同,所以我对answer by Mureinik 的这种类型的解决方案也适用于此。
  • 谢谢安德烈亚斯。调整上述内容以纠正可比问题。我倾向于同意这个问题,因为所述流可能不是最好的工具。
【解决方案3】:

根据Andreas 的澄清和对目前所述要求的严格解释,这应该可行。

  • 我基本上是流式传输types,然后再传输list
  • 按优先级顺序检查每个类型的内部流。
  • 如果找到的第一个价格为正值,则将其退回。
  • 否则检查下一个类型并重复该过程
  • 如果没有匹配项,则返回一个特殊对象。

用于验证的类。

class MyClass {
    private int price;
    private String type;
    
    public MyClass(int price, String type) {
        this.price = price;
        this.type = type;
    }
    
    public int getPrice() {
        return price;
    }
    
    public String getType() {
        return type;
    }
    
    public String toString() {
        return String.format("[%s, %s]", price, type);
    }
}

数据

List<MyClass> list = List.of(new MyClass(0, "Type1"),
        new MyClass(10, "Type2"), new MyClass(2, "Type1"),
        new MyClass(2, "Type4"), new MyClass(2, "Type5"),
        new MyClass(2, "Type2"), new MyClass(10, "Type1"),
        new MyClass(1, "Type2"), new MyClass(2, "Type5"));

String[] types =
        { "Type1", "Type2", "Type3", "Type4", "Type5" };

解决方案

MyClass selected = Arrays.stream(types)
        .map(type -> list.stream()
                .filter(obj -> obj.getType().equals(type))
                .findFirst()
                .orElse(null))
        .filter(a -> a != null && a.getPrice() > 0)
        .findFirst().orElse(new MyClass(-1, "Empty"));

System.out.println(selected);

打印

[10, Type2]

【讨论】:

  • 代码对此不起作用:(T2, 6), (T1, 0), (T1, 10), (T3, 7), (T2, 0), (T3, 5), (T4, 0), (T5, 0) --- 正确的结果应该是 (T3, 5),但代码打印的是 (T2, 6)
  • 所以你的意思是,如果唯一的T1在列表的末尾并且价格为0,那么无论前面有多少其他人,都不会选择任何对象,对!
  • 我的错误是我从头开始重新启动内部流,而不是从以前的失败开始。我错过了一个微妙之处。看看这一切是否都是 OP 的想法会很有趣。
  • 这是规则所说的,但是,是的,只有 OP 才能澄清它是否打算这样工作。当人们不能正确地写出需求时,这是非常令人恼火的,但是当他们这样做时你不能打客户的脸,一次又一次,你只需要硬着头皮向他们收取额外费用。 ?
  • 当我们有 2 个 T1 一个的价格为 null 和一个价格 >0 时,这不起作用。由于 firstFind,它似乎选择了 price = null 的那个。我试图调整过滤器以找到价格为 != null 且 >0 的 First type1 但它似乎不起作用
【解决方案4】:

我可以考虑使用stream先根据Type(假设type为小写)进行排序,然后使用filter过滤掉负价格,然后使用findFirst将其存储在Optional Entity中

Optional<Entity> entity = list.stream().sorted(Comparator.comparing(Entity::getType))
.filter( e -> e.price > 0 ).findFirst();
     

if(entity.isPresent()){
    System.out.println(entity.get().getType()); // test to verify 
}

【讨论】:

猜你喜欢
  • 2011-09-29
  • 1970-01-01
  • 1970-01-01
  • 2020-02-04
  • 2017-10-27
  • 2013-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多