28

私はいつもこれをやりたいと思っていましたが、問題について考え始めるたびに、その指数関数的な性質のために頭がおかしくなります。

私が理解できるようにしたい問題解決者とコードは、カウントダウン数学の問題のためのものです:

与えられた数X1からX5のセットは、数学演算を使用してそれらを組み合わせてYを作成する方法を計算します。乗算、除算、加算、および減算を適用できます。

では、どのように1,3,7,6,8,3作るの348ですか?

回答:(((8 * 7) + 3) -1) *6 = 348

この問題を解決できるアルゴリズムを作成するにはどうすればよいですか?このような問題を解決しようとすると、どこから始めますか?このようなアルゴリズムを設計する際に、どのような重要な考慮事項を考慮する必要がありますか?

4

9 に答える 9

7

以下のc++11で機能するソリューション。

基本的な考え方は、スタックベースの評価(RPNを参照)を使用し、実行可能なソリューションを表示目的でのみ中置記法に変換することです。

N入力桁がある場合は(N-1)、各演算子が2進数であるため、演算子を使用します。

まず、オペランドと演算子(配列)の有効な順列を作成します。selector_有効な順列とは、スタックのアンダーフローなしで評価でき、スタック上の1つの値(結果)で終わる順列です。したがって1 1 +、有効ですが、そうで1 + 1はありません。

このような各オペランドと演算子の順列を、オペランドのすべての順列(values_配列)および演算子のすべての組み合わせ(配列)でテストしますops_。一致する結果はきれいに印刷されます。

引数はコマンドラインから。として取得され[-s] <target> <digit>[ <digit>...]ます。-sスイッチは全数検索を防ぎ、最初に一致した結果のみが出力されます。

./mathpuzzle 348 1 3 7 6 8 3元の質問の回答を得るために使用します)

このソリューションでは、入力桁を連結して数値を形成することはできません。これは、追加の外部ループとして追加できます。

作業コードはここからダウンロードできます。(注:ソリューションを形成するために入力桁を連結するためのサポートでそのコードを更新しました)

追加の説明については、コードコメントを参照してください。

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <iterator>
#include <string>

namespace {

enum class Op {
    Add,
    Sub,
    Mul,
    Div,
};

const std::size_t NumOps = static_cast<std::size_t>(Op::Div) + 1;
const Op FirstOp = Op::Add;

using Number = int;

class Evaluator {
    std::vector<Number> values_; // stores our digits/number we can use
    std::vector<Op> ops_; // stores the operators
    std::vector<char> selector_; // used to select digit (0) or operator (1) when evaluating. should be std::vector<bool>, but that's broken

    template <typename T>
    using Stack = std::stack<T, std::vector<T>>;

    // checks if a given number/operator order can be evaluated or not
    bool isSelectorValid() const {
        int numValues = 0;
        for (auto s : selector_) {
            if (s) {
                if (--numValues <= 0) {
                    return false;
                }
            }
            else {
                ++numValues;
            }
        }
        return (numValues == 1);
    }

    // evaluates the current values_ and ops_ based on selector_
    Number eval(Stack<Number> &stack) const {
        auto vi = values_.cbegin();
        auto oi = ops_.cbegin();
        for (auto s : selector_) {
            if (!s) {
                stack.push(*(vi++));
                continue;
            }
            Number top = stack.top();
            stack.pop();
            switch (*(oi++)) {
                case Op::Add:
                    stack.top() += top;
                    break;
                case Op::Sub:
                    stack.top() -= top;
                    break;
                case Op::Mul:
                    stack.top() *= top;
                    break;
                case Op::Div:
                    if (top == 0) {
                        return std::numeric_limits<Number>::max();
                    }
                    Number res = stack.top() / top;
                    if (res * top != stack.top()) {
                        return std::numeric_limits<Number>::max();
                    }
                    stack.top() = res;
                    break;
            }
        }
        Number res = stack.top();
        stack.pop();
        return res;
    }

