2010-01-19 42 views
9

Tôi muốn phân tích các biểu thức boolean trong PHP. Như trong:Thuật toán phân tích các biểu thức trong ký hiệu infix là gì?

A and B or C and (D or F or not G) 

Các thuật ngữ có thể được coi là số nhận dạng đơn giản. Họ sẽ có một cấu trúc nhỏ, nhưng trình phân tích cú pháp không cần phải lo lắng về điều đó. Nó chỉ nên nhận ra các từ khóa and or not (). Mọi thứ khác là một thuật ngữ.

Tôi nhớ chúng tôi đã viết các đánh giá biểu thức số học đơn giản ở trường, nhưng tôi không nhớ nó đã được thực hiện như thế nào nữa. Tôi cũng không biết từ khóa cần tìm trong Google/SO.

Thư viện đã sẵn sàng sẽ đẹp, nhưng vì tôi nhớ thuật toán khá đơn giản nên có thể vui và giáo dục để tự thực hiện lại nó.

Trả lời

2

Tôi muốn đi với trình phân tích cú pháp Pratt. Nó gần giống như đệ quy gốc nhưng thông minh hơn :) Một lời giải thích khá của Douglas Crockford (của JSLint nổi tiếng) here.

+0

Nó sẽ không phải là một chút quá mức cần thiết cho một cái gì đó đơn giản như một biểu thức? –

4

Tại sao jsut không sử dụng trình phân tích cú pháp PHP?

$terms=array('and','or','not','A','B','C','D'...); 
$values=array('*','+','!',1,1,0,0,1....); 

$expression="A and B or C and (D or F or not G)"; 
$expression=preg_replace($terms, $values,$expression); 
$expression=preg_replace('^(+|-|!|1|0)','',$expression); 
$result=eval($expression); 

Thực tế, regex thứ 2 đó sai (và chỉ bắt buộc nếu bạn cần ngăn chặn bất kỳ việc tiêm mã) - nhưng bạn có ý tưởng.

C.

+0

Vâng ... đó là một khả năng tôi đoán ... Mặc dù nó sẽ là một chút phức tạp hơn vì các điều khoản sẽ có định dạng đặc biệt quá - như # 23 @ 45 –

0

Bạn có thể sử dụng trình phân tích cú pháp LR để tạo cây phân tích và sau đó đánh giá cây để nhận kết quả. Mô tả chi tiết bao gồm các ví dụ có thể được tìm thấy trong Wikipedia. Nếu bạn chưa tự viết mã cho nó thì tôi sẽ viết một ví dụ nhỏ tối nay.

+0

Thảo luận về overkill .... –

+0

Làm điều này chỉ cho các biểu thức logic như đó sẽ không quá mức cần thiết. Bạn sẽ phải sử dụng một số loại phân tích cú pháp. Nhưng tôi phải sửa đổi điều cây phân tích cú pháp mặc dù: bạn sẽ không phải xây dựng một cách rõ ràng nhưng có thể đánh giá kết quả trên bay. Chờ cho ví dụ của tôi và bạn sẽ thấy rằng nó thực sự dễ dàng. ;-) Tuy nhiên, có lẽ chỉ cần sử dụng phân tích cú pháp của PHP thông qua eval() là giải pháp thiết thực nhất cho bạn. – ahans

+0

Triển khai trình phân tích cú pháp LR sẽ không xảy ra hôm nay, nhưng bây giờ tôi quan tâm đến giải pháp này trông như thế nào so với sân shunting ... – ahans

2

Dijkstra's shunting yard algorithm là kiểu truyền thống để chuyển từ mã thông báo sang postfix/graph.

+0

Ahh, đây là cái chúng tôi dùng ở trường! Mặc dù tôi vừa mới nhận ra - nó hoạt động như thế nào với toán tử NOT đơn nhất? –

+0

Không khác nhiều so với nhị phân. Bạn đang chuyển đổi sang RPN, vì vậy A OR NOT B sẽ trở thành A B NOT OR trong rpn. – plinth

0

Cách đơn giản nhất là sử dụng các regex để chuyển đổi biểu thức của bạn thành một biểu thức trong cú pháp php và sau đó sử dụng eval, theo gợi ý của symcbean. Nhưng tôi không chắc chắn nếu bạn muốn sử dụng nó trong mã sản xuất.

