用Amb 解决排队问题

最近看到有这样一条微博: 十个人排队,甲不能站中间,不能站两端,还得和乙挨着,还得和丙隔两个人,还得站丁后面。经过激烈的讨论,大家一致认为,让甲滚xiaohaha_org

 

SO WHAT?那我们到底应该怎么解这个问题呢?

其实和说谎者迷题类似,排队问题也是一种非确定性(non-determistic)问题,我们需要用到回溯(backtracking)来搜索解空间。神奇的AMB(ambiguous function)就是这样一个操作符(operator/evalutor),它可以系统地搜索所有的执行路径,按照深度优先的顺序执行。SICP是这样描述AMB的。

The amb evaluator that we will develop and work with in this section implements a systematic search as follows: When the evaluator encounters an application of amb, it initially selects the first alternative. This selection may itself lead to a further choice. The evaluator will always initially choose the first alternative at each choice point. If a choice results in a failure, then the evaluator automagically backtracks to the most recent choice point and tries the next alternative. If it runs out of alternatives at any choice point, the evaluator will back up to the previous choice point and resume from there.

 

好吧,先不管它有多神奇,那我们要怎么用AMB来解这个问题呢?

下面C#代码中ABCD分别代表甲乙丙丁,DefineValues定义每个人可以选择的位置从1到10都有可能。

Assert(Func<bool>) 表明题目中的条件约束,这很好理解。

Perform(Action) 表明在搜索过程中,如果遇到解,应该执行的Action,这里就是把解打印出来。

如果在Perform里不指定amb.stop() 意味着需要回溯所有解空间并执行Action;如果指定的话,那么在得到第一个解后,AMB就退出执行。

 

    class Program
    {
        static void Main(string[] args)
        {
            using (Amb amb = new Amb())
            {
                var arr = Enumerable.Range(1, 10).ToArray();
                var A = amb.DefineValues(arr);
                var B = amb.DefineValues(arr);
                var C = amb.DefineValues(arr);
                var D = amb.DefineValues(arr);
 
                amb.Assert(() => Distinct<int>(new int[4] { A.Value, B.Value, C.Value, D.Value }));
                amb.Assert(() => A.Value != 4 && A.Value != 5);
                amb.Assert(() => A.Value != arr.Min() && A.Value != arr.Max());
                amb.Assert(() => Math.Abs(A.Value - B.Value) == 1 );
                amb.Assert(() => Math.Abs(A.Value - C.Value) == 3);
                amb.Assert(() => A.Value > D.Value);
 
                amb.Perform(() =>
                {
                    System.Console.WriteLine("{0} {1} {2} {3} ",
                        A.Value, B.Value, C.Value, D.Value);
                    //amb.Stop();
                });
            }
 
            Console.ReadLine();
        }
 
        static bool Distinct<T>(IEnumerable<T> elements)
        {
            return elements.ToList<T>().Count == elements.ToList<T>().Distinct<T>().Count();
        }
    }

 

我们执行一下就会发现,这个题目的解有很多啊,试着改变一下题目把10个人换成5个人,这下解只有4个了。

2 3 5 1
4 3 1 2
4 5 1 2
4 5 1 3

验证一下,貌似都是正确的啊。

 

一个AMB操作符(这里是AMB对象)就可以轻松帮助我们搞定回溯问题,而且在过程中,我们的代码完全和题目相呼应,掌声。

那AMB到底要怎么实现啊,rosettacode上有多种语言下的AMB实现,其中C#的实现只用到了回溯,在其它语言的实现中用了continuation比如ruby,果然BT啊。choose对应上面代码中的DefineValues, assert嘛,呵呵。

 

class  ExhaustedError  <  RuntimeError; end
  def initialize
    @fail  =  proc { fail ExhaustedError,  " amb tree exhausted "  }
  end
  def choose( * choices)
    prev_fail  =  @fail
    callcc {  | sk | 
      choices.each {  | choice | 
        callcc {  | fk | 
          @fail  =  proc {
            @fail  =  prev_fail
            fk.call(:fail)
          }
           if  choice.respond_to ?  :call
            sk.call(choice.call)
           else 
            sk.call(choice)
          end
        }
      }
      @fail.call
    }
  end
  def failure
    choose
  end
  
  def  assert (cond)
    failure unless cond
  end
  alias :require : assert 
end

 

如此强大的C#为什么就不能实现一个Continuation Passing Style (CPS) 的AMB呢?呵呵,请听下回分解。

posted @ 2012-07-13 20:58  芥末丝张  阅读(402)  评论(3编辑  收藏  举报