Rastgele matematik ifadesi oluşturma


16

Rastgele matematiksel ifadeler üretmek ve değerlendirmek için kafamda dolaşan bu fikrim var. Bu yüzden, test etmek için kodlamadan önce bir deneme yapmaya ve bir algoritma geliştirmeye karar verdim.

Misal:

Rastgele oluşturmak istediğim bazı örnek ifadeler şunlardır:

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]

Kolay ve orta olanlar oldukça düz ileri. Rasgele ints rasgele operatörler tarafından ayrılmış, burada çılgın bir şey yok. Ama zor ve zor örneklerden birini yaratabilecek bir şeyle başlamakta biraz sorun yaşıyorum . Tek bir algoritmanın bana son ikisini verebileceğinden bile emin değilim.

Ben ne düşünüyorum:

Bu fikirleri denediğimi söyleyemem , çünkü ilk etapta çalışma şansı olmayan bir yönde ilerlemek için fazla zaman harcamak istemedim. Ama yine de birkaç çözüm düşündüm:

  • Ağaçları kullanma
  • Normal ifadeleri kullanma
  • Çılgın bir "for-type" döngüsü kullanmak (kesinlikle en kötüsü)

Ne için bakıyorum:

Hangi yöne gideceğinizi, düşündüğüm çözümler ile kendi fikirleriniz arasında en iyi olduğuna inandığınızı bilmek istiyorum.

Başlamak için iyi bir yol görürseniz, örneğin algoritmanın başlangıcı veya genel yapısı gibi, doğru yönde bir ipucunu takdir ediyorum.

Ayrıca bu ifadeleri değerlendirmem gerektiğini de unutmayın. Bu, ifade üretildikten sonra veya oluşturma sırasında yapılabilir. Cevabınızda bunu dikkate alırsanız, bu harika.

Dil ile ilgili bir şey aramıyorum, ancak kayıt için, Objective-C'de uygulamayı düşünüyorum, çünkü son zamanlarda en çok çalıştığım dil bu.

:Yalnızca ints'yi değiştirmek istediğim için bu örnekler operatörü içermiyordu ve bu operatör birçok doğrulama ekliyor. Cevabınız bununla ilgili bir çözüm sunuyorsa, bu harika.

Sorumun açıklığa ihtiyacı varsa, lütfen yorumlarda sorun. Yardımın için teşekkürler.


2
hmmm, bir fitness fonksiyonu ekleyin ve genetik programlamaya yönelmişsiniz gibi görünüyor .
Philip

Yanıtlar:


19

İşte probleminizin teorik bir yorumu.

Belirli bir dilden (sözdizimsel olarak doğru tüm cebirsel ifadelerin sonsuz kümesi ) rastgele kelimeler (cebirsel ifade) oluşturmak istiyorsunuz . İşte sadece toplama ve çarpmayı destekleyen basitleştirilmiş bir cebirsel dilbilgisinin resmi bir açıklaması :

E -> I 
E -> (E '+' E)
E -> (E '*' E)

Burada Ebir ifade (yani kendi dilinizde bir kelime) ve Ibir olan bitiş simgesi bir tamsayı temsil (yani yaşanabilecek diğer genişletilmiş değil). Yukarıdaki tanımın Eüç üretim kuralı vardır . Bu tanıma dayanarak, aşağıdaki gibi rastgele geçerli bir aritmetik oluşturabiliriz:

  1. EÇıktı kelimesinin tek sembolü olarak başlayın .
  2. Terminal olmayan sembollerden rastgele birini eşit olarak seçin.
  3. Bu sembol için üretim kurallarından birini rastgele seçerek uygulayın ve uygulayın.
  4. Yalnızca terminal sembolleri kalana kadar 2 - 4. adımları tekrarlayın.
  5. Tüm terminal simgelerini Irastgele tamsayılarla değiştirin.

İşte bu algoritmaların uygulanmasına bir örnek:

E
(E + E)
(E + (E * E))
(E + (I * E))
((E + E) + (I * E))
((I + E) + (I * E))
((I + E) + (I * I))
((I + (E * E)) + (I * I))
((I + (E * I)) + (I * I))
((I + (I * I)) + (I * I))
((2 + (5 * 1)) + (7 * 4))

Senin bir arayüz ile bir ifade temsil etmek seçsin varsayalım Expressionsınıfları tarafından uygulanır IntExpression, AddExpressionve MultiplyExpression. Son ikisinde bir leftExpressionve olur rightExpression. Tüm Expressionalt sınıfların, evaluatebu nesneler tarafından tanımlanan ağaç yapısı üzerinde özyineli olarak çalışan ve kompozit deseni etkili bir şekilde uygulayan bir yöntem uygulaması gerekir .

