Por que um HashMap deve ser usado (em funções) para determinar qual valor retornar (para uma chave) quando uma construção if else pode fazer o trabalho em tempo melhor?

9

Enquanto eu trabalhava recentemente em uma grande empresa, notei que os programadores seguiram esse estilo de codificação:

Suponha que eu tenha uma função que retorne 12 se a entrada for A, 21 se a entrada for B e 45 se a entrada for C.

Para que eu possa escrever a assinatura da função como:

int foo(String s){
    if(s.equals("A"))      return 12;
    else if(s.equals("B")) return 21;
    else if(s.equals("C")) return 45;
    else throw new RuntimeException("Invalid input to function foo");
}

Mas, na revisão de código, fui solicitado a alterar a função para o seguinte:

int foo(String s){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 12);
    map.put("B", 21);
    map.put("C", 45);
    return map.get(s);
}

Não consigo me convencer de por que o segundo código é melhor que o primeiro. O segundo código definitivamente levaria mais tempo para ser executado.

A única razão para usar o segundo código pode ser que ele oferece melhor legibilidade. Mas se a função estiver sendo chamada várias vezes, a segunda função não diminuiria o tempo de execução do utilitário que a chamava?

O que você pensa sobre isso?

Nikunj Banka
fonte
4
Para três valores, um mapa parece um exagero ( switchparece mais apropriado que if-else). Mas, em algum momento, isso se torna problemático. A principal vantagem de usar um mapa é que você pode carregá-lo de um arquivo ou tabela, etc. Se você estiver codificando a entrada para o mapa, não estou vendo muito valor em um comutador.
precisa saber é o seguinte

Respostas:

16

O objetivo é mover a criação do hashmap para fora da função e fazê-lo uma vez (ou apenas menos vezes do que o contrário).

private static final Map<String, Integer> map;
static{
    Map<String, Integer> temp = new HashMap<String, Integer>();
    temp.put("A", 12);
    temp.put("B", 21);
    temp.put("C", 45);
    map = Collections.unmodifiableMap(temp);//make immutable
}

int foo(String s){
    if(!map.containsKey(s))
        throw new RuntimeException("Invalid input to function foo");

    return map.get(s);
}

No entanto, desde o java7, o java7 conseguiu seqüências de caracteres (finais) nos switches:

int foo(String s){
    switch(s){
    case "A":
        return 12;
    case "B": 
        return 21;
    case "C": 
        return 45;
    default: throw new RuntimeException("Invalid input to function foo");
}
catraca arrepiante
fonte
11
Não vejo como isso responde à pergunta dos OPs, então -1 está lá. Mas +1 por sugerir mudança.
user949300
Ele mostra como implementar adequadamente o estilo de codificação para realmente fazer sentido e possivelmente melhorar o desempenho. Ainda não faz sentido para três opções, mas o código original provavelmente foi muito mais longo.
Florian F
12

No seu segundo exemplo, o Mapdeve ser um membro estático privado para evitar sobrecarga de inicialização redundante.

Para grandes quantidades de valores, o mapa terá um desempenho melhor. Usando uma hashtable, pode-se procurar a resposta em tempo constante. O construto múltiplo se precisa comparar a entrada com cada uma das possibilidades até encontrar a resposta certa.

Em outras palavras, a pesquisa de mapa é O (1) enquanto ifs são O (n) onde n é o número de entradas possíveis.

A criação do mapa é O (n), mas é feita apenas uma vez se for um estado constante estático. Para uma pesquisa realizada com freqüência, o mapa superará as ifinstruções a longo prazo, ao custo de um pouco mais de tempo quando o programa estiver iniciando (ou a classe for carregada, dependendo do idioma).

Dito isto, o mapa nem sempre é a ferramenta certa para este trabalho. É bom quando existem muitos valores, ou os valores devem ser configuráveis ​​por meio de um arquivo de texto, entrada do usuário ou banco de dados (nesse caso, o mapa atua como um cache).


fonte
Sim, para grandes quantidades de valores, o mapa terá um desempenho melhor. Mas a quantidade de valores é fixa e são três.
RemcoGerlich
a criação do mapa é O (N), apenas a pesquisa é O (1).
Pieter B
Bons pontos, esclareci minha resposta.
O mapa também requer o desembaraço automático, o que afeta levemente o desempenho.
user949300
@ user949300 em Java, sim, e o código na pergunta parece ser Java. No entanto, ele não está marcado com nenhum idioma, e a abordagem funciona em vários idiomas (incluindo C # e C ++, nenhum dos quais requer boxe).
3

Existem duas velocidades no software: o tempo necessário para escrever / ler / depurar o código; e o tempo necessário para executar o código.

Se você pode me convencer (e seus revisores de código) de que a função hashmap é realmente mais lenta que a if / then / else (depois de refatorar para criar um hashmap estático) E você pode me convencer / revisores de que é necessário tempo suficiente para fazer uma atualização real diferença, então vá em frente e substitua o hashmap pelo if / else.

Caso contrário, o código hashmap é eminentemente legível; e (provavelmente) livre de erros; você pode determinar isso rapidamente apenas olhando para ele. Você não pode realmente dizer a mesma coisa sobre o if / else sem realmente estudá-lo; a diferença é ainda mais exagerada quando existem centenas de opções.

Robert Hanson
fonte
3
Bem, essa objeção cai por terra quando se compara com um interruptor em vez ...
Deduplicator
Também se desfaz se você escrever as instruções if em uma única linha.
gnasher729
2
Ao colocar a criação do hashmap em outro lugar, você está dificultando a compreensão do que realmente acontece. Você precisa ver essas chaves e valores para saber qual é o efeito real da função.
RemcoGerlich
2

Eu prefiro muito a resposta no estilo HashMap.

Há uma métrica para isso

Existe uma métrica de qualidade de código chamada Complexidade Ciclomática . Essa métrica conta basicamente o número de caminhos diferentes através do código ( como calcular a Complexidade Ciclomática ).

Para cada caminho de execução possível, um método se torna cada vez mais difícil de entender E testar completamente a correção.

Tudo se resume ao fato de que "controlar palavras-chave" como: ifs, elses, whiles, etc ... utiliza testes booleanos que podem estar errados. O uso repetido de "controlar palavras-chave" produz código frágil.

Benefícios adicionais

Além disso, a "abordagem baseada em mapa" incentiva os desenvolvedores a pensar nos pares de entrada e saída como um conjunto de dados que pode ser extraído, reutilizado, manipulado em tempo de execução, testado e verificado. Por exemplo, abaixo reescrevi "foo" para não ficarmos permanentemente presos em "A-> 12, B-> 21, C-> 45":

int foo(String s){
    HashMap<String, Integer> map = getCurrentMapping();
    return map.get(s);
}

rachet_freak menciona esse tipo de refator em sua resposta, ele defende velocidade e reutilização, estou defendendo a flexibilidade do tempo de execução (embora o uso de uma coleção imutável possa ter enormes benefícios dependendo da situação)

Ivan
fonte
11
O acréscimo dessa flexibilidade de tempo de execução é uma ótima ideia prospectiva ou um excesso de engenharia desnecessário que torna muito mais difícil descobrir o que está acontecendo. :-).
user949300
@JimmyJames O link funciona para mim: É: leepoint.net/principles_and_practices/complexity/...
Ivan
11
@ user949300 O ponto é que os dados de chave / valor que apoiam o método "foo" são um conceito separado que poderia merecer alguma forma de clareza. A quantidade de código que você deseja escrever para isolar o mapa dependerá muito de quantos itens o mapa contém e com que frequência esses itens precisam ser alterados.
Ivan
11
@ user949300 Parte do motivo pelo qual sugiro retirar a cadeia if-else é que eu vi cadeias if-else em grupos. Mais ou menos como baratas, se houver um método baseado em uma cadeia if-else, provavelmente haverá outro método em outra parte da base de código com uma cadeia if-else semelhante / idêntica. A extração do mapa gera dividendos se considerarmos que existem outros métodos que usam uma estrutura lógica semelhante à pesquisa / troca.
Ivan
Eu também vi duplicados, quase idênticos se / else blocos espalhados por todo o lugar. Bem colocado
Jon Chesterfield
1

Os dados são melhores que o código. Não menos importante, porque é muito tentador adicionar mais um ramo ao código, mas é difícil errar a adição de uma linha a uma tabela. Esta questão é uma pequena instância disso. Você está escrevendo uma tabela de pesquisa. Escreva uma implementação, completa com lógica e documentação condicionais, ou escreva a tabela e procure nela.

Uma tabela de dados é sempre uma representação melhor de alguns dados do que o código, pois a otimização do módulo passa. Quão difícil é a expressão da tabela pode depender da linguagem - eu não conheço Java, mas espero que possa implementar uma tabela de pesquisa de maneira mais simples do que o exemplo no OP.

Esta é uma tabela de pesquisa em python. Se isso é visto como um convite a conflitos, considere que a pergunta não está marcada como java, a refatoração é independente de idioma e que a maioria das pessoas não conhece java.

def foo(s):
    return {
               "A" : 12,
               "B" : 21,
               "C" : 45,
           }[s]

A idéia de reestruturar o código para reduzir o tempo de execução tem mérito, mas prefiro usar um compilador que eleva a configuração comum do que eu mesmo.

Jon Chesterfield
fonte
-1 não é uma resposta para a pergunta.
Pieter B
Em que sentido? Eu teria pedido ao autor que substituísse a cadeia if else por um mapa com base em que dados e código deveriam ser separados. Essa é uma razão clara pela qual o segundo código é melhor que o primeiro, embora todas as chamadas map.put sejam lamentáveis.
Jon Chesterfield
2
@ JonChesterfield Esta resposta é basicamente "use uma linguagem melhor", o que raramente é útil.
walpen
@walpen fair point. Ivan fez praticamente a mesma observação via Java, então eu claramente não precisei usar o python. Vou ver se consigo limpá-lo um pouco
Jon Chesterfield
Em alguns códigos java mais antigos, eu precisava de alguns mapas para algo muito semelhante, então escrevi um pequeno utilitário para converter matrizes N-dimensionais de matrizes 2D em um mapa. Um pouco hacky, mas funcionou bem. Esta resposta mostra o poder do Python / JS no suporte à notação JSONy de maneira simples e fácil.
precisa saber é o seguinte