【问题标题】:Filter stream using correlation between stream elements使用流元素之间的相关性过滤流
【发布时间】:2018-04-26 12:02:47
【问题描述】:

假设有一个如下所示的Person 类:

public class Person {
    private int id;
    private String discriminator;
    // some other fields plus getters/setters
}

现在我有一个StreamPerson 元素,并且该流可能包含多个Person 实例,它们具有相同的id,但不同的discriminator 值,即[Person{“id”: 1, “discriminator”: “A”}, Person{“id”: 1, “discriminator”: “B”}, Person{“id”: 2, “discriminator”: “A”}, ...]

如果至少有一个 Person 实例的 id 具有特定的鉴别器值,我想做的是过滤掉所有具有某个 id 的 Person 实例。因此,继续上面的示例,按鉴别器值“A”过滤将产生一个空集合(当然是在归约操作之后),按鉴别器值“B”过滤将产生一个不包含任何带有 id 的 Person 实例的集合等于 1。

我知道我可以使用groupingBy 收集器减少流并通过Person.id 对元素进行分组,然后如果映射列表包含具有指定鉴别器值的Person 元素,则从生成的Map 中删除映射,但是还在想是否有更简单的方法可以达到同样的效果?

【问题讨论】:

    标签: java java-stream reduction


    【解决方案1】:

    如果我正确理解了您的问题,您将首先找到与鉴别器匹配的所有IDs:

    Set<Integer> ids = persons.stream()
           .filter(p -> "A".equalsIgnoreCase(p.getDiscriminator()))
           .map(Person::getId)
           .collect(Collectors.toSet())
    

    然后删除与这些匹配的条目:

    persons.removeIf(x -> ids.contains(x.getId()))
    

    【讨论】:

    • 可能Collectors.toSet()会更好?
    • @123-xyz 不这么认为,OP 没有显示任何哈希码/等号;我不想假设
    • 是的,您的解决方案正是我想要实现的目标,但我想在不创建任何中间数据结构(如您的示例中的 ids 集合)的情况下做到这一点......如果这可能的话。
    • @jigga 我对此表示怀疑,问题是您需要遍历它一次而不实际影响它以获得正确的Ids;起初我在考虑进行一些就地删除,但这会破坏问题的完整性
    • @Eugene:用Set&lt;Integer&gt; 替换List&lt;Integer&gt; 不需要OP 方面的任何努力,Integer 确实已经有一个合适的hashCode() 实现。由于您为每个人调用ids.contains(…),这显然会提高性能。
    【解决方案2】:

    尤金斯的回答效果很好,但我个人更喜欢单一的陈述。所以我把他的代码整合到一个操作中。看起来像这样:

    final List<Person> result = persons.stream()
        .filter(p -> "B".equalsIgnoreCase(p.getDiscriminator()))
        .map(Person::getId)
        .collect(
            () -> new ArrayList<>(persons),
            ( list, id ) -> list.removeIf(p -> p.getId() == id),
            ( a, b ) -> {throw new UnsupportedOperationException();}
        );
    

    可能需要提到persons的副本是必需的,否则Stream会损坏并遇到null值。

    旁注:当前版本在尝试并行使用时抛出UnsupportedOperationException

    【讨论】:

    • 我没有看到“单个语句”比另一个答案的两个语句更大的优势,更不用说执行将遍历的removeIf 的效率低下整个列表可能每个元素(也称为二次时间复杂度)。
    【解决方案3】:

    所以下面我将介绍我想出的解决方案。首先,我按 Person.id 属性对输入的 Person 集合/流进行分组,然后流过映射条目并过滤掉那些至少有一个值与给定鉴别器匹配的条目。

    import java.util.Arrays;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    public class Main {
    
        public static void main(String[] args) {
            List<Person> persons = Arrays.asList(
                new Person(1, "A"),
                new Person(1, "B"),
                new Person(1, "C"),
                new Person(2, "B"),
                new Person(2, "C"),
                new Person(3, "A"),
                new Person(3, "C"),
                new Person(4, "B")
            );
    
            System.out.println(persons);
            System.out.println(filterByDiscriminator(persons, "A"));
            System.out.println(filterByDiscriminator(persons, "B"));
            System.out.println(filterByDiscriminator(persons, "C"));
        }
    
        private static List<Person> filterByDiscriminator(List<Person> input, String discriminator) {
            return input.stream()
                .collect(Collectors.groupingBy(Person::getId))
                .entrySet().stream()
                .filter(entry -> entry.getValue().stream().noneMatch(person -> person.getDiscriminator().equals(discriminator)))
                .flatMap(entry -> entry.getValue().stream())
                .collect(Collectors.toList());
        }
    
    }
    
    class Person {
    
        private final Integer id;
        private final String discriminator;
    
        public Person(Integer id, String discriminator) {
            Objects.requireNonNull(id);
            Objects.requireNonNull(discriminator);
            this.id = id;
            this.discriminator = discriminator;
        }
    
        public Integer getId() {
            return id;
        }
    
        public String getDiscriminator() {
            return discriminator;
        }
    
        @Override
        public String toString() {
            return String.format("%s{\"id\": %d, \"discriminator\": \"%s\"}", getClass().getSimpleName(), id, discriminator);
        }
    }
    

    【讨论】:

    • 加一个用于创造力。请注意,当您不处理密钥时,可以使用values() 而不是entrySet(),这样就无需一直调用entry.getValue()… values().stream() .filter(list -&gt; list.stream().noneMatch(person -&gt; person.getDiscriminator() .equals(discriminator))) .flatMap(List::stream) .collect(Collectors.toList());
    猜你喜欢
    • 2018-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    相关资源
    最近更新 更多