Algorithm 如何设计一种计算倒计时式数学数谜的算法

Algorithm 如何设计一种计算倒计时式数学数谜的算法,algorithm,combinatorics,Algorithm,Combinatorics,我一直想这样做,但每次我开始思考这个问题时,它都会让我大吃一惊,因为它的指数性质 我希望能够理解的问题解决者和代码用于倒计时数学问题: 给定一组数字X1到X5,计算如何使用数学运算将它们组合成Y。 您可以应用乘法、除法、加法和减法 那么1,3,7,6,8,3如何使348 回答:((8*7)+3)-1)*6=348 如何编写一个算法来解决这个问题?当你试图解决这样的问题时,你从哪里开始?在设计这样一个算法时,您需要考虑哪些重要因素?当然它是指数型的,但它很小,所以一个好的(足够)简单的实现将是一个

我一直想这样做,但每次我开始思考这个问题时,它都会让我大吃一惊,因为它的指数性质

我希望能够理解的问题解决者和代码用于倒计时数学问题:

给定一组数字X1到X5,计算如何使用数学运算将它们组合成Y。 您可以应用乘法、除法、加法和减法

那么
1,3,7,6,8,3
如何使
348

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


如何编写一个算法来解决这个问题?当你试图解决这样的问题时,你从哪里开始?在设计这样一个算法时,您需要考虑哪些重要因素?

当然它是指数型的,但它很小,所以一个好的(足够)简单的实现将是一个好的开始。我建议你放弃通常用括号括起来的中缀符号,使用后缀,这样更容易编程。您始终可以将输出美化为单独的阶段

首先列出并计算所有(有效)数字和运算符序列。例如(在后缀中):

我的Java很可笑,我不是来这里被嘲笑的,所以我将把编写代码留给你

对于所有读到这篇文章的聪明人:是的,我知道即使是像这样的小问题,也有可能更快的更聪明的方法,我只是将OP指向一个初步的工作解决方案。其他人可以用更智能的解决方案写出答案

因此,要回答您的问题:

  • 我从一个算法开始,我认为这个算法会很快引导我找到一个可行的解决方案。在这种情况下,(对我来说)显而易见的选择是对所有可能的计算进行详尽的列举和测试
  • 如果由于性能原因,明显的算法看起来不吸引人,我将开始更深入地思考它,回顾我所知道的其他可能提供更好性能的算法。我可能会先开始编写其中一个
  • 如果我坚持使用穷举算法,并且发现实际上运行时间太长,那么我可能会返回到上一步并再次编写代码。但这必须是值得的,需要进行成本/收益评估——只要我的代码能够超越Rachel Riley,我就会感到满意
  • 重要的考虑因素包括我的时间和电脑时间,我的花费要高很多

    • 我认为,您需要首先严格定义问题。你可以做什么,不可以做什么。你可以从简单开始,只允许乘法、除法、减法和加法

      现在您知道了您的问题空间—输入集、可用操作集和所需输入。如果只有4个操作和x个输入,则组合的数量少于:

      可以执行操作的顺序数(x!)乘以每个步骤上可能选择的操作数:4^x。如您所见,对于6个数字,它给出了合理的2949120操作。这意味着这可能是暴力算法的限制

      一旦你有了蛮力,并且你知道它是有效的,你就可以开始用一些需要你定义启发式函数的方法来改进你的算法


      在我看来,思考这个问题的最佳方式是搜索问题。主要的困难是找到好的启发式方法,或者减少问题空间的方法(如果你的数字与答案相加不一致,你至少需要一次乘法等)。从小事做起,在这基础上,一旦你有了一些代码,就要问后续问题。

      输入显然是一组数字和运算符:D={1,3,3,6,7,8,3}和Op={+,-,*,/}。最直接的算法将是一个解算器,它可以处理这些集合的所有可能组合。其中集合Op的元素可以根据需要经常使用,但集合D中的元素只使用一次。伪代码:

      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的答案。

      非常快速且肮脏的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;
          }
      }
      
      公共类JavaApplication1
      {
      公共静态void main(字符串[]args)
      {
      List=Arrays.asList(1,3,7,6,8,3);
      用于(整数:列表){
      List runList=newarraylist(List);
      删除(整数);
      结果=getOperations(运行列表,整数,348);
      如果(结果、成功){
      System.out.println(整数+结果输出);
      返回;
      }
      }
      }
      公共静态类结果
      {
      公共字符串输出;
      公众的成功;
      }
      公共静态结果getOperations(列表编号、整数中间编号、整数目标)
      {
      结果midResult=新结果();
      如果(中间数==目标){
      middresult.success=true;
      midsult.output=“”;
      返回中间结果;
      }
      for(整数:数字){
      List newList=新阵列列表(编号);
      新列表。删除(编号);
      if(newList.isEmpty()){
      if(中间编号-编号==目标){
      middresult.success=true;
      midResult.output=“-”+数字;
      返回中间结果;
      }
      如果(中间数+数字==目标){
      middresult.success=true;
      midResult.output=“+”+数字;
      返回中间结果;
      }
      如果(中间编号*编号==目标){
      middresult.success=true;
      midResult.output=“*”+数字;
      返回中间结果;
      }
      if(中间编号/编号==目标){
      middresult.success=true;
      midResult.output=“/”+数字;
      返回中间结果;
      }
      米德尔
      
      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;
          }
      }
      
      #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;
      }
      
      #!/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()
      
      comps[m] = 4*sum(binom(m, k)*comps[k]*comps[m-k]//(1 + (2*k)//m) for k in range(1, m//2+1))
      
      total = sum(binom(n, m)*comps[m] for m in range(1, n+1))
      
      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()