【问题标题】:How to translate list A,B to keyed map of tuples with guava如何使用番石榴将列表 A、B 转换为元组的键控映射
【发布时间】:2012-01-12 04:14:03
【问题描述】:

如果这个问题是重复的,我很抱歉,搜索很困难,因为我不确定我要完成的任务的正确名称。最简单的解释是

List<A>, List<B> into Map<Key, Tuple<A,B>> where A.Key matched B.Key

澄清一下:我有一个共享密钥的 A 对象和 B 对象的列表。然后我想将这两个列表关联到一个映射中,其中键匹配到一个键映射和元组 A,B。

我在脑海中想了很多关于如何做到这一点的想法,但其中大多数最终都让我觉得我误用了库(例如 Maps.uniqueIndex 和 Iterables.transform)。谁能指出我正确的方向?

【问题讨论】:

标签: java functional-programming guava


【解决方案1】:

Guava 中没有元组(对等)实现。 (如果在 Java 中实现元组是个好主意,那就另当别论了。)我建议的自然映射是使用 Multimap

List<A> as = Lists.newArrayList(new A(1, "a"), new A(3, "c"), new A(2, "b"));
List<B> bs = Lists.newArrayList(new B(1, 2), new B(3, 6), new B(5, 10));

Function<WithKey, Object> toKey = new Function<WithKey, Object>() {
    @Override public Object apply(WithKey input) { return input.key(); }
};
ImmutableListMultimap<Object, AbstractWithKey> index = 
    Multimaps.index(Iterables.concat(as, bs), toKey);

Multimap<Object, WithKey> m = ArrayListMultimap.create();
for (WithKey w : Iterables.concat(as, bs)) m.put(w.key(), w);

在使用多重映射之前(或在迭代多重映射条目时),您必须检查不变量,例如可能只有 A 或 B 实例的键。 (这不应该是性能问题,因为它可以用 Iterables.filter 懒惰地完成。)

一种类型的重复是另一个问题。您可以检查它们或使用 HashMultimap 来忽略它们。您甚至可以为检查一个值是否唯一的值构建一个带有约束集的多图(参见Multimaps.newSetMultimap(Map> map, Supplier> factory)Constraints.constrainedSet(Set set, Constraint constraint))。这样做的好处是失败速度很快。

使用这些 A 和 B 实现:

interface WithKey {
    Object key();
}
abstract class AbstractWithKey implements WithKey {
    Object key;
    Object v;
    @Override public Object key() { return key; }
    @Override public String toString() { 
        return MoreObjects.toStringHelper(this).add("k", key).add("v", v).toString(); 
    }
}
class A extends AbstractWithKey {
    public A(int i, String v) { 
        key = i;
        this.v = v;
    } 
}
class B extends AbstractWithKey {
    public B(int i, int v) { 
        key = i;
        this.v = v;
    }
}

输出是:

{1=[A{k=1, v=a}, B{k=1, v=2}], 2=[A{k=2, v=b}], 3=[A{ k=3, v=c}, B{k=3, v=6}], 5=[B{k=5, v=10}]}

更新:

如果您必须以元组实例结束,您可以转换 Multimap。

Multimap<Object, WithKey> m = ArrayListMultimap.create(); 
for (WithKey w : Iterables.concat(as, bs)) m.put(w.key(), w);

Function<Collection<WithKey>, Tuple> f = 
    new Function<Collection<WithKey>, Tuple>(){
    @Override public Tuple apply(Collection<WithKey> input) {
        Iterator<WithKey> iterator = input.iterator();
        return new Tuple(iterator.next(), iterator.next());
    } };
Map<Object, Tuple> result = Maps.transformValues(m.asMap(), f);

输出((a,b) 是元组语法):

{1=(A{k=1, v=a},B{k=1, v=2}), 3=(A{k=3, v=c},B{k=3, v=6})}

【讨论】:

  • 我喜欢这个答案,但我希望以一个可以在 JSTL 中迭代的对象结束。如果我正确理解这一点,则 Multimap 包含一个值列表。如果我使用此解决方案并在传递给视图之前调用 asMap,是否有任何简单的方法可以区分什么是 A,什么是 B?
  • 我必须考虑一下。如果映射中的值存储在一个列表中,您从插入顺序中知道第一个值是 A 类型,第二个值如果是 B 类型。但这中断得太快,而且对我的口味来说不够明确。您还可以创建一个由“元组”支持的 Multiset(我不会是一个真正的元组,只是一个让您检索 A 和 B 实例的集合实现)。应该有更好的办法。
【解决方案2】:

你能保证键是唯一的吗? (也就是说,没有两个 A 有相同的密钥?)

如果是这样,我会写如下内容:

Map<Key, A> aMap = Maps.uniqueIndex(theAs, aKeyFunction); // Guava!
Map<Key, B> bMap = Maps.uniqueIndex(theBs, bKeyFunction);

Map<Key, AWithMatchingB> joinedMap = Maps.newHashMap();
for(Map.Entry<Key, A> aEntry : aMap.entrySet()) {
  joinedMap.put(aEntry.getKey(), AWithMatchingB.match(
     aEntry.getValue(), bMap.get(aEntry.getKey())));
}

如果你不能保证 aMap.keySet().equals(bMap.keySet()),那么你应该适当地修改它:检查是否有匹配的 B,等等。

【讨论】:

  • 作为参考,我看到您觉得这个解决方案可能会“滥用库”——作为 Guava 开发人员,这正是我会做的。
【解决方案3】:

在没有 Guava 太多帮助的情况下按键对列表进行排序并将两个列表转换为元组非常易读:

Comparator<WithKey>c = new Comparator<WithKey>(){
    @Override public int compare(WithKey o1, WithKey o2) {
        return o1.key().compareTo(o2.key());
    }
};
Collections.sort(as, c);
Collections.sort(bs, c);

Preconditions.checkArgument(as.size() == bs.size());

Iterator<A> aIt = as.iterator();
Iterator<B> bIt = bs.iterator();
Map<Integer, Tuple> ts = Maps.newHashMap();
while(aIt.hasNext()) {
    A a = aIt.next();
    B b = bIt.next();
    Preconditions.checkArgument(a.key().equals(b.key()));
    ts.put(a.key(), new Tuple(a, b));
}

输出((a,b) 是元组语法):

{1=(A{k=1, v=a},B{k=1, v=2}), 3=(A{k=3, v=c},B{k=3, v=6})}

Guava supports zip 类似 Python 时可以更好地实现:

sa = [(1, "a"), (3, "c")]
sb = [(1, 2), (3, 6)]

sa.sort()
sb.sort()

vs = [(a[0], (a,b)) for (a, b) in zip(sa, sb)]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2013-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-08
    相关资源
    最近更新 更多