Implementando “Você quis dizer …?” Sugestões

Introdução Muitos softwares que analisam comandos (como Git) ou compiladores, quando você especifica algo que é desconhecido, talvez tendo feito um erro de digitação, ofereça “Você quis dizer …?” sugestões para serem úteis. Por exemplo, dado o seguinte programa C: #include int main () {print (“Hello, World! \ N”); } Digite o modo de tela cheia de tela cheia de tela cheia CLANG PARA PRIM: OOPS.C: 4: 3: Erro: Chamada para função não declarada ‘Print’ 4 | print (“Olá, mundo! \ n”); | ^ OOPS.C: 4: 3: NOTA: Você quis dizer ‘printf’? Digite o modo de saída do modo de tela cheia especificamente, ele imprime outros identificadores no escopo que são semelhantes ao desconhecido-o que eu chamo de sugestões de você mencionou-se-mean (DYMS). Eu queria adicionar dyms ao CDECL. Acontece que a maneira canônica de fazer isso é usando algo chamado Damerau -Levenshtein Distury (DLD) isto é: … o número mínimo de operações (consistindo em inserções, deleções ou substituições de um único caractere ou transposição de dois caracteres adjacentes) necessários para alterar uma palavra para a outra. Portanto, para ir de impressão para printf, a distância seria 1 (insira ‘f’). Neste artigo, não vou discutir os detalhes do algoritmo. Para isso, você pode se referir à página da Wikipedia ou a esta melhor explicação. Em vez disso, vou discutir como incorporar o DLD nos programas de maneira eficiente e eficaz. Damerau -Levenshtein API normalmente, o DLD é implementado em uma única função com a assinatura: size_t dam_lev_dist (char const *desconhecido, char const *conhecido); Digite o modo de saída do modo de tela cheia, ele retorna o DLD para ir do desconhecido para conhecido. O problema é que a função nunca é chamada apenas uma vez; É chamado repetidamente em um loop, uma vez para cada palavra -chave conhecida que você está tentando determinar se é o que o usuário quis dizer com desconhecido. Se você observar as implementações, todas elas alocam matrizes de char internamente para armazenamento parcial de resultados e as negociar. Se houver n palavras -chave, isso significa pelo menos N chamadas para Malloc e gratuitas. Melhor, seria calcular a quantidade máxima de memória para alocar para todas as n palavras -chave uma vez, alocá -la antes de ligar para DAM_LEV_DIST e depois chamar de graça uma vez depois de terminar. Para isso, podemos usar a função matrix2d_new: void* dam_lev_new (size_t desconhecido_len, size_t max_known_len) {return matrix2d_new (sizeof (size_t), alignof (size_t), desconhecido_len + 2, max_known_len + 2); } Digite o modo de saída do modo de tela cheia (o motivo do + 2s é incluir linhas e colunas extras para 0 e n, então acessando M[i][j] não precisa ser verificado de limites.) A API muda para: size_t dam_lev_dist (void *working_mem, char const *desconhecido, size_t unten, char const *conhecido, size_t klen); Digite o modo de tela completa Sair do modo de tela cheia Limitada Damerau – Levenshtein Distância, enquanto um DLD entre duas strings é geralmente útil, não é útil especificamente para oferecer sugestões. Por exemplo, dada uma sequência desconhecida de “fixo” e uma sequência conhecida de “flutuação” (com um DLD de 4 entre eles), provavelmente é seguro supor que, porque “fixo” é tão diferente de “flutuar” que não há como “flutuar”, sugerir “flutuar” para “fixo” parecer estranho. Seria melhor não oferecer sugestões do que sugestões não de perto. Portanto, os DLDs são úteis apenas para sugestões quando são limitadas (não muito grandes). Intuitivamente, um DLD útil deve ser relativo ao comprimento da corda conhecida: um DLD de 4 quando o comprimento é 5 é muito grande (80%), mas talvez não quando o comprimento é, digamos, 10 (40%). Portanto, uma sugestão é razoável apenas quando DLD ≤ Alguma porcentagem de Know_len: bool is_similar_enough (did_you_mean const *dym) {return dym-> dam_lev_dist <= (size_t)(dym->conhecido_len * similar_enough_percent + 0,5); } Digite o modo de saída da tela cheia empiricamente, determinei que o seguinte era um bom valor para similar_enough_percent para cdecl: estático duplo const_enough_percent = .37; Digite o modo de tela de tela cheia de saída de tela cheia API-API MEAN A MEI-YOU-MEAN Começaria com: struct did_you_mean {char const *conhecido; // candidato conhecido. size_t conhecido_len; // comprimento de conhecido. size_t dam_lev_dist; // DLD calculado. void *user_data; // dados opcionais do usuário. }; typedef struct did_you_mean did_you_mean_t; Digite o código de usuário do modo de saída de tela cheia de tela cheia alocaria dinamicamente uma matriz de DID_YOU_MEAN e definiria cada um conhecido por uma palavra -chave conhecida. Por exemplo, se o usuário digitar um comando desconhecido do Explai (faltando o n), o CDECL alocará uma matriz de did_you_mean, onde cada um conhecido é um comando CDECL conhecido, um de elenco, declarar, definir, expandir, explicar, ajudar, incluir, definir, mostrar, tipedef ou sair. A convenção que usaremos é que a matriz será uma mais longa que a lista de palavras -chave conhecidas, para que o último elemento possa saber o NULL para marcar o fim. Dado isso, podemos definir: typedef void (* dym_cleanup_fn_t) (did_you_mean_t const*); typedef bool (* dym_similar_fn_t) (did_you_mean_t const*); bool dym_calc (char const *desconhecido, did_you_mean_t *dym_array, dym_similar_fn_t similar_fn, dym_cleanup_fn_t cleanup_fn); Digite o modo de saída do modo de tela cheia. Para similar_fn, podemos passar e is_similar_enough; Cleanup_FN será discutido em breve. A implementação começa como: DID_YOU_MEAN_T *DYM; size_t max_known_len = 0; // Calcule os comprimentos máximos conhecidos para (dym = dym_array; dym-> conhecido! = null; ++ dym) {dym-> conhecido_len = strlen (dym-> conhecido); if (dym-> conhecido_len> max_known_len) max_known_len = dym-> conhecido_len; } // Calcule a distância damerau-levenshtein para todos os candidatos size_t const desconhecido_len = strlen (desconhecido); void *const dl_mem = dam_lev_new (desconhecido_len, max_known_len); para (dym = dym_array; dym-> conhecido! = null; ++ dym) {dym-> dam_lev_dist = dam_lev_dist (dl_mem, desconhecido, desconhecido_len, dym-> conhecido, dym-> conhecido_len); } grátis (dl_mem); // classificar por Damerau -Levenshtein Size_t const dym_size = (size_t) (dym – dym_array); Qsort (Dym_array, Dym_size, Sizeof Dym_array[0](qsort_cmp_fn_t) & dym_cmp); Digite o modo de saída do modo de tela cheia, que é bastante direta. A função DYM_CMP compara dois elementos DID_YOU_MEAN para QSORT: static int dym_cmp (did_you_mean_t const *i_dym, did_you_mean_t const *j_dym) {ssize_t const cmp = (ssize_t) i_dym-> dam) (ssize_t) j_dym-> dam_lev_dist); retornar cmp! = 0? (int) cmp: strcmp (i_dym-> conhecido, j_dym-> conhecido); } Digite Modo de tela cheia de saída Modo de tela cheia O restante da implementação DYM_CALC é: if (Dym_array-> DAM_LEV_DIST> 0) {bool found_at_least_1 = false; for (dym = dym_array; dym-> conhecido! = null; ++ dym) {if (! (*similar_fn) (dym)) quebra; encontrado_at_least_1 = true; } if (found_at_least_1) {// // Literais conhecidos grátis após os melhores e define // o passado o último a nulo para marcar o final. // DYM_CLEANUP_ALL (DYM, Cleanup_FN); *dym = (did_you_mean_t) {0}; retornar true; }} Digite o modo de saída de tela cheia de tela cheia se Dym_array[0].dam_lev_dist é zero, significa que o desconhecido era na verdade uma palavra -chave conhecida. (Nesse caso, oferecer sugestões nem deve ser feito.) Caso contrário, iterem através do Dym_array procurando o primeiro conhecido que não é semelhante o suficiente de acordo com o similar_fn, pare por aí, chame a função Clean_Up para limpar os elementos da matriz até o final do final, o final do final do final do final do final do final do final do final do final. void dym_cleanup_all (did_you_mean_t const *dym, dym_cleanup_fn_t cleanup_fn) {assert (dym! = null); if (cleanup_fn == null) retornar; while (dym-> conhecido! = null) (*limpeza_fn) (dym ++); } Digite o modo de saída do modo de tela full. Se Found_at_Least_1 for false, então: Dym_free (Dym_array, Cleanup_FN); retornar falso; Digite o modo de saída do modo de tela cheia, ou seja, libertamos todo o Dym_array. Por fim, precisamos: void dym_free (did_you_mean_t const *dym_array, dym_cleanup_fn_t cleanup_fn) {if (dym_array! grátis ((void*) dym_array); }} Digite o modo de saída da tela fullcreen FullScreen que o código do usuário deve ligar depois de fornecer todas as sugestões. Conclusão A distância de Damerau-Levenshtein é um algoritmo relativamente simples para oferecer sugestões de Did-You-Mean, mas não pode ser usado por si só para oferecer boas sugestões. A inclusão de tais sugestões nos programas os torna melhores para os usuários. Epílogo Algumas implementações incluem uma função de pontuação alternativa, além de DAM_LEV_DIST que verifica para ver se o conhecido é um prefixo de desconhecido, por exemplo, o UINT é um prefixo de Uint8_T: se sim, substitui o que Dam_Lev_dist teria calculado e retorna 1. Esta técnica pode incluir mais boas sugestões. Eu não incorporei isso no CDECL porque às vezes é bom demais, por exemplo, o UINT retornaria uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t, uintptr_t, uint_fast8_t,…, uint_least8_t,…, isso é um pouco muito. Depende do aplicativo específico. Se você quiser adicioná -lo, é deixado como um exercício para o leitor. O arquivo de origem relevante do CDECL é: os arquivos DAM_LEV e DID_YOU_MEAN são genéricos; Somente os arquivos CDECL_DYM são específicos para o CDECL.

Fonte

Você pode ter perdido