Em poucas palavras
Sem conhecer bastante a literatura, elaborei uma solução que será apresentada na próxima seção, juntamente com uma prova da parte mais difícil. Depois que soubesse o que era necessário, eu poderia procurar na literatura as idéias certas. Aqui está uma rápida apresentação do algoritmo, com base na literatura, que é essencialmente a mesma que desenvolvi.
A primeira coisa a fazer é encontrar uma cadeia de terminais de tamanho mínimo
σ(U) para todos os não terminais Uda gramática. Isso pode ser feito usando a extensão de Knuth para conc-ou gráficos (também conhecidos como gramáticas CF) e-ou gráficos do algoritmo de caminho mais curto de Dijkstra . O exemplo B do artigo de Knuth faz quase o necessário.
Na verdade, Knuth calcula apenas o comprimento dessas cadeias de terminais, mas é bastante fácil modificar seu algoritmo para realmente calcular uma dessas cadeias de terminais σ(U) para cada não terminal U(como faço na minha própria versão abaixo). Nós também definimosσ(a)=a para cada terminal ae estendemos σ como sempre em um homomorfismo de cordas.
Então consideramos um gráfico direcionado onde não terminais são os nós e existe um arco (U,V) se houver uma regra U→αVβ. Se várias dessas regras puderem produzir o mesmo arco(U,V), mantemos um tal que o comprimento |σ(αβ)|é mínimo. O arco é rotulado com essa regra e esse comprimento mínimo
|σ(αβ)| torna-se o peso do arco.
Finalmente, usando o algoritmo de caminho mais curto de Dijkstra, calculamos o caminho mais curto a partir do terminal não terminal inicial Spara cada não terminal da gramática. Dado o caminho mais curto para um terminal não terminalU, os rótulos de regra nos arcos podem ser usados para obter uma derivação
S⟹∗αUβ. Então, para todas as regras do formulárioU→γ na gramática, associamos a cadeia terminal tamanho-mínimo σ(αγβ) que pode ser derivado usando essa regra.
Para alcançar baixa complexidade , o algoritmo de Dijkstra e a extensão de Knuth são implementados com heaps , filas de prioridade AKA. Isso dá ao algoritmo de Dijkstra uma complexidade deO ( n logn + t ), e para o algoritmo de Knuth uma complexidade O ( m logn + t ), onde existem m
regras gramaticais e n não terminais e té o comprimento total de todas as regras. O todo é dominado pela complexidade do algoritmo de Knuth desdem ≥ n.
O que segue é o meu próprio trabalho, antes de produzir a resposta curta acima.
Derivando a solução do algoritmo de eliminação de símbolos inúteis.
Existem vários aspectos nesse algoritmo. Para uma melhor intuição, escolhi apresentá-lo em três versões sucessivas que introduzem progressivamente mais recursos. A primeira versão não responde à pergunta, mas é um algoritmo padrão para eliminação de símbolos inúteis que sugere uma solução. A segunda versão responde à pergunta sem a restrição de minimalidade. A terceira versão fornece uma resposta para a pergunta, satisfazendo a restrição de minimalidade. Essa terceira solução é aprimorada usando uma adaptação para e-ou gráficos do algoritmo de caminho mais curto de Dijkstra .
O resultado final é um algoritmo muito simples, que evita reconsiderar os cálculos já realizados. Mas é menos intuitivo e requer uma prova.
Essa resposta tenta apenas responder à pergunta conforme precisada pelo comentário do OP: " para cada regra de produção, quero gerar uma sequência mínima que leve o analisador do estado inicial, através da produção sendo testada, a um conjunto de terminais. "Portanto, apenas tento obter um conjunto de cadeias de caracteres para que, para cada regra, haja uma cadeia no conjunto que seja uma das cadeias de tamanho mínimo da linguagem que tenha uma derivação usando a regra.
No entanto, deve-se observar que o fato de uma string "invocar" uma regra, ou seja, ter uma derivação usando essa regra, não significa necessariamente que a regra será considerada por um analisador que trabalha com gramáticas ambíguas e resolve arbitrariamente as ambiguidades. Lidar com essa situação provavelmente exigiria um conhecimento mais preciso do analisador e pode ser uma pergunta mais complexa.
O algoritmo básico
Para resolver esta questão, pode-se começar com o algoritmo clássico para remoção de símbolos inúteis em gramáticas sem contexto. Está na seção 4.4, pp 88-89, de Hopcroft & Ullman, edição de 1979. Mas a apresentação aqui pode ser um pouco diferente.
O algoritmo visa justamente provar a existência de tal cobertura, conforme solicitado pelo OP, e consiste em duas partes:
lema 4.1 de H&U, página 88: remoção de todos os não terminais improdutivos . Isso é feito tentando encontrar para cada terminal uma cadeia de terminais na qual ele possa derivar. Uma maneira simples de explicar isso é a seguinte: Você cria um conjuntoPr o dsímbolos produtivos, que você inicializa com todos os terminais. Em seguida, para cada regra, ainda não processada, com todos os seus símbolos do lado direito (RHS)Pr o d, você adiciona o lado não terminal LHS (lado esquerdo) ao conjunto Pr o de você remove todas as regras com o mesmo LHS não terminal do conjunto de regras a serem processadas. Você itera o processo até que não haja nenhuma regra com todos os seus símbolos RHS emPr o d. Os restantes não terminais, não emPr o d no final deste processo, são improdutivos: não podem ser derivados em uma cadeia de terminais e, portanto, podem ser removidos da gramática.
lema 4.2 de H&U, página 89: remoção de todos os símbolos inacessíveis . Isso é feito pela acessibilidade clássica do nó em gráficos direcionados, considerando os não terminais como nós e tendo um arco( U, V) se houver uma regra você→ α de tal modo que
V ocorre em α. Você cria um conjuntoR e a c h de símbolos alcançáveis que são inicializados apenas com o símbolo inicial S. Então, para cada símbolo não terminalvocê no R e a c h ou mais tarde adicionado a ele e para todas as regras você→ α, você adiciona a R e a c h todos os símbolos em α. Quando todos os não terminais naR e a c h foram processados, todos os símbolos (terminais ou não terminais) que não estão incluídos no R e a c hnão pode aparecer em uma sequência derivada do símbolo inicial e, portanto, é inútil. Assim, eles podem ser removidos da gramática.
Esses dois algoritmos básicos são úteis para simplificar os resultados brutos de algumas técnicas de construção gramatical, como as usadas para a interseção de uma linguagem livre de contexto e de um conjunto regular. Em particular, este é útil na limpeza dos resultados de analisadores gerais CF .
A remoção inútil de símbolos não terminais é necessária no contexto da solução da pergunta, pois as regras que os utilizam não podem ser "invocadas" (isto é, usadas em sua derivação) por qualquer string do idioma.
Construindo um conjunto de cadeias que invocam todas as regras
(Ainda não estamos procurando por seqüências mínimas.)
Agora, respondendo especificamente à pergunta, é preciso remover de fato todos os símbolos inúteis, sejam símbolos inacessíveis ou símbolos não-terminais improdutivos, e também regras inúteis com termos não-terminais inúteis como o LHS. Eles não têm chance de serem invocados de maneira útil ao analisar uma cadeia de terminais (embora alguns possam desperdiçar o tempo de processamento de um analisador quando não forem removidos; quais podem perder tempo depende da tecnologia do analisador).
Agora consideramos, para cada regra (útil), a produção de uma cadeia de terminais que a invoca, ou seja, que pode ser gerada usando essa regra. Isso é essencialmente o que é feito por esses dois algoritmos acima, embora eles não mantenham as informações, pois estão satisfeitos em provar a existência dessas seqüências para garantir que os não terminais sejam alcançáveis e produtivos.
Modificamos o primeiro algoritmo (lema 4.1), mantendo com cada terminal não terminal você no conjunto Pr o d uma cadeia de terminais σ( U) deriva de: você⟹∗σ( U). Para cada terminal, definimos oσcomo o mapeamento de identidade. Quandovocê é adicionado ao conjunto Pr o d porque uma regra
você→ γ tem todos os seus símbolos RHS em Pr o d, então definimos
σ( U) = σ( γ)estendendo σ como um homomorfismo em cordas, e removemos todos U-rules, ou seja, todas as regras com U como LHS.
Modificamos o segundo algoritmo (lema 4.2) mantendo cada símbolo não terminal U Adicionado a Reach o caminho usado para alcançá-lo a partir do símbolo inicial S, que fornece as regras sucessivas para obter uma derivação S⟹∗αUβ.
Então, para cada regra U→γna gramática, produzimos uma cadeia de terminais que "chama" esta regra da seguinte maneira. Tomamos do resultado do segundo algoritmo a derivação
S⟹∗αUβ. Em seguida, aplicamos a regra para obter a stringαγβ. Uma cadeia de terminais "invocando" a regraU→γ é σ(αγβ)
Construindo um conjunto de seqüências mínimas que "invocam" todas as regras
Ignoramos a questão de eliminar símbolos inúteis, que podem ser um subproduto desses algoritmos modificados.
Construir um conjunto de seqüências mínimas depende primeiro da obtenção de uma sequência derivada mínima para cada não terminal. Isso é feito modificando ainda mais o primeiro algoritmo (lema 4.1). Primeiro, removemos do conjunto de regras a serem processadas todas as regras recursivas (ou seja, com um símbolo LHS ocorrendo na string RHS). É óbvio que nenhuma dessas regras pode derivar para uma cadeia terminal mais curta que as regras não recursivas com o mesmo LHS. E deve haver pelo menos uma regra não recursiva se o LHS não for um não-terminal inútil (porque não produtivo).
Então procedemos como antes para construir o conjunto Prod de símbolos produtivos, associados a cada sinbol U uma cadeia de terminais, que notamos σ(U). A cordaσ(U) é produzido como antes pela aplicação da regra U→γ, substituindo cada não terminal V ocorrendo em γ com σ(V). Até o momento, era necessário aplicar isso a apenas uma regra com um dado não terminalU
como seu LHS, o primeiro que teria todos os seus não terminais RHS em
Prode, em seguida, ignore os outros, porque qualquer string derivada faria. Mas agora estamos procurando uma string derivada mínima. Portanto, para um não terminalU, isso deve ser feito para todas as regras com Ucomo LHS. Mas mantemos apenas uma cadeia de terminaisσ(U), substituindo o atual pelo recém-encontrado, sempre que o novo for menor.
Além disso, sempre que a string σ(U) é substituído por um menor, todas as regras com ocorrência de Uno RHS que já havia sido processado, é necessário recolocar o conjunto de regras a serem processadas, pois as alterações permitem derivar seu RHS em uma sequência mais curta. Portanto, isso exigirá mais iterações, mas acabará sendo finalizado, pois nenhuma dessas strings fica muito menor do que a string vazia.
No final deste primeiro algoritmo, a string σ(U) é uma das menores seqüências que podem ser derivadas de U. Pode haver outros.
Agora também temos que modificar o segundo algoritmo para obter, para cada não terminal U, (uma das) a cadeia mais curta que contém U como o único não terminal. Para fazer isso, mantemos o mesmo gráfico direcionado com não terminais que os nós e com um arco(U,V) se houver uma regra
U→αVβ. Mas agora colocamos pesos nos arcos, para calcular o comprimento mínimo do contexto do terminal que deve ser associado aos não terminais alcançáveis. O peso associado ao arco(U,V) acima é o comprimento |σ(αβ)|, onde o mapeamento σé estendido aos terminais como a identidade e depois estendido novamente como um homomorfismo de cadeia. É o comprimento de (uma das) seqüências terminais mais curtas que podem ser derivadas da sequência
αβ. Observe queVé removido neste cálculo. No entanto, quando existem várias ocorrências deVno RHS, apenas um deve ser removido. Pode haver vários possíveis(U,V) arcos, com pesos diferentes, se houver várias regras com U como LHS e Vno RHS. Nesse caso, apenas (um de) o arco mais leve é mantido.
Neste gráfico, não procuramos mais apenas a acessibilidade de nós de
S, mas para o caminho ponderado mais curto que atinge todos os nós do símbolo inicial S. Isso pode ser feito com o algoritmo de Dijkstra .
Dado o caminho mais curto para um terminal não terminal U, lemos como antes como uma sequência de regras, da qual obtemos uma derivação
S⟹∗αUβ. Então, para todas as regras do formulárioU→γ na gramática, produzimos uma cadeia terminal mínima que "chama" essa regra como
σ(αγβ)
Observação : a mesma seqüência mínima provavelmente pode ser usada para várias regras. Mas o fato de uma das seqüências usar uma regraρ na sua derivação não significa necessariamente que é uma sequência mínima para essa regra ρ, como pode ter sido encontrado para outra regra, enquanto um mais curto pode ser encontrado para ρ. Pode ser possível aumentar a probabilidade de que a mesma sequência mínima seja encontrada para várias regras usando alguma política de prioridade sempre que houver flexibilidade. Mas vale a pena o problema?
Um algoritmo mais rápido para o mínimo de string de terminal derivado de um não-terminal
Construindo a função σ de tal modo que σ(U) é uma cadeia terminal mínima derivada de Ué feito acima com uma técnica bastante ingênua que requer uma reconsideração iterativa do trabalho já realizado quando uma nova sequência derivada menor é encontrada para alguns não terminais. Isso é um desperdício, mesmo que o processo termine claramente.
Propomos aqui um algoritmo mais eficiente, que é, em essência, uma adaptação ao gráfico gramatical de CF de uma extensão do algoritmo de caminho mais curto de Dijkstra para e-ou gráficos, com uma definição adequada do conceito de caminho para um e-ou gráfico . Essa variante do algoritmo provavelmente existe na literatura (supondo que esteja correta), mas não consegui encontrá-lo nos recursos que posso acessar. Por isso, estou descrevendo-o em mais detalhes, juntamente com uma prova.
Como anteriormente, removemos primeiro do conjunto de regras a serem processadas todas as regras recursivas (ou seja, regras com um símbolo LHS ocorrendo na cadeia RHS). É óbvio que nenhuma dessas regras recursivas pode derivar para uma cadeia terminal mais curta que as regras não recursivas com o mesmo LHS. E, para um LHSU deve haver pelo menos um não recursivo
U- regra se o símbolo Unão é um não terminal inútil (porque não produtivo). Isso não é estritamente necessário, mas reduz o número de regras a serem consideradas posteriormente.
Então procedemos como antes para construir o conjunto Prod de símbolos produtivos, associados a cada sinbol X uma cadeia de terminais, que notamos σ(X), que é uma cadeia de terminais de tamanho mínimo derivável de
X (no algoritmo anterior, isso era verdade somente após o término). O conjunto Prod é inicializado com todos os símbolos do terminal e para cada símbolo do terminal a, definimos σ(a)=a.
Então consideramos todas as regras U→γ de modo que todos os símbolos RHS estejam em Prode escolhemos um desses que σ(γ)é tamanho mínimo. Então nós adicionamosU para Prodcom σ(U)=σ(γ)e remova tudo U-regras. Repetimos até que todos os terminais produtivos tenham sido inseridosProd. Qualquer não terminalU, uma vez inserido
Prod, nunca precisa ser considerado novamente para mudar σ(U) para uma corda menor.
Prova :
Os algoritmos anteriores eram mais ou menos intuitivamente óbvios. Este é um pouco mais complicado, devido ao caráter e-ou do gráfico, e uma prova parece mais necessária. Tudo o que precisamos é realmente o seguinte lema, que estabelece a correção do algoritmo quando aplicado à última iteração.
Lema : após cada iteração do algoritmo,σ(X) é uma cadeia de terminais de tamanho mínimo derivável de X, para todos X no Prod.
O passo base é óbvio, pois isso é verdadeiro por definição para todos os terminais no Prod quando é inicializado.
Então, supondo que seja verdade depois que alguns não terminais foram adicionados ao
Prod, deixei U→γ ser a regra escolhida para adicionar um novo não terminal ao Prod. Sabemos que esta regra é escolhida porque
γ∈Prod∗ e σ(γ) é tamanho mínimo em todo RHS de todas as regras com um RHS em Prod∗. EntãoU é adicionado a Prod, e temos apenas que provar que σ(γ) é uma cadeia de terminais de tamanho mínimo derivável de U.
Obviamente, esse é o caso de todas as derivações que começam com a regra
U→γ, já que por hipótese de indução, aplicação de mapeamento σ é tal que todos os não terminais na σsão substituídos por cadeias terminais de tamanho mínimo derivadas delas. Portanto, nenhuma outra derivação pode produzir uma cadeia terminal mais curta.
Assim, consideramos apenas derivações começando com outra U-regra
U→β, de tal modo que
β⟹∗w∈Σ∗, Onde Σ é o conjunto de símbolos do terminal.
E se β∈Prod∗, uma sequência mínima na qual ela pode derivar é
σ(β). Mas, desde que escolhemos a regraU→γ, deve ser isso |σ(β)|≥|σ(γ)|. Então a regra
U→β não deriva em uma substring terminal menor.
O último caso a considerar é quando β∉Prod∗, e então consideramos uma derivação β⟹∗w∈Σ∗. Se essa derivação envolver apenas não-terminais,Prod, então
β∈Prod∗, que é um caso que já vimos. Portanto, consideramos apenas derivações que possuem etapas usando uma regra com seu LHS que não esteja emProd. DeixeiV→α tal regra, tal que
α∈Prod∗. Deve haver pelo menos uma dessas regras, uma vez que elas são parcialmente ordenadas por ordem de derivação, e w∈Prod∗.
Assim nós temos U⟹β⟹∗μVν. Nós sabemos issoμ e ν derivam em uma sequência de tamanho pelo menos 0 e, como não Vregra com um RHS em Prod∗ foi escolhido, eles derivam em cordas terminais de comprimento pelo menos igual a
|σ(γ)|. Portanto, com a regraU→β, U
deriva de uma cadeia terminal de comprimento pelo menos igual a
|σ(γ)|. ■