ランダムな数式の生成


16

ランダムな数式を生成および評価するために、このアイデアを頭の中で実行しています。そこで、私はそれをテストするためにコーディングする前に、それを試してアルゴリズムを作り上げることにしました。

例:

ランダムに生成する式の例を次に示します。

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]

簡単メディアのものはかなり単純明快です。ランダムintな演算子で区切られたランダムなs、ここではクレイジーなものはありません。しかし、難しい例の1つを作成できるものを始めるのに苦労ています。1つのアルゴリズムで最後の2つが得られるかどうかさえわかりません。

私が考えていること:

私はそれらのアイデアを試したと言うことはできません。なぜなら、そもそも仕事をする機会がなかった方向に進むのに多くの時間を無駄にしたくなかったからです。それでも、私はいくつかの解決策を考えました:

  • 木を使う
  • 正規表現を使用する
  • クレイジーな「for-type」ループを使用する(間違いなく最悪)

私が探しているもの:

私が検討した解決策とあなた自身のアイデアの間で、あなたが行くのが最善であると信じる方法を知りたいです。

開始する良い方法を見つけたら、正しい方向へのリード、たとえばアルゴリズムの開始や、その一般的な構造に感謝します。

また、これらの式を評価する必要があることに注意してください。これは、式が生成された後、またはその作成中に実行できます。あなたの答えでそれを考慮に入れれば、それは素晴らしいことです。

言語に関連するものを探しているわけではありませんが、記録のために、Objective-Cで実装することを考えています。

:私はints を操作したいだけなので、これらの例には演算子は含まれていません。この演算子は多くの検証を追加します。あなたの答えがこれを処理するソリューションを提供する場合、それは素晴らしいことです。

私の質問に説明が必要な場合は、コメントでお尋ねください。ご協力いただきありがとうございます。


2
うーん、フィットネス機能を追加すると、遺伝的プログラミングに向かっているように見えます。
フィリップ

回答:


19

ここにあなたの問題の理論的解釈があります。

特定の言語(構文的に正しいすべての代数表現の無限のセット)からランダムに単語(代数表現)を生成しようとしています。加算と乗算のみをサポートする簡略化された代数文法の形式的な説明は次のとおりです。

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

ここで、E式(すなわち、あなたの言語の単語)で、Iある終端記号整数を表す(すなわち、それは任意の更なる拡大していません)。の上記の定義にEは3つの生産規則があります。この定義に基づいて、次のように有効な算術をランダムに構築できます。

  1. E出力ワードの単一シンボルとして開始します。
  2. 非終端記号のいずれかを一様にランダムに選択します。
  3. そのシンボルの生産規則の1つを一様にランダムに選択し、適用します。
  4. 終端記号のみが残るまで、手順2〜4を繰り返します。
  5. すべての終端記号Iをランダムな整数に置き換えます。

このアルゴリズムの適用例を次に示します。

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))

私はあなたがインターフェイスを持つ式を表すことを選択するだろうと仮定しExpressionたクラスによって実装されるIntExpressionAddExpressionMultiplyExpression。後者の2つにはとがleftExpressionありrightExpressionます。すべてのExpressionサブクラスはevaluate、これらのオブジェクトによって定義されたツリー構造で再帰的に動作し、複合パターンを効果的に実装するメソッドを実装するために必要です。

上記の文法とアルゴリズムでは、式Eを終端記号に展開する確率Iはのみですがp = 1/3、式をさらに2つの式に展開する確率はであることに注意してください1-p = 2/3。したがって、上記のアルゴリズムによって生成される式の整数の予想される数は実際には無限です。式の予想される長さは、再帰関係の影響を受けます

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

ここで、生産規則の適用l(n)後の算術式の予想される長さを示しnます。したがってp、ルールにかなり高い確率を割り当てE -> Iて、結果としてかなり小さい確率の高い表現になるようにすることをお勧めします。

編集:上記の文法があまりにも多くの括弧を生成することを心配しているなら、文法がこの問題を非常にエレガントに回避するセバスチャン・ネグラススの答えを見てください。


