랜덤 수학 표현 생성


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, 여기서 미친 것은 없습니다. 하지만 몇 가지 문제가 하나 만들 수 뭔가를 시작하는 데 열심히 하고 더 열심히 예. 하나의 알고리즘이 마지막 두 가지를 줄 수 있는지조차 확실하지 않습니다.

내가 고려하고있는 것 :

처음에는 일할 기회가 없었던 방향으로가는 데 많은 시간을 낭비하고 싶지 않았기 때문에 그 아이디어를 시도 했다고 말할 수 는 없습니다. 그러나 여전히 몇 가지 해결책을 생각했습니다.

  • 나무 사용하기
  • 정규 표현식 사용
  • 미친 "for-type"루프 사용 (확실히 최악)

내가 찾는 것 :

내가 생각한 솔루션과 자신의 아이디어 사이에 어떤 방법이 최선인지 생각하고 싶습니다.

시작하는 좋은 방법이 있다면 알고리즘의 시작 또는 일반적인 구조와 같은 올바른 방향의 리드에 감사드립니다.

또한 그 표현들을 평가해야 할 것입니다. 이는 표현식이 생성 된 후 또는 작성 중에 수행 될 수 있습니다. 당신의 대답에서 그것을 고려한다면, 그것은 좋습니다.

나는 언어와 관련된 것을 찾고 있지 않지만 레코드를 위해 Objective-C로 구현하려고 생각하고 있습니다. 최근에 가장 많이 사용하는 언어이기 때문입니다.

이 예제에는 :조작 만하고 싶기 때문에 연산자가 포함되어 있지 않으며이 int연산자는 많은 검증을 추가합니다. 귀하의 답변 이이 솔루션을 처리하는 솔루션을 제공한다면 훌륭합니다.

내 질문에 명확한 설명이 필요하면 의견을 요청하십시오. 당신의 도움을 주셔서 감사합니다.


2
흠, 피트니스 기능을 추가하면 유전자 프로그래밍 으로 향하는 것처럼 보입니다 .
Philip

답변:


19

다음은 문제에 대한 이론적 해석입니다.

주어진 언어 (구문 적으로 올바른 대수적 표현의 무한 세트 )에서 무작위로 단어 (대수식)를 생성하려고합니다 . 다음은 덧셈과 곱셈 만 지원 하는 단순화 된 대수 문법에 대한 공식적인 설명입니다 .

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

여기서, E식 (즉, 언어의 단어)이며, IA는 터미널 심볼 의 정수를 나타내는 (즉, 그것은 더 확장 아니에요은). 위의 정의 E에는 세 가지 생산 규칙이 있습니다. 이 정의에 따라 다음과 같이 유효한 산술을 무작위로 작성할 수 있습니다.

  1. E출력 단어의 단일 기호로 시작하십시오 .
  2. 비 터미널 기호 중 하나를 임의로 선택하십시오.
  3. 해당 심볼의 생산 규칙 중 하나를 임의로 선택하여 적용하십시오.
  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클래스에 의해 구현 IntExpression, AddExpression그리고 MultiplyExpression. 후자의 두 사람은 다음을 것 leftExpression하고 rightExpression. 모든 Expression서브 클래스는 evaluate이러한 객체에 의해 정의 된 트리 구조에서 재귀 적으로 작동하고 복합 패턴을 효과적으로 구현 하는 메소드 를 구현해야합니다 .

위의 문법과 알고리즘의 경우 표현식 E을 터미널 기호 로 확장 할 확률 I은 단지 p = 1/3이고, 표현식을 두 개의 추가 표현식으로 확장 할 확률은 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상당히 작은 표현으로 끝나는 것이 좋습니다.

편집 : 위의 문법이 너무 많은 괄호를 생성한다고 걱정되면 Sebastian Negraszus의 대답을보십시오 .이 문법은이 문제를 매우 우아하게 피합니다.


우와 .. 대단해, ​​난 정말 좋아, 고마워! 나는 여전히 올바른 선택을하기 위해 제안 된 모든 솔루션을 조금 더 살펴 봐야한다. 다시 한번 감사드립니다.
rdurand

편집 해 주셔서 감사합니다. 그건 제가 생각하지 못한 것입니다. 2-4 단계를 거치는 횟수를 제한하는 것이 효과가 있다고 생각하십니까? 2-4 단계를 4 번 (또는 무엇이든) 반복 한 후에 규칙 E-> I ?
rdurand

1
@rdurand : 물론입니다. m2-4 반복 후에 재귀 적 생산 규칙을 ​​'무시' 한다고 가정하십시오 . 이것은 예상 된 크기의 표현으로 이어질 것 l(m)입니다. 그러나 예상되는 크기가 무한하더라도 무한 식 생성 확률 은 0 이므로 (이론적으로) 필요하지 않습니다 . 그러나 실제로는 메모리가 유한 할뿐만 아니라 작기 때문에 여러분의 접근 방식이 유리합니다.
blubb

귀하의 솔루션으로는 표현하는 동안 표현을 해결할 수있는 방법을 찾지 못했습니다. 있어요 ? 나중에도 여전히 해결할 수 있지만 오히려 그렇지 않습니다.
rdurand

원하는 경우 기본 표현으로 난수로 시작하여 blubb에 설명 된 방식으로 난수를 임의로 분해 (재 작성)하지 않겠습니까? 그러면 전체 표현식에 대한 해답을 얻을 수있을뿐만 아니라 표현식 트리의 각 분기에 대한 하위 해를 쉽게 얻을 수 있습니다.
mikołak

7