Yukarıdaki dilbilgisi ve algoritma için, bir ifadeyi Ebir terminal sembolüne genişletme olasılığının Isadece p = 1/3, bir ifadeyi iki ifadeye genişletme olasılığının sadece olduğuna dikkat edin 1-p = 2/3. Bu nedenle, yukarıdaki algoritma tarafından üretilen bir formülde beklenen tamsayı sayısı aslında sonsuzdur. Bir ifadenin beklenen uzunluğu, tekrarlama ilişkisine tabidir

l(0) = 1
l(n) = p * l(n-1) + (1-p) * (l(n-1) + 1)
     = l(n-1) + (1-p)

burada üretim kurallarının uygulanmasından l(n)sonra aritmetik ifadenin beklenen uzunluğunu belirtir n. Bu nedenle p, kurala oldukça yüksek bir olasılık atamanızı öneririz, böylece yüksek olasılıkla E -> Ioldukça küçük bir ifade elde edersiniz.

DÜZENLEME : Yukarıdaki dilbilgisinin çok fazla parantez üretmesinden endişe ediyorsanız , dilbilgisi bu sorunu çok zarif bir şekilde önleyen Sebastian Negraszus'un cevabına bakın .


Vay .. Bu harika, bunu çok beğendim, teşekkürler! Doğru seçimi yapmak için önerilen tüm çözümlere biraz daha bakmak zorundayım. Tekrar teşekkürler, harika cevap.
rdurand

Düzenlemeniz için teşekkürler, bu düşünmediğim bir şey. Sizce 2-4. Adımlarda kaç kez çalışabileceğinizi sınırlamak işe yarayabilir mi? Say, adımların 2-4 4 (her ne ya) tekrarlamadan sonra, sadece izin kural E-> I ?
rdurand

1
@rdurand: Evet, elbette. m2-4 yinelemeden sonra , özyinelemeli üretim kurallarını 'görmezden gelirsiniz' Bu beklenen boyutta bir ifadeye yol açacaktır l(m). Bununla birlikte, bunun (teorik olarak) gerekli olmadığına dikkat edin, çünkü beklenen boyut sonsuz olmasına rağmen sonsuz bir ifade üretme olasılığı sıfırdır. Ancak, pratikte bellek sadece sonlu değil, aynı zamanda küçük :) çünkü yaklaşımınız olumludur
blubb

Çözümünüzle, ifadeyi oluştururken çözebileceğim bir yol göremiyorum. Var mı? Daha sonra hala çözebilirim, ama istemem.
rdurand

Bunu istiyorsanız, neden temel ifade olarak rastgele bir sayıyla başlamayın ve açıklandığı şekilde rasgele bir şekilde (yeniden yaz) işlemlere ayırın? Daha sonra, yalnızca tüm ifade için bir çözüme sahip olmakla kalmaz, aynı zamanda ifade ağacının her bir dalı için kolayca alt çözüme sahip olursunuz.
mikołak

7

her şeyden önce aslında ifade postfix gösterimde oluşturmak istiyorum , rastgele ifade oluşturduktan sonra kolayca infix dönüştürmek veya değerlendirebilirsiniz, ancak postfix bunu yapmak parantez veya öncelik hakkında endişelenmenize gerek yok demektir.

Ayrıca, ifadenizdeki bir sonraki işleç için kullanılabilir toplam terim sayısını (örneğin, hatalı biçimlendirilmiş ifadeler oluşturmaktan kaçınmak istediğinizi varsayarsak) yani şöyle bir şey tutacağım:

string postfixExpression =""
int termsCount = 0;
while(weWantMoreTerms)
{
    if (termsCount>= 2)
    {
         var next = RandomNumberOrOperator();
         postfixExpression.Append(next);
         if(IsNumber(next)) { termsCount++;}
         else { termsCount--;}
    }
    else
    {
       postfixExpression.Append(RandomNumber);
       termsCount++;
     }
}

Açıkçası bu sözde kod bu yüzden test edilmez / hatalar içerebilir ve muhtemelen bir dize kullanmazsınız, ancak tür gibi bazı ayrımcı birliğin yığını


bu, şu anda tüm operatörlerin ikili olduğunu varsayar, ancak farklı arity
jk

Çok teşekkürler. RPN'i düşünmedim, bu iyi bir fikir. Birini kabul etmeden önce tüm cevapları inceleyeceğim, ama bence bu işe yarayabilir.
rdurand

Düzeltme için +1. Bir ağaç inşa etmekten daha basit olduğunu düşündüğüm bir yığından daha fazlasını kullanma ihtiyacını ortadan kaldırabilirsiniz.
Neil