Cách khác là mã số đơn giản recursive descent parser của riêng bạn. Nó không phải là khó như nó có thể âm thanh. Đối với một ngữ pháp đơn giản như vậy của bạn (biểu thức boolean), bạn có thể dễ dàng mã một từ đầu. Bạn cũng có thể sử dụng trình tạo trình phân tích cú pháp tương tự như ANTLR cho php, có thể tìm kiếm trình tạo trình phân tích cú pháp php sẽ bật lên một cái gì đó.

2

Tôi đã triển khai thuật toán shunting yard theo đề xuất của bước thứ chín. Tuy nhiên, thuật toán này chỉ cung cấp cho bạn ký hiệu postfix, còn gọi là ký pháp ngược Ba Lan (RNP). Bạn vẫn phải đánh giá nó, nhưng điều đó khá dễ dàng khi bạn có biểu thức trong RNP (được mô tả ví dụ here).

Mã bên dưới có thể không theo phong cách PHP tốt, kiến ​​thức PHP của tôi bị hạn chế một chút. Nó sẽ là đủ để có được ý tưởng mặc dù.

$operators = array("and", "or", "not"); 
$num_operands = array("and" => 2, "or" => 2, "not" => 1); 
$parenthesis = array("(", ")"); 

function is_operator($token) { 
    global $operators; 
    return in_array($token, $operators); 
} 

function is_right_parenthesis($token) { 
    global $parenthesis; 
    return $token == $parenthesis[1]; 
} 

function is_left_parenthesis($token) { 
    global $parenthesis; 
    return $token == $parenthesis[0]; 
} 

function is_parenthesis($token) { 
    return is_right_parenthesis($token) || is_left_parenthesis($token); 
} 

// check whether the precedence if $a is less than or equal to that of $b 
function is_precedence_less_or_equal($a, $b) { 
    // "not" always comes first 
    if ($b == "not") 
     return true; 

    if ($a == "not") 
     return false; 

    if ($a == "or" and $b == "and") 
     return true; 

    if ($a == "and" and $b == "or") 
     return false; 

    // otherwise they're equal 
    return true; 
} 


function shunting_yard($input_tokens) { 
    $stack = array(); 
    $output_queue = array(); 

    foreach ($input_tokens as $token) { 
     if (is_operator($token)) { 
      while (is_operator($stack[count($stack)-1]) && is_precedence_less_or_equal($token, $stack[count($stack)-1])) { 
        $o2 = array_pop($stack); 
        array_push($output_queue, $o2); 
      } 
      array_push($stack, $token); 

     } else if (is_parenthesis($token)) { 
      if (is_left_parenthesis($token)) { 
       array_push($stack, $token); 
      } else { 
       while (!is_left_parenthesis($stack[count($stack)-1]) && count($stack) > 0) { 
        array_push($output_queue, array_pop($stack)); 
       } 
       if (count($stack) == 0) { 
        echo ("parse error"); 
        die(); 
       } 
       $lp = array_pop($stack); 
      } 
     } else { 
      array_push($output_queue, $token); 
     } 
    } 

    while (count($stack) > 0) { 
     $op = array_pop($stack); 
     if (is_parenthesis($op)) 
      die("mismatched parenthesis"); 
     array_push($output_queue, $op); 
    } 

    return $output_queue; 
} 

function str2bool($s) { 
    if ($s == "true") 
     return true; 
    if ($s == "false") 
     return false; 
    die('$s doesn\'t contain valid boolean string: '.$s.'\n'); 
} 

function apply_operator($operator, $a, $b) { 
    if (is_string($a)) 
     $a = str2bool($a); 
    if (!is_null($b) and is_string($b)) 
     $b = str2bool($b); 

    if ($operator == "and") 
     return $a and $b; 
    else if ($operator == "or") 
     return $a or $b; 
    else if ($operator == "not") 
     return ! $a; 
    else die("unknown operator `$function'"); 
} 

function get_num_operands($operator) { 
    global $num_operands; 
    return $num_operands[$operator]; 
} 

function is_unary($operator) { 
    return get_num_operands($operator) == 1; 
} 

function is_binary($operator) { 
    return get_num_operands($operator) == 2; 
} 

function eval_rpn($tokens) { 
    $stack = array(); 
    foreach ($tokens as $t) { 
     if (is_operator($t)) { 
      if (is_unary($t)) { 
       $o1 = array_pop($stack); 
       $r = apply_operator($t, $o1, null); 
       array_push($stack, $r); 
      } else { // binary 
       $o1 = array_pop($stack); 
       $o2 = array_pop($stack); 
       $r = apply_operator($t, $o1, $o2); 
       array_push($stack, $r); 
      } 
     } else { // operand 
      array_push($stack, $t); 
     } 
    } 

    if (count($stack) != 1) 
     die("invalid token array"); 

    return $stack[0]; 
} 