우선 postfix 표기법으로 표현식을 실제로 생성 합니다. 임의의 표현식을 생성 한 후 infix로 쉽게 변환하거나 평가할 수 있지만 postfix에서 수행하면 괄호 또는 우선 순위에 대해 걱정할 필요가 없습니다.

또한 표현식에서 다음 연산자가 사용할 수있는 총 용어 수를 유지합니다 (잘못된 형식의 표현식을 생성하지 않으려는 경우).

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

분명히 이것은 의사 코드이므로 테스트되지 않았거나 실수를 포함 할 수 있으며 아마도 문자열을 사용하지 않고 유형과 같은 일부 구별 된 노조의 스택을 사용합니다


이것은 현재 모든 연산자가 이진 인 것으로 가정하지만 다른 arity
jk의

고마워 나는 RPN을 생각하지 않았다. 그것은 좋은 생각이다. 하나를 수락하기 전에 모든 답변을 살펴볼 것이지만,이 작업을 수행 할 수 있다고 생각합니다.
rdurand

수정 후 +1 스택보다 더 많은 것을 사용할 필요가 없습니다. 트리를 만드는 것보다 간단하다고 생각합니다.
Neil

2
@rdurand post-fix의 장점 중 하나는 우선 순위 (post-fix 스택에 추가하기 전에 이미 고려한)에 대해 걱정할 필요가 없음을 의미합니다. 그런 다음 스택에서 찾은 첫 번째 연산자를 팝한 다음 결과를 스택으로 푸시 할 때까지 찾은 모든 피연산자를 팝하면 스택에서 마지막 값을 팝할 때까지이 방식으로 계속 진행합니다.
Neil

1
@rdurand이 표현식 2+4*6-3+7은 수정 후 스택 + 7 - 3 + 2 * 4 6( 스택의 맨 오른쪽)으로 변환됩니다. 4와 6 *을 밀고 연산자를 적용한 다음 24를 다시 밉니다. 그런 다음 24와 2를 팝하고 연산자 +를 적용한 다음 26을 다시 밉니다. 이런 식으로 계속하면 정답을 얻을 수 있습니다. 공지 사항이 * 4 6있는 첫 번째 스택에 용어. 즉, 괄호없이 우선 순위를 이미 결정 했으므로 먼저 수행 됩니다.
Neil

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

이 알고리즘은 항상 불균형 트리를 생성하는 것 같습니다. 왼쪽 분기는 깊고 오른쪽은 단일 숫자입니다. 각 표현을 시작하는 데 너무 많은 개방 매개 변수가 있으며 작업 순서는 항상 왼쪽에서 오른쪽입니다.
scriptin

답변 주셔서 감사합니다, 댄, 도움이됩니다. 그러나 @scriptin, 나는이 대답에서 당신이 싫어하는 것을 이해하지 못합니까? 조금 설명해 주시겠습니까?
rdurand

@scriptin, 표시 순서의 간단한 무작위 화로 해결할 수 있습니다. 업데이트를 참조하십시오.

@rdurand @ dan1111 스크립트를 시도했습니다. 큰 왼쪽 하위 트리 문제는 해결되었지만 생성 된 트리는 여전히 매우 불균형합니다. 이 사진 은 내가 의미하는 바를 보여줍니다. 이것은 문제로 간주되지 않을 수도 있지만, 같은 하위 표현식 (A + B) * (C + D)이 생성 된 표현식에 표시 되지 않는 상황으로 이어지고 중첩 된 Parens가 많이 있습니다.
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의 탁월한 답변과 약간 다릅니다.

여기서 구축하려는 것은 본질적으로 역으로 작동하는 파서입니다. 문제와 파서가 공통적으로 갖는 것은 문맥이없는 문법입니다 . 이것은 Backus-Naur 형식입니다 .

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*)와 비끝 (터미널과 같은 다른 비끝, 구성 것들로 조립하려고 number하거나 op). 문제는 비 터미널에서 시작하여 역으로 작동하여 "또는"(파이프) 기호 사이에서 임의의 것을 선택하고 하나에 도달 할 때까지 프로세스를 반복적으로 반복합니다.

다른 답변 중 일부는 이것이 트리 문제라고 제안했는데, 이는 다른 비 터미널을 통해 직접 또는 간접적으로 자신을 참조하는 비 터미널이없는 특정 좁은 클래스의 경우에 해당합니다. 문법이 허용하기 때문에이 문제는 실제로 유 방향 그래프 입니다. (다른 비 터미널을 통한 간접 참조도 여기에 포함됩니다.)

1980 년대 후반 Usenet에 게시 된 Spew 라는 프로그램이있었습니다.이 프로그램 은 원래 임의의 타블로이드 헤드 라인을 생성하도록 설계되었으며 이러한 "역 문법"을 실험하기위한 훌륭한 수단입니다. 임의의 터미널 스트림 생성을 지시하는 템플리트를 읽음으로써 작동합니다. 유쾌한 가치 (헤드 라인, 컨트리 노래, 발음 할 수있는 영어 횡설수설) 외에도 일반 텍스트에서 XML, 구문 적으로 수정되었지만 컴파일 할 수없는 C에 이르기까지 다양한 테스트 데이터를 생성하는 데 유용한 수많은 템플릿을 작성했습니다. K & R C로 작성되었고 추악한 템플릿 형식을 가지고 있으며, 잘 컴파일되고 광고 된대로 작동합니다. 문제를 해결하는 템플릿을 작성 하여 pastebin에 게시했습니다. 여기에 많은 텍스트를 추가하는 것은 적절하지 않은 것 같습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.