    bool nextValuesPermutation() {
        return std::next_permutation(values_.begin(), values_.end());
    }

    bool nextOps() {
        for (auto i = ops_.rbegin(), end = ops_.rend(); i != end; ++i) {
            std::size_t next = static_cast<std::size_t>(*i) + 1;
            if (next < NumOps) {
                *i = static_cast<Op>(next);
                return true;
            }
            *i = FirstOp;
        }
        return false;
    }

    bool nextSelectorPermutation() {
        // the start permutation is always valid
        do {
            if (!std::next_permutation(selector_.begin(), selector_.end())) {
                return false;
            }
        } while (!isSelectorValid());
        return true;
    }

    static std::string buildExpr(const std::string& left, char op, const std::string &right) {
        return std::string("(") + left + ' ' + op + ' ' + right + ')';
    }

    std::string toString() const {
        Stack<std::string> stack;
        auto vi = values_.cbegin();
        auto oi = ops_.cbegin();
        for (auto s : selector_) {
            if (!s) {
                stack.push(std::to_string(*(vi++)));
                continue;
            }
            std::string top = stack.top();
            stack.pop();
            switch (*(oi++)) {
                case Op::Add:
                    stack.top() = buildExpr(stack.top(), '+', top);
                    break;
                case Op::Sub:
                    stack.top() = buildExpr(stack.top(), '-', top);
                    break;
                case Op::Mul:
                    stack.top() = buildExpr(stack.top(), '*', top);
                    break;
                case Op::Div:
                    stack.top() = buildExpr(stack.top(), '/', top);
                    break;
            }
        }
        return stack.top();
    }

public:
    Evaluator(const std::vector<Number>& values) :
            values_(values),
            ops_(values.size() - 1, FirstOp),
            selector_(2 * values.size() - 1, 0) {
        std::fill(selector_.begin() + values_.size(), selector_.end(), 1);
        std::sort(values_.begin(), values_.end());
    }

    // check for solutions
    // 1) we create valid permutations of our selector_ array (eg: "1 1 + 1 +",
    //    "1 1 1 + +", but skip "1 + 1 1 +" as that cannot be evaluated
    // 2) for each evaluation order, we permutate our values
    // 3) for each value permutation we check with each combination of
    //    operators
    // 
    // In the first version I used a local stack in eval() (see toString()) but
    // it turned out to be a performance bottleneck, so now I use a cached
    // stack. Reusing the stack gives an order of magnitude speed-up (from
    // 4.3sec to 0.7sec) due to avoiding repeated allocations.  Using
    // std::vector as a backing store also gives a slight performance boost
    // over the default std::deque.
    std::size_t check(Number target, bool singleResult = false) {
        Stack<Number> stack;

        std::size_t res = 0;
        do {
            do {
                do {
                    Number value = eval(stack);
                    if (value == target) {
                        ++res;
                        std::cout << target << " = " << toString() << "\n";
                        if (singleResult) {
                            return res;
                        }
                    }
                } while (nextOps());
            } while (nextValuesPermutation());
        } while (nextSelectorPermutation());
        return res;
    }
};

} // namespace

int main(int argc, const char **argv) {
    int i = 1;
    bool singleResult = false;
    if (argc > 1 && std::string("-s") == argv[1]) {
        singleResult = true;
        ++i;
    }
    if (argc < i + 2) {
        std::cerr << argv[0] << " [-s] <target> <digit>[ <digit>]...\n";
        std::exit(1);
    }
    Number target = std::stoi(argv[i]);
    std::vector<Number> values;
    while (++i <  argc) {
        values.push_back(std::stoi(argv[i]));
    }
    Evaluator evaluator{values};
    std::size_t res = evaluator.check(target, singleResult);
    if (!singleResult) {
        std::cout << "Number of solutions: " << res << "\n";
    }
    return 0;
}
于 2013-03-12T08:52:02.157 に答える
7