// $input = array("A", "and", "B", "or", "C", "and", "(", "D", "or", "F", "or", "not", "G", ")"); 
$input = array("false", "and", "true", "or", "true", "and", "(", "false", "or", "false", "or", "not", "true", ")"); 
$tokens = shunting_yard($input); 
$result = eval_rpn($tokens); 
foreach($input as $t) 
    echo $t." "; 
echo "==> ".($result ? "true" : "false")."\n"; 
+0

Tuyệt vời. Hoàn hảo. Cảm ơn bạn. –

15

parsers gốc đệ quy là thú vị để viếtdễ đọc. Bước đầu tiên là viết ra ngữ pháp của bạn.

Có thể đây là ngữ pháp bạn muốn.

expr  = and_expr ('or' and_expr)* 
and_expr = not_expr ('and' not_expr)* 
not_expr = simple_expr | 'not' not_expr 
simple_expr = term | '(' expr ')' 

Biến tính năng này thành trình phân tích cú pháp đệ quy là siêu dễ dàng. Chỉ cần viết một hàm cho mỗi nonterminal.

def expr(): 
    x = and_expr() 
    while peek() == 'or': 
     consume('or') 
     y = and_expr() 
     x = OR(x, y) 
    return x 

def and_expr(): 
    x = not_expr() 
    while peek() == 'and': 
     consume('and') 
     y = not_expr() 
     x = AND(x, y) 
    return x 

def not_expr(): 
    if peek() == 'not': 
     consume('not') 
     x = not_expr() 
     return NOT(x) 
    else: 
     return simple_expr() 

def simple_expr(): 
    t = peek() 
    if t == '(': 
     consume('(') 
     result = expr() 
     consume(')') 
     return result 
    elif is_term(t): 
     consume(t) 
     return TERM(t) 
    else: 
     raise SyntaxError("expected term or (") 

Điều này chưa hoàn tất. Bạn phải cung cấp thêm một ít mã:

  • Chức năng đầu vào.consume, peekis_term là các chức năng bạn cung cấp. Chúng sẽ dễ thực hiện bằng cách sử dụng cụm từ thông dụng. consume(s) đọc mã thông báo đầu vào tiếp theo và phát ra lỗi nếu nó không khớp với s. peek() chỉ trả về một cái nhìn tại mã thông báo tiếp theo mà không cần dùng nó. is_term(s) trả về true nếu s là một thuật ngữ.

  • Chức năng đầu ra.OR, AND, NOTTERM được gọi mỗi khi một phần của biểu thức được phân tích cú pháp thành công. Họ có thể làm bất cứ điều gì bạn muốn.

  • Chức năng trình bao bọc. Thay vì chỉ gọi expr trực tiếp, bạn sẽ muốn viết một hàm bao bọc nhỏ khởi tạo các biến được sử dụng bởi consumepeek, sau đó gọi expr và cuối cùng kiểm tra để đảm bảo không có đầu vào còn dư.

Ngay cả với tất cả điều này, nó vẫn là một lượng nhỏ mã. In Python, the complete program is 84 lines và bao gồm một vài thử nghiệm.

+0

+1 cho vui! Tôi luôn coi đó là phương pháp tốt nhất để lập trình tốt. – Edmund

+0

Vâng ... nó rất đơn giản và dễ đọc! : D Tôi đoán tôi sẽ phải tìm hiểu thêm một chút về điều này. Như tôi đã hiểu (nhưng tôi không hiểu nhiều), điều này chỉ nhìn về phía trước cho một biểu tượng, vì vậy nó có thể thất bại trên một số ngữ pháp ... nhưng sau đó nó có lẽ không phải là trường hợp của tôi và có lẽ tôi sai. : P –

+0

Cái cụ thể này chỉ trông phía trước một biểu tượng, nhưng nếu bạn cần thêm một chút nữa, bạn chắc chắn có thể thêm một hàm 'peek_ahead (n)' và đi các loại hạt. Điều này thực sự là một sức mạnh của đệ quy gốc: hacking nó để làm một cái gì đó đặc biệt (vì ngôn ngữ bạn đang phân tích là một chút screwy) thường là khá đơn giản. –

Các vấn đề liên quan