Mapas aninhados x chaves combinadas

8

no projeto em que estou trabalhando, tivemos três tipos diferentes de preços, dependendo da idade do usuário (adulto, criança, etc.). Então, tivemos no banco de dados uma tabela parecida com esta:

PRICES
type     Amount
A         20
B         15
C         ..
D         ..

No começo, tínhamos apenas quatro tipos diferentes de preços; portanto, no código, tínhamos algo parecido com isto:

Map<String, BigDecimal> prices = new HashMap<String, BigDecimal>();

Onde as chaves eram o tipo de preço.

Recentemente, eles adicionaram uma nova regra de negócios que adiciona três subtipos a cada tipo de preço; agora, temos algo parecido com isto:

PRICES
type   subtype  Amount
A          1      20
A          2      15
A          3      ..
B          1      ..
B          2      ..
...        ..     ..

Qual das duas opções a seguir você acha que é melhor e por quê?

Mapas aninhados

Map<String, Map<String, BigDecimal>> prices;

onde as chaves são o tipo e subtipo de preço:

prices.get(type).get(subtype);

Chaves combinadas

O mesmo mapa que originalmente:

Map<String, BigDecimal> prices;

E concatene as chaves para indexar os diferentes preços:

prices.get(type+"_"+subtype);
mario595
fonte
Essa é mais uma questão de design e não há código suficiente para a Revisão de Código.
200_success
Uma pequena observação: sua chave combinada proposta pode levar a problemas futuros, por exemplo, necessitando de um '_' em um tipo de preço. Em vez disso, considere ter uma class PriceKey{ PriceType type; PriceSubtype subtype; }chave. Este pode então ser facilmente prorrogado
Caleth

Respostas:

8

As chaves aninhadas e combinadas têm seus lugares. bowmore fornece um argumento pro para chaves compostas e um argumento contra para mapas aninhados. Deixe-me fornecer a oposição leal:

As teclas de mapa composto funcionam muito bem quando você procura um item conhecido específico.

Os mapas aninhados funcionam bem quando você precisa encontrar rapidamente todas as variações, tipos e subtipos do tipo A. Por exemplo, escolher A (vs. B, C, ...) pode ser o primeiro passo em uma árvore de decisão. Depois que o usuário ou algoritmo ou o que quer que escolher A, você precisará conhecer apenas os subtipos de A e B..Z ou B..ZZZZZ não importam mais.

Agora você está lidando com uma estrutura de pesquisa muito rígida e eficiente para a sub-pesquisa. Se você tentar fazer isso com chaves compostas, acabará fazendo uma verificação completa da tabela a la [ (key, value) for (key, value) in prices.items() if key.startswith('A') ]. Essa não é uma operação eficiente e será lenta se o mapa for grande.

Mapas aninhados também funcionam bem quando o número de níveis de aninhamento pode aumentar. A estrutura do problema já foi estendida de typepara (type, subtype). Existe alguma chance de a próxima rev precisar (type, subtype, variation)ou (type, subtype, version)? Nesse caso, uma abordagem de mapeamento aninhado pode ser estendida de maneira limpa. Essa, no entanto, é uma vantagem estilística de segunda ordem, especialmente se comparada à vantagem da "fácil sub-pesquisa" acima.

Jonathan Eunice
fonte
verdade seja dita, a pesquisa incremental por chaves compostas pode evitar uma 'verificação de tabela' usando um SortedMap.
Bowmore
Você pode melhorar as características de pesquisa de chaves compostas classificando o mapa. Mas você não pode evitar todas as verificações dessa maneira. A localização da chave "RXR_1_a" em uma tabela ou o mapeamento classificado de 10.000 chaves, por exemplo, ainda requer a localização do índice da tabela onde "RXR_ *" começa e termina. Mesmo com a pesquisa binária, isso não é hora zero. Também requer os mesmos key.startswith('RXR')testes que eu mencionei. Os mapas aninhados são sempre operações O (1) e não exigem a sobrecarga de pré-classificação do mapa ou comparações de cadeias.
27616 Jonathan Eunice
É isso mesmo, eu disse que você poderia evitar uma operação no estilo 'mesa digitalizada'. Na minha resposta, também aponto que, para esse tipo de pesquisa, é mais fácil consultar o banco de dados diretamente. Ambas as abordagens terão problemas com a pesquisa de uma chave parcial que não inicia no primeiro campo, e um banco de dados pode aliviar isso com índices adicionais.
Bowmore
Um SortedMap pode evitar uma pesquisa linear completa, mas não uma pesquisa de alto custo. Não vejo como a operação de penhorar em um banco de dados ajuda; isso pode simplificar o código do cliente, talvez, mas à custa de solicitações externas lentas e estruturas de dados extras (os índices necessários para tornar a operação ainda razoavelmente eficiente). Uma estrutura aninhada tem algumas desvantagens, com certeza, mas é independente e altamente eficiente, com ou sem suporte a banco de dados de backup.
27616 Jonathan Eunice
Como uma pesquisa binária é uma pesquisa de alto custo?
Bowmore
6