確かにそれは指数関数的ですが、それは小さいので、良い(十分な)素朴な実装は良いスタートになるでしょう。通常のインフィックス表記をブラケットで削除し、postfixを使用することをお勧めします。プログラミングが簡単です。いつでも出力を別のステージとしてきれいにすることができます。

数値と演算子のすべての(有効な)シーケンスをリストして評価することから始めます。例(接尾辞):

1 3 7 6 8 3 + + + + + -> 28
1 3 7 6 8 3 + + + + - -> 26

私のJavaは笑えるものです。笑うためにここに来ることはないので、コーディングはあなたに任せます。

これを読んでいるすべての賢い人々に:はい、私はこのような小さな問題でもより速くなる可能性が高いより賢いアプローチがあることを知っています、私はOPを最初の実用的な解決策に向けているだけです。他の誰かがよりスマートなソリューションで答えを書くことができます。

だから、あなたの質問に答えるために:

  • 私は、すぐに実用的なソリューションにつながると思うアルゴリズムから始めます。この場合、(私にとって)明らかな選択は、考えられるすべての計算の徹底的な列挙とテストです。
  • 明らかなアルゴリズムがパフォーマンス上の理由で魅力的でないように見える場合は、それについてより深く考え始め、より良いパフォーマンスを提供する可能性が高いと私が知っている他のアルゴリズムを思い出します。代わりに、それらの1つを最初にコーディングし始めるかもしれません。
  • 徹底的なアルゴリズムに固執し、実行時間が実際には長すぎることがわかった場合は、前の手順に戻って再度コーディングする可能性があります。しかし、それはしばらくの間価値があるはずです。コスト/利益の評価を行う必要があります。私のコードがレイチェル・ライリーを上回ることができる限り、私は満足するでしょう。
  • 重要な考慮事項には、私の時間コンピューターの時間の関係が含まれます。
于 2013-03-08T11:53:46.953 に答える
7

Javaでの非常に迅速で汚いソリューション:

public class JavaApplication1
{

    public static void main(String[] args)
    {
        List<Integer> list = Arrays.asList(1, 3, 7, 6, 8, 3);
        for (Integer integer : list) {
            List<Integer> runList = new ArrayList<>(list);
            runList.remove(integer);
            Result result = getOperations(runList, integer, 348);
            if (result.success) {
                System.out.println(integer + result.output);
                return;
            }
        }
    }

    public static class Result
    {

        public String output;
        public boolean success;
    }

    public static Result getOperations(List<Integer> numbers, int midNumber, int target)
    {
        Result midResult = new Result();
        if (midNumber == target) {
            midResult.success = true;
            midResult.output = "";
            return midResult;
        }
        for (Integer number : numbers) {
            List<Integer> newList = new ArrayList<Integer>(numbers);
            newList.remove(number);
            if (newList.isEmpty()) {
                if (midNumber - number == target) {
                    midResult.success = true;
                    midResult.output = "-" + number;
                    return midResult;
                }
                if (midNumber + number == target) {
                    midResult.success = true;
                    midResult.output = "+" + number;
                    return midResult;
                }
                if (midNumber * number == target) {
                    midResult.success = true;
                    midResult.output = "*" + number;
                    return midResult;
                }
                if (midNumber / number == target) {
                    midResult.success = true;
                    midResult.output = "/" + number;
                    return midResult;
                }
                midResult.success = false;
                midResult.output = "f" + number;
                return midResult;
            } else {
                midResult = getOperations(newList, midNumber - number, target);
                if (midResult.success) {
                    midResult.output = "-" + number + midResult.output;
                    return midResult;
                }
                midResult = getOperations(newList, midNumber + number, target);
                if (midResult.success) {
                    midResult.output = "+" + number + midResult.output;
                    return midResult;
                }
                midResult = getOperations(newList, midNumber * number, target);
                if (midResult.success) {
                    midResult.output = "*" + number + midResult.output;
                    return midResult;
                }
                midResult = getOperations(newList, midNumber / number, target);
                if (midResult.success) {
                    midResult.output = "/" + number + midResult.output;
                    return midResult
                }
            }

        }
        return midResult;
    }
}

