2014-11-26 2 views
10

저는 Java 용 수학 게임을 제작 중이며,이 과제의 세부 사항에 따라이 부분에 머물러 있습니다. 규칙은 간단합니다. 각 숫자를 한 번만 사용하고 사용자로부터 읽은 4 개의 숫자 만 사용하여 24를 얻는 한 가지 방정식을 찾아야합니다.자바에서 수학 게임을 작성하십시오.

예를 들어 숫자 4,7,8,8의 경우, 가능한 해결책은 (7- (8/8)) * 4 = 24입니다.

4 자릿수

대부분의 세트가 입력 2,2,4,7 24을 얻기 위해 다양한 방식으로 사용될 수있는 예를 들어 24 초래할 여러 방정식에 사용될 수 :

2 + 2 * (4 + 7) = 24

2 + 2 * (7 + 4) = 24

(2 + 2) * 7-4 = 24

(2 * 2) * 7-4 = 24

2 * (2 * 7) -4 = 24

또한 24와 같은 방정식이 될 수없는 4 개의 숫자 조합도 있습니다. 예를 들어 1,1,1,1. 이 경우 프로그램은 24와 같은 가능한 방정식이 없음을 반환해야합니다.

참고 : 1에서 9 사이의 4 개의 정수를 입력하지만 모든 연산을 계산할 때는 double을 사용합니다. 예를 들어 숫자 3, 8, 8을 수식 8/(3-8/3) = 24에 결합 할 수 있습니다.

워크 플로 : 프로그램에서 사용자의 4 개의 숫자를 읽고 수식은 24가됩니다. 알고리즘은 4 개의 숫자, 모든 가능한 조합 및 가능한 모든 수식의 가능한 모든 순서를 열거해야합니다.

그러면 Numbers a, b, c, d의 64 순열이되고 연산자의 순열은 +-/*이됩니다. 이 결론에 도달 한 방법은 4^3 4 연산자 만 방정식에서 3 개의 채우기 점이었습니다. 오늘은 제외하고 평가 방법을 쓰는 데 어려움을 겪고 있으며 방정식에 모회사 (parentases)를 고려하고 있습니다. 여기

내 코드입니다 :

public static void evaluate(cbar [][] operations , double [][] operands) 
{ 
    /* 
    This is the part that gets me how am I supposed to account 
    for parentases and bring all these expressions togather to 
    actually form and equation. 
    */ 
} 
+0

빠른 질문입니까? 이것이 요구되지 않는다면 이것은 감소 될 수 있고 괄호는 상당히 쉽게 처리 될 수 있습니다. – Obicere

+1