Evite mapas aninhados. Eles são mais difíceis de dimensionar e levam a códigos muito detalhados e difíceis de ler com todas as declarações genéricas aninhadas em andamento.

Mais importante, os mapas em Java tendem a consumir muita memória. Preencher um mapa com ainda mais mapas agravará o problema de consumo de memória.

Por fim, é mais fácil pensar em um mapa que usa chaves compostas.

O uso de chaves compostas facilitará sua vida nos casos mais comuns, mas algumas coisas serão mais difíceis. Obter todos os preços de um componente-chave específico, por exemplo, mas é mais provável que você consulte esse resultado diretamente do banco de dados, em vez de destilá-lo do Mapa.

Bowmore
fonte
Concordo com sua afirmação, no entanto, às vezes, mapas aninhados são inevitáveis ​​e, nesse cenário, não encontrei uma boa maneira de manipulá-los em Java. Sempre que precisamos ler / manipular algo genérico em Java, principalmente arquivos JSON, você precisa de mapas aninhados, a menos que queira codificar a estrutura do seu arquivo no aplicativo.
Froginvasion
1

Isso tem menos a ver com "qual implementação é melhor" e mais com "com qual abstração devo trabalhar".

Tanto a chave composta quanto o mapa de mapas têm seus pontos fortes e fracos, todos residindo no domínio do desempenho (ou seja, velocidade / uso de memória). Eles não diferem em sua funcionalidade: ambos pegam dois valores e retornam o valor "put" anteriormente.

Como eles são funcionalmente equivalentes, a primeira coisa que você deve fazer é abstrair sobre eles. Não se preocupe com qual é o melhor. Crie uma interface DoubleKeyedMap com todos os métodos necessários e use-a no seu código. Em seguida, escreva qualquer implementação que puder escrever mais rapidamente e siga em frente.

SOMENTE depois de escrever seu aplicativo e descobrir que sua implementação de chave composta não é filtrada na primeira chave muito rapidamente ou que o mapa de mapas está consumindo muita memória, você deve otimizar.

A otimização prematura é a raiz de todo mal. Não abstrair é pior.

Ben Seidel
fonte
Bem dito. Não abstrair é pior! Eu adoro isso
Zack Bartel
0

Ambas as opções não são boas na minha opinião. Diga e se a lógica de negócios mudar novamente para que você tenha outro subtipo?

O que eu sugiro que você faça é o seguinte:

  1. Use uma chave substituta para uma chamada de Typetabela.Type(INT auto_inc id, VARCHAR(255) name, VARCHAR(255) desc, INT status, etc)
  2. Na sua Pricetabela, use chave estrangeira na Typetabela acima, para que pareçaPrice(INT type_id, INT price)
  3. Verifique se NÃO suporta a exclusão de um tipo. Você simplesmente marca um tipo inactiveporque , devido às referências pendentes, a exclusão seria uma verdadeira dor de cabeça

Agora, a lógica do usuário e da empresa pode adicionar qualquer combinação de subtipo de tipo ao esquema. O que eles precisariam fazer é simplesmente criar uma nova linha na Typetabela e alterar namee / ou descna tabela antiga.

No seu caso, o usuário seria renomear tipo Apara tipo A_1, adicionar um novo tipo chamado A_2e definir novo preço para A_2a15

InformedA
fonte