アップデート

これは基本的に、指数関数的に複雑な単純なブルートフォースアルゴリズムです。getOperatiosn()ただし、関数の再帰の各レベルで処理する数列または(および)操作の順序付けに役立つヒューリスティック関数を利用することで、いくつかの改善を得ることができます。

このようなヒューリスティック関数の例は、たとえば、中間結果と合計ターゲット結果の違いです。

ただし、この方法では、ベストケースとアベレージケースの複雑さのみが改善されます。最悪の場合の複雑さはそのままです。

最悪の場合の複雑さは、ある種の分岐切断によって改善できます。この場合、それが可能かどうかはわかりません。

于 2013-03-08T13:02:35.320 に答える
5

入力は明らかに数字と演算子のセットです:D={1,3,3,6,7,8,3}およびOp={+、-、*、/}。最も簡単なアルゴリズムは、これらのセットのすべての可能な組み合わせを列挙するブルートフォースソルバーです。セットOpの要素は何度でも使用できますが、セットDの要素は1回だけ使用されます。擬似コード:

D={1,3,3,6,7,8,3}
Op={+,-,*,/}
Solution=348
for each permutation D_ of D:
   for each binary tree T with D_ as its leafs:
       for each sequence of operators Op_ from Op with length |D_|-1:
           label each inner tree node with operators from Op_
           result = compute T using infix traversal
           if result==Solution
              return T
return nil

それ以外:jedrus07とHPMの回答を読んでください。

于 2013-03-08T11:57:05.300 に答える
1

はるかに簡単なアプローチは、インテリジェントにブルートフォースすることです。6つの数値と4つの演算子から作成できる式の数は限られており、それらすべてを調べるだけです。

幾つか?すべての数値を使用する必要はなく、同じ演算子を複数回使用する可能性があるため、この問題は、「最大6枚の葉と4つの可能なラベルで、ラベル付きの厳密な二分木(別名完全二分木)をいくつ作成できるか」と同等です。非リーフノードごとに?」

n枚の葉を持つ完全な二分木の量はcatalan(n-1)に等しい。あなたはこれを次のように見ることができます:

n個の葉を持つすべての完全な二分木にはn-1個の内部ノードがあり、独自の方法でn-1個のノードを持つ非完全な二分木に対応します(完全な葉からすべての葉を削除して取得します)。たまたまn個のノードを持つcatalan(n)の可能な二分木があるので、n枚の葉を持つ厳密な二分木はcatalan(n-1)の可能な異なる構造を持っていると言えます。

リーフ以外のノードごとに4つの可能な演算子があります。4^(n-1)の可能性リーフにはnで番号を付けることができます。*(6は(n-1)を選択)さまざまな方法。(k回発生する数ごとにこれをk!で割るか、すべての数が異なることを確認してください)

したがって、6つの異なる数値と4つの可能な演算子に対して、Sum(n = 1 ... 6)[Catalan(n-1)* 6!/(6-n)!* 4 ^(n-1)]合計33,665,406の可能な式。それほど多くはありません。

これらの木をどのように列挙しますか?

n-1以下のノードを持つすべてのツリーのコレクションが与えられた場合、すべてのn-1ツリーと空のツリー、すべてのn-2ツリーと1ノードツリー、すべてnを体系的にペアリングすることにより、nノードを持つすべてのツリーを作成できます。 -2つのノードツリーすべてを含む3つのツリーなど、新しく形成されたツリーの左右のサブツリーとして使用します。

したがって、空のセットから始めて、最初にルートノードのみを持つツリーを生成し、次に新しいルートからそれを左または右のサブツリーとして使用して、次のような2つのツリーを生成できます:/および。等々。

それらをその場で一連の式に変換し(演算子と数値をループするだけ)、目標の数値が得られるまで評価することができます。

于 2019-03-18T16:04:46.350 に答える
1

私はPythonで独自のカウントダウンソルバーを作成しました。

これがコードです。GitHubでも入手できます:

#!/usr/bin/env python3

import sys
from itertools import combinations, product, zip_longest
from functools import lru_cache

assert sys.version_info >= (3, 6)


class Solutions:

    def __init__(self, numbers):
        self.all_numbers = numbers
        self.size = len(numbers)
        self.all_groups = self.unique_groups()

    def unique_groups(self):
        all_groups = {}
        all_numbers, size = self.all_numbers, self.size
        for m in range(1, size+1):
            for numbers in combinations(all_numbers, m):
                if numbers in all_groups:
                    continue
                all_groups[numbers] = Group(numbers, all_groups)
        return all_groups

    def walk(self):
        for group in self.all_groups.values():
            yield from group.calculations


class Group:

    def __init__(self, numbers, all_groups):
        self.numbers = numbers
        self.size = len(numbers)
        self.partitions = list(self.partition_into_unique_pairs(all_groups))
        self.calculations = list(self.perform_calculations())

    def __repr__(self):
        return str(self.numbers)

    def partition_into_unique_pairs(self, all_groups):
        # The pairs are unordered: a pair (a, b) is equivalent to (b, a).
        # Therefore, for pairs of equal length only half of all combinations
        # need to be generated to obtain all pairs; this is set by the limit.
        if self.size == 1:
            return
        numbers, size = self.numbers, self.size
        limits = (self.halfbinom(size, size//2), )
        unique_numbers = set()
        for m, limit in zip_longest(range((size+1)//2, size), limits):
            for numbers1, numbers2 in self.paired_combinations(numbers, m, limit):
                if numbers1 in unique_numbers:
                    continue
                unique_numbers.add(numbers1)
                group1, group2 = all_groups[numbers1], all_groups[numbers2]
                yield (group1, group2)

    def perform_calculations(self):
        if self.size == 1:
            yield Calculation.singleton(self.numbers[0])
            return
        for group1, group2 in self.partitions:
            for calc1, calc2 in product(group1.calculations, group2.calculations):
                yield from Calculation.generate(calc1, calc2)

    @classmethod
    def paired_combinations(cls, numbers, m, limit):
        for cnt, numbers1 in enumerate(combinations(numbers, m), 1):
            numbers2 = tuple(cls.filtering(numbers, numbers1))
            yield (numbers1, numbers2)
            if cnt == limit:
                return

    @staticmethod
    def filtering(iterable, elements):
        # filter elements out of an iterable, return the remaining elements
        elems = iter(elements)
        k = next(elems, None)
        for n in iterable:
            if n == k:
                k = next(elems, None)
            else:
                yield n

    @staticmethod
    @lru_cache()
    def halfbinom(n, k):
        if n % 2 == 1:
            return None
        prod = 1
        for m, l in zip(reversed(range(n+1-k, n+1)), range(1, k+1)):
            prod = (prod*m)//l
        return prod//2


class Calculation:

    def __init__(self, expression, result, is_singleton=False):
        self.expr = expression
        self.result = result
        self.is_singleton = is_singleton

    def __repr__(self):
        return self.expr

    @classmethod
    def singleton(cls, n):
        return cls(f"{n}", n, is_singleton=True)

    @classmethod
    def generate(cls, calca, calcb):
        if calca.result < calcb.result:
            calca, calcb = calcb, calca
        for result, op in cls.operations(calca.result, calcb.result):
            expr1 = f"{calca.expr}" if calca.is_singleton else f"({calca.expr})"
            expr2 = f"{calcb.expr}" if calcb.is_singleton else f"({calcb.expr})"
            yield cls(f"{expr1} {op} {expr2}", result)

    @staticmethod
    def operations(x, y):
        yield (x + y, '+')
        if x > y:                     # exclude non-positive results
            yield (x - y, '-')
        if y > 1 and x > 1:           # exclude trivial results
            yield (x * y, 'x')
        if y > 1 and x % y == 0:      # exclude trivial and non-integer results
            yield (x // y, '/')


def countdown_solver():
    # input: target and numbers. If you want to play with more or less than
    # 6 numbers, use the second version of 'unsorted_numbers'.
    try:
        target = int(sys.argv[1])
        unsorted_numbers = (int(sys.argv[n+2]) for n in range(6))  # for 6 numbers
#        unsorted_numbers = (int(n) for n in sys.argv[2:])         # for any numbers
        numbers = tuple(sorted(unsorted_numbers, reverse=True))
    except (IndexError, ValueError):
        print("You must provide a target and numbers!")
        return

    solutions = Solutions(numbers)
    smallest_difference = target
    bestresults = []
    for calculation in solutions.walk():
        diff = abs(calculation.result - target)
        if diff <= smallest_difference:
            if diff < smallest_difference:
                bestresults = [calculation]
                smallest_difference = diff
            else:
                bestresults.append(calculation)
    output(target, smallest_difference, bestresults)


def output(target, diff, results):
    print(f"\nThe closest results differ from {target} by {diff}. They are:\n")
    for calculation in results:
        print(f"{calculation.result} = {calculation.expr}")


if __name__ == "__main__":
countdown_solver()

アルゴリズムは次のように機能します。

  1. 番号は、長さ6のタプルに降順で配置されます。次に、長さが1〜6のすべての一意のサブグループが作成され、最小のグループが最初に作成されます。

    例:(75、50、5、9、1、1)-> {(75)、(50)、(9)、(5)、(1)、(75、50)、(75、9)、 (75、5)、...、(75、50、9、5、1、1)}。

  2. 次に、グループは階層ツリーに編成されます。すべてのグループは、空でないサブグループのすべての一意の順序付けられていないペアに分割されます。

    例:(9、5、1、1)-> [(9、5、1)+(1)、(9、1、1)+(5)、(5、1、1)+(9)、 (9、5)+(1、1)、(9、1)+(5、1)]。

  3. 数値の各グループ内で、計算が実行され、結果が保存されます。長さ1のグループの場合、結果は単に数値そのものになります。より大きなグループの場合、計算はサブグループのすべてのペアで実行されます。各ペアで、最初のサブグループのすべての結果が+、-、x、および/を使用して2番目のサブグループのすべての結果と結合され、有効な結果が保存されます。

    例:(75、5)はペア((75)、(5))で構成されます。(75)の結果は75です。(5)の結果は5です。(75、5)の結果は[75 + 5 = 80、75-5 = 70、75 * 5 = 375、75 / 5=15]です。

  4. このようにして、最小のグループから最大のグループまで、すべての結果が生成されます。最後に、アルゴリズムはすべての結果を繰り返し処理し、ターゲット番号に最も近い結果を選択します。

m個の数のグループの場合、算術計算の最大数は次のとおりです。

comps[m] = 4*sum(binom(m, k)*comps[k]*comps[m-k]//(1 + (2*k)//m) for k in range(1, m//2+1))

長さが1から6のすべてのグループの場合、計算の最大総数は次のようになります。

total = sum(binom(n, m)*comps[m] for m in range(1, n+1))

これは1144386です。実際には、アルゴリズムが重複グループの結果を再利用し、些細な操作(0の加算、1の乗算など)を無視し、ゲームのルールで中間結果が正の整数(除算演算子の使用を制限します)。

于 2019-10-19T20:37:29.157 に答える
0

まず、問題を厳密に定義する必要があると思います。許可されていることと許可されていないこと。あなたはそれを単純にし、乗算、除算、減算、加算のみを許可することから始めることができます。

これで、問題のある空間の入力のセット、使用可能な操作のセット、および必要な入力がわかりました。4つの操作とxの入力しかない場合、組み合わせの数は次の数より少なくなります。

操作を実行できる順序の数(x!)に、すべてのステップで可能な操作の選択肢を掛けたもの:4^x。あなたが6つの数のために見ることができるように、それは合理的な2949120操作を与えます。これは、これがブルートフォースアルゴリズムの制限になる可能性があることを意味します。

ブルートフォースがあり、それが機能することがわかったら、ヒューリスティック関数を定義する必要があるある種のA*アルゴリズムを使用してアルゴリズムの改善を開始できます。

私の意見では、それについて考える最良の方法は探索問題としてです。主な難しさは、優れたヒューリスティック、または問題のスペースを減らす方法を見つけることです(答えに足りない数がある場合は、少なくとも1つの乗算などが必要になります)。小さなことから始めて、それに基づいて、コードができたらフォローアップの質問をします。

于 2013-03-08T11:54:19.167 に答える
0

私は少し単純なバージョンを書きました:

  1. リストからの2つの(異なる)要素のすべての組み合わせに対して、+、-、*、/を使用してそれらを組み合わせます(a> bなので、abのみが必要であり、a%b=0の場合はa/bのみであることに注意してください)
  2. 組み合わせがターゲットの場合、ソリューションを記録します
  3. 削減されたリストを再帰的に呼び出す
import sys

def driver():
    try:
        target = int(sys.argv[1])
        nums = list((int(sys.argv[i+2]) for i in range(6)))
    except (IndexError, ValueError):
        print("Provide a list of 7 numbers")
        return
    solutions = list()
    solve(target, nums, list(), solutions)
    unique = set()
    final = list()
    for s in solutions:
        a = '-'.join(sorted(s))
        if not a in unique:
            unique.add(a)
            final.append(s)
    for s in final:     #print them out
        print(s)

def solve(target, nums, path, solutions):
    if len(nums) == 1:
        return
    distinct = sorted(list(set(nums)), reverse = True)
    rem1 = list(distinct)
    for n1 in distinct: #reduce list by combining a pair
        rem1.remove(n1)
        for n2 in rem1:
            rem2 = list(nums)       # in case of duplicates we need to start with full list and take out the n1,n2 pair of elements
            rem2.remove(n1)
            rem2.remove(n2)
            combine(target, solutions, path, rem2, n1, n2, '+')
            combine(target, solutions, path, rem2, n1, n2, '-')
            if n2 > 1:
                combine(target, solutions, path, rem2, n1, n2, '*')
                if not n1 % n2:
                    combine(target, solutions, path, rem2, n1, n2, '//')

def combine(target, solutions, path, rem2, n1, n2, symb):
    lst = list(rem2)
    ans = eval("{0}{2}{1}".format(n1, n2, symb))
    newpath = path + ["{0}{3}{1}={2}".format(n1, n2, ans, symb[0])]
    if ans == target:
        solutions += [newpath]
    else:
        lst.append(ans)
        solve(target, lst, newpath, solutions)
    
if __name__ == "__main__":
    driver()
于 2020-03-02T21:24:25.780 に答える
0

これを行うためのターミナルアプリケーションを作成しました: https ://github.com/pg328/CountdownNumbersGame/tree/main

中には、解空間のサイズの計算の図が含まれています(n *((n-1)!^ 2)*(2 ^ n-1)なので、n =6->2,764,800です。私は知っています、グロス)、そしてもっと重要なのはそれがなぜであるかです。私の実装は、チェックしてみればそこにありますが、そうでない場合は、ここで説明します。

基本的に、最悪の場合、それはブルートフォースです。なぜなら、私が知る限り、特定のブランチが明示的にチェックせずに有効な答えになるかどうかを判断することは不可能だからです。そうは言っても、平均的なケースはそのほんの一部です。それは{その数}を有効な解の数で割ったものです(私のプログラムでは約1000が見られる傾向があり、10程度が一意で、残りはそれらの10の順列です)。番号を手で振った場合、約2,765のブランチをチェックして、時間がかからないようにします。(はい、Pythonでもです。)

TL; DR:ソリューションスペースは巨大で、すべてのソリューションを見つけるのに数百万回の操作が必要ですが、必要な答えは1つだけです。最適なルートは、ブルートフォース攻撃を見つけて吐き出すまでです。

于 2020-11-16T23:07:20.963 に答える