【问题标题】:Grouping by two properties and mapping to different object按两个属性分组并映射到不同的对象
【发布时间】:2016-11-11 22:30:33
【问题描述】:

我有以下数据:

uuid    id1 id2 hId hName       percent golden
1       J   K   a   fetchflow   38%     34%
2       J   K   b   codelibs1   45%     34%
3       J   K   c   codelibs2   97%     34%
10      K   L   a   fetchflow   16%     10%
11      K   L   b   codelibs1   95%     10%
12      K   L   c   codelibs2   12%     10%
13      K   M   a   fetchflow   64%     14%
14      K   M   b   codelibs1   53%     14%
15      K   M   c   codelibs2   48%     14%

想要达到这个目的:

Compare To  Golden  a   b   c
J       K   34%     38% 45% 97%
K       L   10%     16% 95% 12%
K       M   14%     64% 53% 48%

注意:Pair(id1, id2) == Pair(id2, id1),所以它们可以互换。

我想将它存储在以下 java 数据结构中:

class Foo {
    int id1;
    int id2;
    double golden;
    /*
        [a -> 0.38,
        b -> 0.45,
        c -> 0.97]
    */
    Map<Integer, Double> comparisons;
}

我目前有以下代码,但我无法将其映射到我想要的数据结构:

comparisons
        .stream()
        .collect(
                groupingBy(
                        Function.identity(),
                        () -> new TreeMap<>(
                                Comparator.<ComparisonResultSet, Integer>comparing(o -> o.vacancy_id_1).thenComparing(o -> o.vacancy_id_2)
                        ),
                        collectingAndThen(
                                reducing((o, o2) -> o), Optional::get
                        )
                ));

【问题讨论】:

  • 您的比较器不能使 id1id2 互换。
  • 1:是的,我注意到了。我可能会为 id1 和 id2 使用可互换的元组。 2:new Foo(J, K, 0.34, [a=0.38; b=0.45; c=0.97]new Foo(K, L, 0.1, [a=0.16; b=0.95; c=0.12]; new Foo(K, M, 0.14, [a=0.64; b=0.53; c=0.48]

标签: java sql lambda java-8 java-stream


【解决方案1】:

一个解决方案,或者说是起点,是

List<Foo> result = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
            o -> Arrays.asList(o.vacancy_id_1, o.vacancy_id_2),
            Collectors.toMap(o -> o.hId, o -> Arrays.asList(o.percent, o.golden))),
    m -> m.entrySet().stream().map(e -> new Foo(
            e.getKey().get(0), e.getKey().get(1),
            e.getValue().values().stream().mapToDouble(l->l.get(1))
                    .reduce((a,b)->{assert a==b; return a; }).getAsDouble(),
            e.getValue().entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, en->en.getValue().get(0)))
    )).collect(Collectors.toList())
));

它只使用标准的 Collection 类,这使事情变得复杂。它按Arrays.asList(o.vacancy_id_1, o.vacancy_id_2) 分组,这意味着ID 的排序。您可以用new HashSet&lt;&gt;(…) 包装它以获得与顺序无关的密钥,但是,当涉及Foo 实例的构造时,这会使解决方案复杂化,因为需要专用的id1id2。即

List<Foo> result = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
            o -> new HashSet<>(Arrays.asList(o.vacancy_id_1, o.vacancy_id_2)),
            Collectors.toMap(o -> o.hId, o -> Arrays.asList(o.percent, o.golden))),
    m -> m.entrySet().stream().map(e -> {
        Iterator<Integer> it = e.getKey().iterator();
        return new Foo(
            it.next(), it.next(),
            e.getValue().values().stream().mapToDouble(l->l.get(1))
                    .reduce((a,b)->{assert a==b; return a; }).getAsDouble(),
            e.getValue().entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, en->en.getValue().get(0)))
        );
    }).collect(Collectors.toList())
));

请注意,new HashSet&lt;&gt;(Arrays.asList(o.vacancy_id_1, o.vacancy_id_2)) 在 Java 9 中可以替换为 Set.of(o.vacancy_id_1, o.vacancy_id_2)

专用的与顺序无关的对类型将使操作更简单,尤其是当您从一开始就将源类型和结果类型中的两个 id 属性替换为该类型的单个属性时。

另一个障碍是“黄金”属性。没有它,下游收集器将是Collectors.toMap(o -&gt; o.hId, o -&gt; o.percent),为Foo 结果生成所需的映射。由于我们必须在此处携带另一个属性,因此映射需要在“黄金”属性被归约为单个值之后进行后续转换。

使用类似的pair类

public final class UnorderedPair<T> {
    public final T a, b;

    public UnorderedPair(T a, T b) {
        this.a = a;
        this.b = b;
    }
    public int hashCode() {
        return a.hashCode()+b.hashCode()+UnorderedPair.class.hashCode();
    }
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof UnorderedPair)) return false;
        final UnorderedPair<?> other = (UnorderedPair<?>) obj;
        return a.equals(other.a) && b.equals(other.b)
            || a.equals(other.b) && b.equals(other.a);
    }
}

还有来自this answerpairing 收集器,我们得到

List<Foo> result = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
        o -> new UnorderedPair<>(o.vacancy_id_1, o.vacancy_id_2),
            pairing(
                Collectors.toMap(o -> o.hId, o -> o.percent),
                Collectors.reducing(null, o -> o.golden,
                    (a,b) -> {assert a==null || a.doubleValue()==b; return b; }),
            (m,golden) -> new AbstractMap.SimpleImmutableEntry<>(m,golden))),
    m -> m.entrySet().stream().map(e -> new Foo(
        e.getKey().a, e.getKey().b, e.getValue().getValue(), e.getValue().getKey()))
    .collect(Collectors.toList())
));

但是,如前所述,在 source 和 result 中具有无序对类型的单个属性将大大简化任务。

【讨论】:

  • 注意:至少 IntelliJ 不能在倒数第二行 .map(e -&gt; .... 中推断出正确的类型,但代码仍然可以正常编译和运行。
【解决方案2】:

我考虑到 id1 和 id2 和 gold 是相同的,id1 和 id2 可以互换。

这个怎么样:

list.stream().collect(Collectors.collectingAndThen(Collectors.groupingBy(struct -> {
        String first = struct.getId1();
        String second = struct.getId2();

        if (first.compareTo(second) > 0) {
            return ImmutableList.of(first, second, struct.getGolden());
        }
        return ImmutableList.of(second, first, struct.getGolden());

    }, Collectors.toMap(Structure::getHId, Structure::getPercentage)),
            elem -> elem.entrySet().stream().map(entry -> {
                ImmutableList<?> values = entry.getKey();
                return new Foo((String) values.get(0), (String) values.get(1), (Integer) values.get(2),
                        entry.getValue());
            }).collect(Collectors.toList())));

那个可互换的钥匙让事情有点难看。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-29
    相关资源
    最近更新 更多