2
@rdurand Son düzeltme avantajının bir parçası, öncelik hakkında endişelenmenize gerek olmadığı anlamına gelir (düzeltme sonrası yığınına eklemeden önce zaten dikkate alınmıştır). Bundan sonra, yığın üzerinde bulduğunuz ilk operatörü açana kadar bulduğunuz tüm işlenenleri patlatın ve ardından sonucu yığının üzerine itin ve son yığının son değerini çıkana kadar bu şekilde devam edin.
Neil

1
@rdurand İfade 2+4*6-3+7, düzeltme sonrası yığına dönüştürülür + 7 - 3 + 2 * 4 6( yığının üst kısmı en sağdadır). 4 ve 6'yı itip operatörü uygularsınız *, ardından 24'ü tekrar itersiniz. Daha sonra 24 ve 2'yi patlatıp + operatörünü uygularsınız, ardından 26'yı tekrar itersiniz. Bu şekilde devam ediyorsunuz ve doğru cevabı alacağınızı göreceksiniz. Bildirim o * 4 6vardır ilk yığın terimler. Bu, ilk önce gerçekleştirildiği anlamına gelir çünkü parantez gerektirmeden önceliği belirlediniz.
Neil

4

blubb'un cevabı iyi bir başlangıç, ancak resmi dilbilgisi çok fazla parantez yaratıyor.

İşte benim almam:

E -> I
E -> M '*' M
E -> E '+' E
M -> I
M -> M '*' M
M -> '(' E '+' E ')'

Ebir ifade, Ibir tam sayı ve Mçarpma işlemi için bağımsız değişken olan bir ifadedir.


1
Güzel uzatma, bu kesinlikle daha az dağınık görünüyor!
blubb

Blubb'un cevabı hakkında yorum yaparken, istenmeyen parantezler tutacağım. Belki rastgele "daha az rastgele";) eklenti için teşekkürler!
rdurand

3

"Zor" ifadedeki parantezler değerlendirme sırasını temsil eder. Görüntülenen formu doğrudan oluşturmaya çalışmak yerine, rasgele sırayla işleçlerin bir listesini hazırlayın ve ifadenin görüntüleme formunu bundan alın.

Sayılar: 1 3 3 9 7 2

Operatörler: + * / + *

Sonuç: ((1 + 3) * 3 / 9 + 7) * 2

Görüntüleme formunun türetilmesi nispeten basit bir özyinelemeli algoritmadır.

Güncelleme: burada Perl'de görüntüleme formunu oluşturmak için bir algoritma var. Çünkü +ve *dağıtımcıdır, bu işleçler için terimlerin sırasını rastgele seçer. Bu, parantezlerin bir tarafta birikmesini önlemeye yardımcı olur.

use warnings;
use strict;

sub build_expression
{
    my ($num,$op) = @_;

    #Start with the final term.
    my $last_num = pop @$num; 
    my $last_op = pop @$op;

    #Base case: return the number if there is just a number 
    return $last_num unless defined $last_op;

    #Recursively call for the expression minus the final term.
    my $rest = build_expression($num,$op); 

    #Add parentheses if there is a bare + or - and this term is * or /
    $rest = "($rest)" if ($rest =~ /[+-][^)]+$|^[^)]+[+-]/ and $last_op !~ /[+-]/);

    #Return the two components in a random order for + or *.
    return $last_op =~ m|[-/]| || rand(2) >= 1 ? 
        "$rest $last_op $last_num" : "$last_num $last_op $rest";        
}

my @numbers   = qw/1 3 4 3 9 7 2 1 10/;
my @operators = qw|+ + * / + * * +|;

print build_expression([@numbers],[@operators]) , "\n";

Bu algoritma her zaman dengesiz ağaçlar üretiyor gibi görünüyor: sol dal derin, sağ dal ise sadece tek bir sayı. Her ifadenin başlangıcında çok fazla açılış parası olurdu ve işlem sırası her zaman sağdan sola doğrudur.
scriptin

Cevabınız için teşekkürler, dan, yardımcı olur. Ama @scriptin, bu cevapta neyi sevmediğinizi anlamıyorum? Biraz açıklayabilir misiniz?
rdurand

@scriptin, bu, görüntüleme sırasının basit rasgele seçilmesiyle düzeltilebilir. Güncellemeye bakın.

@rdurand @ dan1111 Senaryoyu denedim. Büyük sol alt ağaç sorunu düzeltildi, ancak üretilen ağaç hala çok dengesiz. Bu resim ne demek istediğimi gösteriyor. Bu bir sorun olarak kabul edilmeyebilir, fakat alt ifadelere sever duruma yol açar (A + B) * (C + D)edilir asla oluşturulan ifadelerde sunulan ve iç içe Pars bir yeri vardır.
scriptin

3
@scriptin, bunu düşündükten sonra, bunun bir sorun olduğunu kabul ediyorum.

2

Ağaç yaklaşımını genişletmek için, her düğümün bir yaprak veya ikili ifade olduğunu varsayalım:

Node := Leaf | Node Operator Node

Bir yaprağın burada rastgele oluşturulmuş bir tam sayı olduğunu unutmayın.

Şimdi rastgele bir ağaç oluşturabiliriz. Her düğümün yaprak olma olasılığına karar vermek, beklenen derinliği kontrol etmemize izin verir, ancak mutlak bir maksimum derinlik de isteyebilirsiniz:

Node random_tree(leaf_prob, max_depth)
    if (max_depth == 0 || random() > leaf_prob)
        return random_leaf()

    LHS = random_tree(leaf_prob, max_depth-1)
    RHS = random_tree(leaf_prob, max_depth-1)
    return Node(LHS, RHS, random_operator())

Daha sonra, ağacı yazdırmanın en basit kuralı, ()yaprak olmayan her ifadeyi sarmak ve operatörün önceliği hakkında endişelenmekten kaçınmaktır.


Örneğin, son örnek ifadenizi parantez içine alırsam:

(8 - 1 + 3) * 6 - ((3 + 7) * 2)
((((8 - 1) + 3) * 6) - ((3 + 7) * 2))

onu oluşturacak ağacı okuyabilirsiniz:

                    SUB
                  /      \
               MUL        MUL
             /     6     /   2
          ADD          ADD
         /   3        3   7
       SUB
      8   1

1

Ağaçları kullanırdım. İfadelerin üretimi üzerinde size büyük kontrol verebilirler. Örneğin, dal başına derinliği ve her seviyenin genişliğini ayrı ayrı sınırlayabilirsiniz. Ağaç temelli nesil, aynı zamanda, sonuç sırasında (ve alt sonuçların) yeterince sert ve / veya çözülemeyecek kadar zor olmadığından emin olmak için de yararlı olan, nesil sırasında yanıt verir. Özellikle bir noktada bölüm operatörü eklerseniz, tam sayıları değerlendiren ifadeler oluşturabilirsiniz.


Cevabınız için teşekkürler. Ağaçlar hakkında aynı düşünceye sahiptim, alt ifadeleri değerlendirebilme / kontrol edebilme Belki çözümünüz hakkında biraz daha ayrıntı verebilir misiniz? Böyle bir ağacı nasıl inşa edersiniz (ne kadar değil , genel yapı ne olurdu)?
rdurand

1

İşte Blubb'un mükemmel cevabına biraz farklı bir bakış:

Burada inşa etmeye çalıştığınız şey aslında tersine çalışan bir ayrıştırıcıdır. Sorununuzun ve ayrıştırıcının ortak noktası bağlamsız bir dilbilgisidir , bu Backus-Naur biçimindedir :

digit ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
number ::= <digit> | <digit> <number>
op ::= '+' | '-' | '*' | '/'
expr ::= <number> <op> <number> | '(' <expr> ')' | '(' <expr> <op> <expr> ')'

Ayrıştırıcılar terminalleri akımı ile başlar (değişmez belirteçleri gibi 5ya da *) ve nonterminallerin (terminalleri ve bu gibi diğer nonterminallerin, oluşan şeyler içine araya getirmesi numberya da op). Sorununuz, terminal olmayanlarla başlar ve tersine çalışır, karşılaşıldığında "veya" (boru) sembolleri arasında rastgele herhangi bir şey seçer ve bir terminale ulaşana kadar işlemi yinelemeli olarak tekrarlar.

Diğer cevaplardan birkaçı, bunun doğrudan veya dolaylı olarak başka bir nonterminal aracılığıyla referans veren terminal olmayanların olmadığı dar bir vaka sınıfı için olan bir ağaç problemi olduğunu öne sürdü. Dilbilgileri buna izin verdiğinden, bu sorun gerçekten yönlendirilmiş bir grafiktir . (Başka bir terminale bağlı olmayan dolaylı referanslar da buna yöneliktir.)

1980'lerin sonunda Usenet'te yayınlanan ve rastgele tabloid başlıkları oluşturmak için tasarlanmış olan ve aynı zamanda bu "ters gramerleri" denemek için harika bir araç olan Spew adlı bir program vardı . Rastgele bir terminal akışı üretimini yönlendiren bir şablon okuyarak çalışır. Eğlence değerinin ötesinde (başlıklar, ülke şarkıları, belirgin İngilizce anlamsızlık), düz metinden XML'e, sözdizimsel olarak doğru ama uyumsuz C'ye kadar test verileri oluşturmak için yararlı olan çok sayıda şablon yazdım. 26 yaşında olmasına rağmen ve K&R C ile yazılmış ve çirkin bir şablon formatına sahip, sadece iyi derler ve reklamı gibi çalışır. Sorununuzu çözen bir şablon çırptım ve macun üzerine yayınladım çünkü buraya o kadar fazla metin eklemek uygun görünmüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.