わあ..それは素晴らしい、私はそれがとても好きです、ありがとう!私はまだ、正しい選択をするために提案されたすべてのソリューションをもう少し検討する必要があります。再びありがとう、素晴らしい答え。
rdurand

編集してくれてありがとう、それは私が考えていなかったものです。手順2から4を実行する回数を制限することでうまくいくと思いますか?たとえば、ステップ2〜4を4回(または何回も)繰り返した後、ルールE-> Iのみを許可します。
-rdurand

1
@rdurand:はい、もちろん。m2〜4回繰り返した後、再帰的なプロダクションルールを「無視」します。これにより、予想されるサイズの式が生成されますl(m)。ただし、予想されるサイズが無限であっても、無限の式を生成する確率はゼロであるため、これは(理論的には)不要であることに注意してください。ただし、実際にはメモリは有限であるだけでなく小さいため、あなたのアプローチは好ましいです:)
blubb

あなたのソリューションでは、構築中に式を解決できる方法がわかりません。何かありますか?後で解決できますが、そうではありません。
rdurand

それが必要な場合は、ベース式として乱数から始めて、説明されている方法でランダムに操作に分解(書き換え)してみませんか?そうすれば、式全体の解が得られるだけでなく、式ツリーの各ブランチのサブ解も簡単に取得できます。
ミコワク

7

まず、実際には後置記法で式を生成します。ランダム式を生成した、簡単に中置に変換したり評価したりできますが、後置でそれを行うと、括弧や優先順位を気にする必要がなくなります。

また、式の次の演算子で使用可能な用語の数の合計を維持します(不正な形式の式の生成を避けたい場合)。つまり、次のようになります。

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++;
     }
}

明らかにこれは擬似コードなので、テストされていない/間違いがある可能性があり、おそらく文字列を使用するのではなく、タイプのような差別化されたユニオンのスタックを使用します


これは、現在、すべての演算子がバイナリで想定しているが、そのかなり容易では異なるアリティのオペレータと拡張する
JK。

どうもありがとう。私はRPNを考えていませんでした、それは良い考えです。答えを受け入れる前にすべての答えを調べますが、この作品を作ることができると思います。
rdurand

修正後の場合は+1。スタック以外のものを使用する必要をなくすことができます。これは、ツリーを構築するよりも簡単だと思います。
ニール

2
@rdurand修正後の利点の一部は、優先順位を心配する必要がないことを意味します(修正後のスタックに追加する前に既に考慮されています)。その後、スタックで最初に見つかった演算子をポップするまで、見つかったすべてのオペランドをポップしてから、結果をスタックにプッシュし、最後の値をスタックからポップするまでこのように続けます。
ニール

1
@rdurand式2+4*6-3+7は修正後スタックに変換されます+ 7 - 3 + 2 * 4 6(スタックの一番上)。4と6 *を押してoperatorを適用し、24を押し戻します。次に24と2をポップして演算子+を適用し、26を押し戻します。この方法で続行すると、正しい答えが得られることがわかります。予告* 4 6されている最初のスタック上の用語。つまり、括弧を使用せずに優先順位を既に決定しているため、最初に実行されます。
ニール

4

blubbの答えは良いスタートですが、彼の正式な文法はあまりにも多くのパラセシスを作成します。

これが私の見解です。

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

E式、I整数でありM、乗算演算の引数である式です。


1
すてきな拡張機能、これは確かにすっきりしているようです!
-blubb

blubbの答えについてコメントしたように、不要な括弧を残しておきます。ランダムを「ランダムに少なく」するかもしれません;)アドオンをありがとう!
-rdurand

3

「ハード」式の括弧は、評価の順序を表します。表示されたフォームを直接生成しようとするのではなく、ランダムな順序で演算子のリストを考え出し、それから式の表示フォームを導き出します。

番号: 1 3 3 9 7 2

演算子: + * / + *

結果: ((1 + 3) * 3 / 9 + 7) * 2

表示フォームの導出は、比較的単純な再帰アルゴリズムです。

更新:これは、表示フォームを生成するPerlのアルゴリズムです。ので+*分配され、それは、これらの事業者のための用語の順序をランダム化します。これは、括弧が片側に積み重なるのを防ぐのに役立ちます。

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";

