Eu tenho essa idéia circulando na minha cabeça, para gerar e avaliar expressões matemáticas aleatórias. Então, decidi tentar e elaborar um algoritmo, antes de codificá-lo para testá-lo.
Exemplo:
Aqui estão alguns exemplos de expressões que eu quero gerar aleatoriamente:
4 + 2 [easy]
3 * 6 - 7 + 2 [medium]
6 * 2 + (5 - 3) * 3 - 8 [hard]
(3 + 4) + 7 * 2 - 1 - 9 [hard]
5 - 2 + 4 * (8 - (5 + 1)) + 9 [harder]
(8 - 1 + 3) * 6 - ((3 + 7) * 2) [harder]
Os fáceis e médios são bem diretos. Random int
é separado por operadores aleatórios, nada de louco aqui. Mas eu estou tendo alguns problemas para começar com algo que poderia criar uma das duras e mais duras exemplos. Não tenho certeza de que um único algoritmo possa me dar os dois últimos.
O que estou considerando:
Não posso dizer que tentei essas idéias, porque realmente não queria perder muito tempo indo em uma direção que não tinha chance de trabalhar em primeiro lugar. Mas ainda assim, pensei em algumas soluções:
- Usando árvores
- Usando expressões regulares
- Usando um loop "for-type" louco (certamente o pior)
O que estou olhando:
Gostaria de saber qual o melhor caminho a seguir, entre as soluções que considerei e suas próprias idéias.
Se você vir uma boa maneira de começar, eu apreciaria uma vantagem na direção certa, por exemplo, com o início do algoritmo ou com uma estrutura geral dele.
Observe também que terei que avaliar essas expressões. Isso pode ser feito após a expressão ser gerada ou durante sua criação. Se você levar isso em consideração na sua resposta, isso é ótimo.
Não estou procurando nada relacionado à linguagem, mas, para o registro, estou pensando em implementá-la no Objective-C, pois é a linguagem com a qual mais estou trabalhando recentemente.
Esses exemplos não incluíram o :
operador, já que eu quero apenas manipular int
s, e esse operador adiciona muitas verificações. Se sua resposta fornecer uma solução para lidar com essa, isso é ótimo.
Se minha pergunta precisar de algum esclarecimento, pergunte nos comentários. Obrigado pela ajuda.
fonte
Respostas:
Aqui está uma interpretação teórica do seu problema.
Você está procurando gerar aleatoriamente palavras (expressão algébrica) a partir de um determinado idioma (o conjunto infinito de todas as expressões algébricas sintaticamente corretas). Aqui está uma descrição formal de uma gramática algébrica simplificada que suporta apenas adição e multiplicação:
Aqui,
E
está uma expressão (ou seja, uma palavra do seu idioma) eI
é um símbolo terminal (ou seja, não é expandido mais) representando um número inteiro. A definição acima paraE
tem três regras de produção . Com base nessa definição, podemos construir aleatoriamente uma aritmética válida da seguinte maneira:E
o símbolo único da palavra de saída.I
por números inteiros aleatórios.Aqui está um exemplo da aplicação desses algoritmos:
Eu suponho que você iria escolher para representar uma expressão com uma interface
Expression
que é implementada por classesIntExpression
,AddExpression
eMultiplyExpression
. Os dois últimos teriam umleftExpression
erightExpression
. Todas asExpression
subclasses são necessárias para implementar umevaluate
método, que funcione recursivamente na estrutura em árvore definida por esses objetos e efetivamente implemente o padrão composto .Observe que, para a gramática e algoritmo acima, a probabilidade de expandir uma expressão
E
em um símbolo terminalI
é apenasp = 1/3
, enquanto a probabilidade de expandir uma expressão em duas expressões adicionais é1-p = 2/3
. Portanto, o número esperado de números inteiros em uma fórmula produzida pelo algoritmo acima é realmente infinito. O comprimento esperado de uma expressão está sujeito à relação de recorrênciaonde
l(n)
denota o comprimento esperado da expressão aritmética após an
aplicação das regras de produção. Por isso, sugiro que você atribua uma probabilidade bastante altap
à regra, deE -> I
modo que você acabe com uma expressão bastante pequena e com alta probabilidade.Edição : Se você está preocupado que a gramática acima produz muitos parênteses, observe a resposta de Sebastian Negraszus , cuja gramática evita esse problema com muita elegância.
fonte
m
iterações de 2-4, você 'ignore' as regras de produção recursiva. Isso levará a uma expressão do tamanho esperadol(m)
. Observe, no entanto, que isso não é (teoricamente) necessário, pois a probabilidade de gerar uma expressão infinita é zero, mesmo que o tamanho esperado seja infinito. No entanto, sua abordagem é favorável, pois, na prática, a memória não é apenas finita, mas também pequena :) #Antes de tudo, eu realmente geraria a expressão na notação postfix , você pode facilmente converter em infixo ou avaliar depois de gerar sua expressão aleatória, mas fazê-lo no postfix significa que você não precisa se preocupar com parênteses ou precedência.
Eu também manteria um total em execução do número de termos disponíveis para o próximo operador em sua expressão (supondo que você queira evitar a geração de expressões malformadas), ou seja, algo como isto:
obviamente, esse é um pseudo-código, portanto não é testado / pode conter erros e você provavelmente não usaria uma string, mas uma pilha de alguma união discriminada como o tipo
fonte
2+4*6-3+7
é convertida na pilha pós-correção+ 7 - 3 + 2 * 4 6
(a parte superior da pilha está mais à direita). Você pressiona 4 e 6 e aplica o operador*
, depois pressiona 24 novamente. Em seguida, você abre 24 e 2 e aplica o operador + e pressiona 26 novamente. Você continua dessa maneira e encontrará a resposta certa. Observe que* 4 6
são os primeiros termos da pilha. Isso significa que ele é executado primeiro porque você já determinou a precedência sem precisar de parênteses.A resposta de Blubb é um bom começo, mas sua gramática formal cria muitas parênteses.
Aqui está a minha opinião:
E
é uma expressão,I
um número inteiro eM
é uma expressão que é um argumento para uma operação de multiplicação.fonte
Os parênteses na expressão "rígida" representam a ordem da avaliação. Em vez de tentar gerar o formulário exibido diretamente, basta criar uma lista de operadores em ordem aleatória e derivar o formulário de exibição da expressão.
Números:
1 3 3 9 7 2
Operadores:
+ * / + *
Resultado:
((1 + 3) * 3 / 9 + 7) * 2
A derivação do formulário de exibição é um algoritmo recursivo relativamente simples.
Atualização: aqui está um algoritmo em Perl para gerar o formulário de exibição. Porque
+
e*
são distributivos, randomiza a ordem dos termos para esses operadores. Isso ajuda a impedir que os parênteses se acumulem de um lado.fonte
(A + B) * (C + D)
são apresentadas em expressões geradas e também há muitos parênteses aninhados.Para expandir a abordagem em árvore, digamos que cada nó seja uma folha ou uma expressão binária:
Observe que uma folha é apenas um número inteiro gerado aleatoriamente aqui.
Agora, podemos gerar aleatoriamente uma árvore. Decidir a probabilidade de cada nó ser uma folha nos permite controlar a profundidade esperada, embora você também queira uma profundidade máxima absoluta:
Então, a regra mais simples para imprimir a árvore é envolver
()
cada expressão que não seja folha e evitar preocupações com a precedência do operador.Por exemplo, se eu colocar parênteses sua última expressão de amostra:
você pode ler a árvore que a geraria:
fonte
Eu usaria árvores. Eles podem lhe dar um grande controle sobre a geração das expressões. Por exemplo, você pode limitar a profundidade por ramo e a largura de cada nível separadamente. A geração baseada em árvore também fornece a resposta já durante a geração, o que é útil se você deseja garantir que também o resultado (e sub-resultados) sejam difíceis o suficiente e / ou não muito difíceis de resolver. Especialmente se você adicionar um operador de divisão em algum momento, poderá gerar expressões que serão avaliadas em números inteiros.
fonte
Aqui está uma visão um pouco diferente da excelente resposta de Blubb:
O que você está tentando construir aqui é essencialmente um analisador que opera em sentido inverso. O que seu problema e um analisador têm em comum é uma gramática sem contexto , esta na forma Backus-Naur :
Os analisadores começam com um fluxo de terminais (tokens literais como
5
ou*
) e tentam montá-los em não-terminais (itens compostos por terminais e outros não-terminais, comonumber
ouop
). Seu problema começa com os não terminais e funciona ao contrário, escolhendo qualquer coisa entre os símbolos "ou" (canal) aleatoriamente quando um é encontrado e repetindo recursivamente o processo até chegar a um terminal.Algumas das outras respostas sugeriram que esse é um problema de árvore, que é para uma certa classe restrita de casos em que não existem termos não terminais que se referem direta ou indiretamente a outros não termos. Como as gramáticas permitem isso, esse problema é realmente um gráfico direcionado . (Referências indiretas através de outros não-terminais também contam para isso.)
Havia um programa chamado Spew publicado na Usenet no final dos anos 80, que foi originalmente projetado para gerar manchetes aleatórios dos tablóides e também é um ótimo veículo para experimentar essas "gramáticas reversas". Ele opera lendo um modelo que direciona a produção de um fluxo aleatório de terminais. Além do seu valor de diversão (manchetes, músicas country, rabiscos pronunciados em inglês), escrevi vários modelos que são úteis para gerar dados de teste que variam de texto sem formatação a XML a C. sintaticamente correto, mas não compilável. Apesar de ter 26 anos e escrito em K&R C e com um formato de modelo feio, ele compila muito bem e funciona como anunciado. Eu criei um modelo que resolve o seu problema e publiquei no pastebin já que adicionar muito texto aqui não parece apropriado.
fonte