2015-06-03 2 views
6

La domanda è praticamente nel titolo. Sto cercando un algoritmo più efficiente della ricerca completa attraverso le raccolte.LEFT OUTER JOIN di due collezioni

ho due collezioni:

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

Dove

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

e

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

    //GET, SET 
} 

La collezione col1, che è del tipo List<Map<TypeId, Object> > contiene solo PLAYER, PARTNER, PLATFORMTypeId s.

ho bisogno di scrivere un metodo:

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

che sta per produrre List<Map<TypeId, Object> > ciascun l'ingresso entry della mappa contiene ulteriori valori-chiave (AMOUNT, AMOUNT's value) dove AMOUNT's value è il valore del amount campo dell'istanza e di Entity se e.player_id = entry.get(PLAYER) && e.platform_id = entry.get(PLATFORM) e null in caso contrario.

Infatti, l'operazione sarebbe uguale

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

ESEMPIO:

col1: 
[{PLATFORM: 1, PARTNER: 1, PLAYER: 1}, 
{PLATFORM: 1, PARTNER: 3, PLAYER: 1}, 
{PLATFORM: 2, PARTNER: 1, PLAYER: 2} 
{PLATFORM: 3, PARTNER: 4, PLAYER: 5}] 

col2: 
[Entity(platform_id = 1, player_id = 1, amount = 100), 
Entity(platform_id = 2, player_id = 2, amount = 200), 
Entity(platform_id = 3, player_id = 4, amount = 300)] 

result: 
[{PLATFORM: 1, PARTNER: 1, PLAYER: 1, AMOUNT: 100}, 
{PLATFORM: 1, PARTNER: 3, PLAYER: 1, AMOUNT: 100}, 
{PLATFORM: 2, PARTNER: 1, PLAYER: 2, AMOUNT: 200}, 
{PLATFORM: 3, PARTNER: 4, PLAYER: 5, AMOUNT: null}] 
+1

Ci aiuterebbe a visualizzare il requisito in un modo migliore se si forniscono dati di esempio che mostrano anche l'input e l'output previsto. Spesso è più semplice modellare oggetti e interrogarli per i dati se si dispone di dati di input e output di esempio. – CKing

+0

@ChetanKinger Potresti dare un'occhiata al campione? – user3663882

+0

Quindi, "PARTNER" non influisce sulla condizione di "join"? – CKing

risposta

1

È facile apportare modifiche in-place, modificando l'elenco col1 invece di creare nuovi List. Ecco Java-8 Soluzione:

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; 
} 

suppongo che la modifica del col1 in posto è soddisfacente in questo caso. Si noti che anche se si memorizza il risultato in una nuova lista, sarà inutile se si modificano le mappe esistenti. Quindi per rendere il risultato totalmente indipendente dallo col1, dovrai copiare tutte le mappe.

Si noti inoltre che non è molto efficace come per ogni voce col1 attraversa il col2, quindi la complessità è approssimativamente col1.size()*col2.size(). E 'probabilmente meglio nel vostro caso di buttare via una classe Entity e crearne uno nuovo che memorizza PlatformId e playerid solo (con buona esecuzione equals e hashCode) e usarlo come carta fondamentale:

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; 
    } 
} 

In questo modo, invece di col2 elencare si avrà un 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)); 

Ora il vostro compito può essere risolto facilmente ed efficacemente (anche con 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

La libreria Guava fornisce gli idiomi funzionali che sono perfetti per questo tipo di trasformazioni.Ecco un esempio di implementazione del metodo utilizzando Guava che non richiede la modifica della firma del metodo:

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; 
       } 
      }); 
} 

Ciò che è necessario, tuttavia, è quello di creare una classe EntityKey che identifica un'entità (analogo alla chiave primaria nel DB) . Questa classe può quindi essere utilizzata per l'implementazione di equals (e hashCode) in Entity, consentendo l'archiviazione di entità in una mappa di ricerca.

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; 
    } 
}