このアルゴリズムは常に不均衡なツリーを生成するようです。左の枝は深く、右の枝はただ1つの数字です。各式の開始には開始パラメータが多すぎ、操作の順序は常に左から右になります。
scriptin

ダン、答えてくれてありがとう。しかし、@ scriptin、私はあなたがこの答えで好きではないものを理解していませんか?少し説明してもらえますか?
-rdurand

@scriptinは、表示順序の単純なランダム化で修正できます。アップデートをご覧ください。

@rdurand @ dan1111スクリプトを試しました。左の大きなサブツリーの問題は修正されましたが、生成されたツリーは依然として非常に不均衡です。この写真は私が意味することを示しています。これは問題とはみなされないかもしれませんが、生成された式に副表現(A + B) * (C + D)決して提示されない状況につながり、ネストされた括弧もたくさんあります。
scriptin

3
@scriptin、これについて考えた後、これが問題であることに同意します。

2

ツリーのアプローチを拡張するために、各ノードがリーフまたはバイナリ式のいずれかであるとしましょう:

Node := Leaf | Node Operator Node

ここでは、リーフはランダムに生成された整数であることに注意してください。

これで、ランダムにツリーを生成できます。各ノードがリーフである確率を決定すると、予想される深さを制御できますが、絶対最大深さも必要になる場合があります。

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())

次に、ツリーを印刷するための最も簡単なルールは、()各非リーフ式をラップし、演算子の優先順位を気にしないことです。


たとえば、最後のサンプル式を括弧で囲んだ場合:

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

それを生成するツリーを読むことができます:

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

1

木を使います。式の生成を細かく制御できます。たとえば、分岐ごとの深さと各レベルの幅を個別に制限できます。また、ツリーベースの生成では、生成中に既に答えが得られます。これは、結果(およびサブ結果)も十分に困難であるか、解決するのが難しくない場合に役立ちます。特に、ある時点で除算演算子を追加すると、整数に評価される式を生成できます。


ご回答有難うございます。木についても同じ考えを持っていて、部分式を評価/チェックできました。たぶん、あなたはあなたの解決策についてもう少し詳細を与えることができますか?このようなツリーをどのように構築しますか(実際の構造ではなく、一般的な構造はどうなりますか)。
-rdurand

1

Blubbの優れた答えについての若干異なる見解を以下に示します。

ここで構築しようとしているのは、基本的に逆に動作するパーサーです。あなたの問題とパーサーの共通点は、文脈自由文法です。これはバッカスナウア形式です:

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

パーサは、端末のストリーム(リテラルのようなトークンで始まる5*)と非終端記号(のような端末や他の非終端構成のもの、にそれらを組み立てるしようnumberop)。あなたの問題は非端末から始まり、逆に作用し、「or」(パイプ)記号の間で何かに遭遇するとランダムに選択し、端末に到達するまでプロセスを再帰的に繰り返します。

他のいくつかの答えは、これがツリーの問題であることを示唆しています。これは、別の非ターミナルを介して直接または間接的に自身を参照する非ターミナルがない特定の狭いクラスの場合です。文法がそれを許しているので、この問題は実際には有向グラフです。(別の非端末を介した間接参照もこれにカウントされます。)

1980年代後半にUsenetで公開されたSpew というプログラムは、もともとランダムなタブロイドの見出しを生成するように設計されており、これらの「逆文法」を試すための素晴らしい手段でもありました。端末のランダムストリームの生成を指示するテンプレートを読み取ることで動作します。アミューズメントの価値(見出し、カントリーソング、発音の英語の意味不明なもの)を超えて、プレーンテキストからXML、構文的には正しいがコンパイルできないCに至るまでのテストデータを生成するのに役立つ多くのテンプレートを作成しました。26歳にもかかわらずK&R Cで書かれており、見苦しいテンプレート形式であるため、コンパイルが正常に行われ、広告どおりに機能します。問題を解決するテンプレートを作成し、pastebinに投稿しました ここにそれほど多くのテキストを追加することは適切ではないようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.