Ramificação reduzida, sensoriamento robótico e complexidade ciclomática

Um código kata para aprender as melhores práticas

Sean Batir Blocked Unblock Seguir Seguindo 9 de janeiro fonte: Steve Johnson via pexels

Este é um kata de código rápido para ilustrar o princípio “reduzir ramificação” e aguçar seu apetite por detecção de robô em menos de 5 minutos.

Quando criamos um agente robótico, a solução ingênua é primeiro assumir que o robô tem uma probabilidade uniforme de "em qualquer lugar". Matematicamente, isso é modelado como uma probabilidade igual de que o robô se mova em qualquer direção. Para simplificar, vamos restringir isso a um caso de 1 dimensão, e digamos que nosso robô só pode viajar em uma linha reta de eixo 1-D / X e visitar 5 portas.

Se houver 5 portas e todas as probabilidades forem distribuídas uniformemente, a representação inicial do vetor, a localização do robô, é 1/5 = 0,2.

Então, supondo que você receba um vetor que represente suas probabilidades uniformes e ingênuas:

 probs = [0,2, 0,2, 0,2, 0,2, 0,2] 

Agora, os robôs detectam seu ambiente, que deve se alinhar com seu destino. Vamos dizer que dizemos ao nosso robô que ele só pode viajar para uma "porta rosa" em vez de uma porta azul.

Se nosso robô perceber que ele está em uma porta rosa, seus sensores dizem que há uma probabilidade de 0,90 que ele está de fato em uma porta rosa.

Então, vamos nos dar algumas variáveis adicionais que representam um sucesso "Hit" falhou "Miss", bem como uma variável que representa o nosso ambiente ou portas.

 portas = ['rosa', 'azul', 'azul', 'azul', 'rosa'] 
 T = 'rosa' 
 pHit = 0,90 
 pMiss = 0,10 

Seu objetivo é escrever uma função sensorial para o nosso robô, que avalie se o alvo T corresponde à porta em que está e multiplique a probabilidade de sentir a porta alvo contra a distribuição uniforme.

Abaixo, forneci duas soluções, em Python 3, com uma solução ingênua e uma que captura a essência “code kata” acima. um que é ingênuo e um poderia ser relatado como um pouco melhor.

Implementação Ingênua

 sentido de def (probs, T): 
 afterSense = [] 
 para eu no intervalo (len (probs)): 
 se portas [i] == T: 
 afterSense.append (probs [i] * pHit) 
 outro: 
 afterSense.append (probs [i] * pMiss) 
 retornar afterSense 

Melhor implementação

 sentido de def (probs, T): 
 afterSense = [] 
 para eu no intervalo (len (probs)): 
 acertar = (T = = portas [i]) 
 afterSense.append (probs [i] * (pressione * pHit + (1-hit) * pMiss)) 
 retornar afterSense 

Na melhor implementação, você se libera de realizar uma avaliação volumosa de 4 linhas se / então. Em vez disso, você gera um caminho linear independente em nosso código que pode ser percorrido e colapsá-lo em uma linha.

 afterSense.append (probs [i] * (pressione * pHit + (1-hit) * pMiss)) 

Isso porque quando avaliamos:

 golpes = (T = = portas [i]) 

O valor armazenado em ocorrências retornará um booleano True (1) ou um booleano False (0). Isso se alinha a um princípio geral de “bom estilo de codificação”, geralmente chamado de reduzir ou minimizar ramificações .

Em última análise, o objetivo é limitar o nível de complexidade ciclomática , que é outra métrica que mede o número de “ramificações” ou caminhos linearmente independentes.

A complexidade ciclomática de uma seção do código-fonte é o número de caminhos linearmente independentes dentro dela.

Se você é uma criatura visual, talvez essa figura seja útil para você.

Imagem adotada de McCabe, TJ, “Uma medida de complexidade”, IEEE Trans. em Engenharia de Software , SE-2 (4), pp.308-320 (1976)

Espero que isso ajude outros desenvolvedores e pessoas curiosas por aí! Sinta-se à vontade para me enviar uma mensagem ou comentar abaixo. Estou sempre aberto a comentários.