가능한 [수학 24 게임 자바] (http://stackoverflow.com/questions/27118479/math-twenty-four-game-java) –

+0

그것의 숙제? –

답변

4
나는 방정식, 즉 루트가 나타내는 연산자를 가지고있는 구문 트리를 저장하기 위해 트리 구조를 사용하도록 추천 할 것입니다

등 피연산자와를 대표하는 두 아이 재귀 적으로. 그런 식으로 작업하는 것이 더 깨끗한 코드 일 것입니다. 왜냐하면 "손으로"피연산자의 조합을 생성 할 필요가 없기 때문에 1 차원 char []에서 모든 피연산자를 선택하는 코드를 만들 수 있습니다 operands = 새로운 char [] { '+', '-', '*', '/'} 배열.

구문 트리를 사용하고 싶지 않거나 사용 사례에 필요하지 않다고 생각하면 항상 1 차원 배열에서 피연산자를 선택하는 코드를 만드는 다른 방법을 찾아서 다른 데이터 구조. 그러나 나는 특히 당신이하고있는 모든 조합을 쓰는 것을 피할 것입니다. 그것은 유지하기가 매우 쉽지 않습니다.

9

이 문제는 몇 가지 문제점을 나타냅니다. 내 솔루션은 약 200 줄 정도입니다. 내가 원하는 수만큼 일반화했기 때문에 과제가 요구하는 것보다 조금 더 길다. 알고리즘을 공부하고 나만의 솔루션을 작성하는 것이 좋습니다.

우리가 극복해야하는 주요 장애물은 다음과 같습니다.

  • 반복없이 순열을 생성하는 방법은 무엇입니까?

  • 어떻게 산술 표현식을 작성하고 평가합니까?

  • 어떻게 표현식을 고유 한 문자열로 변환합니까?

순열을 생성하는 데는 여러 가지 방법이 있습니다. 이해하기 쉽기 때문에 재귀 적 접근 방식을 선택했습니다. 주된 복잡성은 용어가 반복 될 수 있다는 것입니다. 즉, 4! = 4*3*2*1 순열보다 적을 수 있습니다. 예를 들어, 용어가 1 1 1 2 인 경우, 단지 4 개의 순열이 있습니다.

순열 복제를 피하기 위해 용어를 정렬하여 시작합니다. 재귀 함수는 역 추적없이 왼쪽에서 오른쪽으로 모든 중복 용어에 대한 위치를 찾습니다. 예를 들어 첫 번째 1이 배열에 배치되면 나머지 모든 1 항이 오른쪽에 배치됩니다. 그러나 2 용어가 생기면 배열의 처음으로 돌아갈 수 있습니다.

산술 표현식을 작성하기 위해 다른 재귀 함수를 사용합니다. 이 함수는 순열의 두 용어 사이의 각 위치를 보면서 배열을 위치의 왼쪽 세그먼트와 오른쪽 세그먼트로 나눕니다. 왼쪽 및 오른쪽 세그먼트에 대한 식을 작성하기 위해 한 쌍의 재귀 호출을 만듭니다. 마지막으로 결과로 나오는 자식 표현식을 네 개의 산술 연산자로 결합합니다. 기본 경우는 배열의 크기가 1이므로 분할 할 수 없습니다. 결과적으로 연산자가없고 자식이없고 값만있는 노드가됩니다.

double 값에 대한 산술 연산을 통해 표현식을 평가하는 것은 부동 소수점 나누기의 부정확성으로 인해 문제가 될 수 있습니다. 예를 들어 1.0/3 = 0.33333...이지만 3 * 0.33333... = 0.99999...입니다. 따라서 double 값을 사용할 때 1/3 * 3 = 1을 확실히 알기가 어렵습니다. 이러한 어려움을 피하기 위해 Fraction 클래스를 정의했습니다. 분수에 대한 산술 연산을 수행하고 항상 가장 큰 제수로 결과를 단순화합니다. 0으로 나누기해도 오류 메시지가 발생하지 않습니다. 대신에 분수 0/0을 저장합니다.

마지막 퍼즐 조각은 표현식을 문자열로 변환하는 것입니다. 우리는 불필요하게 반복하지 않도록 정식 또는 정규화 된 문자열을 만들고 싶습니다. 예를 들어 1 + (1 + (1 + 2))((1 + 1) + 1) + 2은 본질적으로 동일한 표현이므로 표시하지 않으려합니다. 가능한 모든 괄호를 표시하는 대신 1 + 1 + 1 + 2을 표시하기 만하면됩니다.

우리는 필요한 경우에만 괄호를 추가하여이를 수행 할 수 있습니다. 우선 순위가 높은 연산자 (곱하기 또는 나눗셈)가 우선 순위가 낮은 연산자 (더하기 또는 빼기)가있는 노드의 부모 인 경우 괄호가 필요합니다. 우선 순위는 조작 순서라고도하는 조작자 우선 순위를 의미합니다. 상위 우선 순위 운영자는 하위 우선 순위 운영자보다 긴밀하게 바인딩됩니다. 따라서 부모 노드가 자식 노드의 연산자보다 우선 순위가 높으면 자식을 괄호로 묶어야합니다. 고유 한 문자열로 끝나기 위해 해시 집합을 결과 목록에 추가하기 전에 해시 집합을 확인합니다.

다음 프로그램 Equation.java은 명령 줄에서 사용자 입력을 허용합니다. 게임의 매개 변수는 Equation 클래스의 첫 번째 줄에 있습니다. 이를 수정하여 더 많은 용어, 더 큰 용어 및 다른 목표 값을 갖는 표현식을 작성할 수 있습니다.

import java.lang.*; 
import java.util.*; 
import java.io.*; 

class Fraction {     // Avoids floating-point trouble. 
    int num, denom; 
    static int gcd(int a, int b) { // Greatest common divisor. 
    while (b != 0) { 
     int t = b; 
     b = a % b; 
     a = t; 
    } 
    return a; 
    } 
    Fraction(int num, int denom) { // Makes a simplified fraction. 
    if (denom == 0) {    // Division by zero results in 
     this.num = this.denom = 0; // the fraction 0/0. We do not 
    } else {      // throw an error. 
     int x = Fraction.gcd(num, denom); 
     this.num = num/x; 
     this.denom = denom/x;  
    } 
    } 
    Fraction plus(Fraction other) { 
    return new Fraction(this.num * other.denom + other.num * this.denom, 
     this.denom * other.denom); 
    } 
    Fraction minus(Fraction other) { 
    return this.plus(new Fraction(-other.num, other.denom)); 
    } 
    Fraction times(Fraction other) { 
    return new Fraction(this.num * other.num, this.denom * other.denom); 
    } 
    Fraction divide(Fraction other) { 
    return new Fraction(this.num * other.denom, this.denom * other.num); 
    } 
    public String toString() {  // Omits the denominator if possible. 
    if (denom == 1) { 
     return ""+num; 
    } 
    return num+"/"+denom; 
    } 
} 

class Expression {    // A tree node containing a value and 
    Fraction value;     // optionally an operator and its 
    String operator;    // operands. 
    Expression left, right; 
    static int level(String operator) { 
    if (operator.compareTo("+") == 0 || operator.compareTo("-") == 0) { 
     return 0;     // Returns the priority of evaluation, 
    }        // also known as operator precedence 
    return 1;      // or the order of operations. 
    } 
    Expression(int x) {    // Simplest case: a whole number. 
    value = new Fraction(x, 1); 
    } 
    Expression(Expression left, String operator, Expression right) { 
    if (operator == "+") { 
     value = left.value.plus(right.value); 
    } else if (operator == "-") { 
     value = left.value.minus(right.value); 
    } else if (operator == "*") { 
     value = left.value.times(right.value); 
    } else if (operator == "/") { 
     value = left.value.divide(right.value); 
    } 
    this.operator = operator; 
    this.left = left; 
    this.right = right; 
    } 
    public String toString() {  // Returns a normalized expression, 
    if (operator == null) {  // inserting parentheses only where 
     return value.toString(); // necessary to avoid ambiguity. 
    } 
    int level = Expression.level(operator); 
    String a = left.toString(), aOp = left.operator, 
      b = right.toString(), bOp = right.operator; 
    if (aOp != null && Expression.level(aOp) < level) { 
     a = "("+a+")";    // Parenthesize the child only if its 
    }        // priority is lower than the parent's. 
    if (bOp != null && Expression.level(bOp) < level) { 
     b = "("+b+")"; 
    } 
    return a + " " + operator + " " + b; 
    } 
} 

public class Equation { 

    // These are the parameters of the game. 
    static int need = 4, min = 1, max = 9, target = 24; 

    int[] terms, permutation; 
    boolean[] used; 
    ArrayList<String> wins = new ArrayList<String>(); 
    Set<String> winSet = new HashSet<String>(); 
    String[] operators = {"+", "-", "*", "/"}; 

    // Recursively break up the terms into left and right 
    // portions, joining them with one of the four operators. 
    ArrayList<Expression> make(int left, int right) { 
    ArrayList<Expression> result = new ArrayList<Expression>(); 
    if (left+1 == right) { 
     result.add(new Expression(permutation[left])); 
    } else { 
     for (int i = left+1; i < right; ++i) { 
     ArrayList<Expression> leftSide = make(left, i); 
     ArrayList<Expression> rightSide = make(i, right); 
     for (int j = 0; j < leftSide.size(); ++j) { 
      for (int k = 0; k < rightSide.size(); ++k) { 
      for (int p = 0; p < operators.length; ++p) { 
       result.add(new Expression(leftSide.get(j), 
        operators[p], 
        rightSide.get(k))); 
      } 
      } 
     } 
     } 
    } 
    return result; 
    } 

    // Given a permutation of terms, form all possible arithmetic 
    // expressions. Inspect the results and save those that 
    // have the target value. 
    void formulate() { 
    ArrayList<Expression> expressions = make(0, terms.length); 
    for (int i = 0; i < expressions.size(); ++i) { 
     Expression expression = expressions.get(i); 
     Fraction value = expression.value; 
     if (value.num == target && value.denom == 1) { 
     String s = expressions.get(i).toString(); 
     if (!winSet.contains(s)) {// Check to see if an expression 
      wins.add(s);   // with the same normalized string 
      winSet.add(s);   // representation was saved earlier. 
     } 
     } 
    } 
    } 

    // Permutes terms without duplication. Requires the terms to 
    // be sorted. Notice how we check the next term to see if 
    // it's the same. If it is, we don't return to the beginning 
    // of the array. 
    void permute(int termIx, int pos) { 
    if (pos == terms.length) { 
     return; 
    } 
    if (!used[pos]) { 
     permutation[pos] = terms[termIx]; 
     if (termIx+1 == terms.length) { 
     formulate(); 
     } else { 
     used[pos] = true; 
     if (terms[termIx+1] == terms[termIx]) { 
      permute(termIx+1, pos+1); 
     } else { 
      permute(termIx+1, 0); 
     } 
     used[pos] = false; 
     } 
    } 
    permute(termIx, pos+1); 
    } 

    // Start the permutation process, count the end results, display them. 
    void solve(int[] terms) { 
    this.terms = terms;   // We must sort the terms in order for 
    Arrays.sort(terms);   // the permute() function to work. 
    permutation = new int[terms.length]; 
    used = new boolean[terms.length]; 
    permute(0, 0); 
    if (wins.size() == 0) { 
     System.out.println("There are no feasible expressions."); 
    } else if (wins.size() == 1) { 
     System.out.println("There is one feasible expression:"); 
    } else { 
     System.out.println("There are "+wins.size()+" feasible expressions:"); 
    } 
    for (int i = 0; i < wins.size(); ++i) { 
     System.out.println(wins.get(i) + " = " + target); 
    } 
    } 

    // Get user input from the command line and check its validity. 
    public static void main(String[] args) { 
    if (args.length != need) { 
     System.out.println("must specify "+need+" digits"); 
     return; 
    } 
    int digits[] = new int[need]; 
    for (int i = 0; i < need; ++i) { 
     try { 
     digits[i] = Integer.parseInt(args[i]); 
     } catch (NumberFormatException e) { 
     System.out.println("\""+args[i]+"\" is not an integer"); 
     return; 
     } 
     if (digits[i] < min || digits[i] > max) { 
     System.out.println(digits[i]+" is outside the range ["+ 
      min+", "+max+"]"); 
     return; 
     } 
    } 
    (new Equation()).solve(digits); 
    } 
} 
1

아래의 코드로 비슷한 퍼즐을 수정했습니다. 여기

public static boolean game24Points(int[] operands) { 
    ScriptEngineManager sem = new ScriptEngineManager(); 
    ScriptEngine engine = sem.getEngineByName("javascript"); 

    char[] operations = new char[] { '+', '-', '*', '/' }; 
    for (int i = 0; i < 4; i++) { 
     for (int j = 0; j < 4; j++) { 
      for (int k = 0; k < 4; k++) { 
       try { 
        String exp = "" + operands[0] + operations[i] + operands[1] + operations[j] 
          + operands[2] + operations[k] + operands[3]; 
        String res = engine.eval(exp).toString(); 
        if (Double.valueOf(res).intValue() == 24) { 
         System.out.println(exp); 
         return true; 
        } 
       } catch (ScriptException e) { 
        return false; 
       } 
      } 
     } 
    } 
    return false; 
} 

는 경우`이미 사건이있는 경우가 + b``B + A`를 포함하는 절대적으로 필요하다,을 testcases

public void testCase01() { 
    int[] operands = { 7, 2, 1, 10 }; 
    assertEquals(true, Demo.game24Points(operands)); 
} 

public void testCase02() { 
    int[] operands = { 1, 2, 3, 4 }; 
    assertEquals(true, Demo.game24Points(operands)); 
} 

public void testCase03() { 
    int[] operands1 = { 5, 7, 12, 12 }; 
    assertEquals(true, Demo.game24Points(operands1)); 
    int[] operands = { 10, 3, 3, 23 }; 
    assertEquals(true, Demo.game24Points(operands)); 
} 
관련 문제