两个集合的左外部联接

时间:2015-06-03 06:55:17

标签: java collections

问题几乎在标题中。我正在寻找一种比通过集合完全搜索更有效的算法。

我有两个系列:

List<Map<TypeId, Object> > col1;
List<Entity> col2;

哪里

public enum TypeId{
    PLAYER,
    PARTNER,
    PLATFORM,
    AMOUNT
}

public class Entity{
    private int player_id;
    private int platform_id
    private BigDecimal amount;

    //GET, SET
}

col1类型的List<Map<TypeId, Object> >集合仅包含PLAYER, PARTNER, PLATFORM TypeId个。

我需要写一个方法:

public List<Map<TypeId, Object> > merge(List<Map<TypeId, Object> > col1, List<Entity> col2){
    //Impl
}

每个地图条目List<Map<TypeId, Object> >将生成entry,其中包含其他键值(AMOUNT, AMOUNT's value),其中AMOUNT's valueamount的值e的{​​{1}}实例的字段,如果Entity,则e.player_id = entry.get(PLAYER) && e.platform_id = entry.get(PLATFORM)。{/ p>

实际上,操作与

相同
null

示例:

col1 LEFT OUTER JOIN 
col2 ON e.player_id = entry.get(PLAYER) && e.platform_id = entry.get(PLATFORM)

2 个答案:

答案 0 :(得分:1)

更容易进行就地更改,修改col1列表而不是创建新的List。这是Java-8解决方案:

public List<Map<TypeId, Object> > merge(List<Map<TypeId, Object> > col1, List<Entity> col2){
    col1.forEach(map -> map.put(TypeId.AMOUNT, 
        col2.stream()
            .filter(e -> e.player_id == (int)map.get(TypeId.PLAYER) && 
                         e.platform_id == (int)map.get(TypeId.PLATFORM))
            .findFirst().map(e -> e.amount).orElse(null)
        ));
    return col1;
}

我认为在这种情况下,更改col1到位是令人满意的。请注意,即使将结果存储到新列表中,如果修改现有映射也无效。因此,要使结果完全独立于col1,您必须复制所有地图。

另请注意,对于遍历col1的每个col2条目,它不是很有效,因此复杂度大致为col1.size()*col2.size()。在你的情况下,最好扔掉Entity类并创建一个只存储platformId和playerId的新类(正确实现equalshashCode)并将其用作映射键:

public static class PlatformAndPlayer {
    private final int playerId, platformId;

    public PlatformAndPlayer(int playerId, int platformId) {
        this.playerId = playerId;
        this.platformId = platformId;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + platformId;
        result = prime * result + playerId;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        PlatformAndPlayer other = (PlatformAndPlayer) obj;
        if (platformId != other.platformId)
            return false;
        if (playerId != other.playerId)
            return false;
        return true;
    }
}

通过这种方式代替col2列表,您将获得Map

Map<PlatformAndPlayer, BigDecimal> col2 = new HashMap<>();
col2.put(new PlatformAndPlayer(1, 1), BigDecimal.valueOf(100));
col2.put(new PlatformAndPlayer(2, 2), BigDecimal.valueOf(200));
col2.put(new PlatformAndPlayer(3, 4), BigDecimal.valueOf(300));

现在您的任务可以轻松有效地解决(即使使用Java 5):

public static List<Map<TypeId, Object>> merge(
        List<Map<TypeId, Object>> col1,
        Map<PlatformAndPlayer, BigDecimal> col2) {
    for (Map<TypeId, Object> map : col1) {
        map.put(TypeId.AMOUNT, col2.get(new PlatformAndPlayer(
            (int) map.get(TypeId.PLAYER), (int) map.get(TypeId.PLATFORM))));
    }
    return col1;
}

答案 1 :(得分:1)

Guava库提供了适合这些转换的功能习惯用法。以下是使用Guava的方法的示例实现,不需要更改方法签名:

public List<Map<TypeId, Object>> merge(List<Map<TypeId, Object>> col1,
        List<Entity> col2) {                

    // create a lookup table for getting the amounts
    // based on entities (entity keys)
    final Map<Entity, BigDecimal> entityLookupTable = Maps.toMap(col2,
             new Function<Entity, BigDecimal>() {
                 @Override
                 public BigDecimal apply(Entity entity) {
                     return entity.getAmount();
                 }
    });

    // transform the col1 list using a transform function 
    // that adds the AMOUNT fetched from the lookup table to each entry map
    return Lists.transform(col1, new Function<Map<TypeId, Object>,
                                             Map<TypeId, Object>>() {

        @Override
        public Map<TypeId, Object> apply(Map<TypeId, Object> typeToValueMap) {

                    Entity keyWrapper = new Entity(
                            new EntityKey(
                                (Integer) typeToValueMap.get(TypeId.PLAYER),
                                (Integer) typeToValueMap.get(TypeId.PLATFORM)),
                            null);

                    typeToValueMap.put(TypeId.AMOUNT,
                            entityLookupTable.get(keyWrapper));

                    return typeToValueMap;
                }
            });
}

但是,需要创建一个标识实体的EntityKey类(类似于DB中的主键)。然后,可以使用此类在equals中实现hashCode(和Entity),从而在查找映射中存储实体。

public class EntityKey {

    private int player_id;
    private int platform_id;

    public EntityKey(int player_id, int platform_id) {
        this.player_id = player_id;
        this.platform_id = platform_id;
    }

    /* Generated by Eclipse */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + platform_id;
        result = prime * result + player_id;
        return result;
    }

    /* Generated by Eclipse */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        EntityKey other = (EntityKey) obj;
        if (platform_id != other.platform_id)
            return false;
        if (player_id != other.player_id)
            return false;
        return true;
    }        
}

public class Entity {

    private EntityKey key;
    private BigDecimal amount;

    public Entity(EntityKey key, BigDecimal amount) {
        this.key = key;
        this.amount = amount;
    }      

    /* Generated by Eclipse */
    /* Simply delegates to EntityKey */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((key == null) ? 0 : key.hashCode());
        return result;
    }

    /* Generated by Eclipse */
    /* Simply delegates to EntityKey */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Entity other = (Entity) obj;
        if (key == null) {
            if (other.key != null)
                return false;
        } else if (!key.equals(other.key))
            return false;
        return true;
    }

    /**
     * @return the amount
     */
    public BigDecimal getAmount() {
        return amount;
    }
}