zoukankan      html  css  js  c++  java
  • 让电脑像人脑一样思考,谁养鱼问题断言推理解法

    记得第一次接触这个所谓的“爱因斯坦谜题”的时候,也花了很多时间做了一个比较难看的解法,大体上就是将不可能的条件筛掉一部分,然后再穷举。这样的解法可以说是相当难看的,因为只要题目的条件稍微变变,程序又要改一大堆。也是受限于当时的技术条件,的确很难实现一个比较通用的解题器。
    不过在时过境迁的今天,利用新思想和新技术,我们能不能交出一份漂亮的答卷呢?
    答案是肯定的。

    我设计的第一个版本的思路是这样的,穷举所有的情况,然后将已知线索作为筛选器来检查每一种情况,如果有一种情况符合所有的线索,则就是最终答案。
    类似于排列组合这种运算,老实说还真没有什么现成的方案,还是老老实实的笛卡尔积再筛选吧。
    利用多重from表达式,我们能很方便的得到N个集合的笛卡尔积。
    房子有五种宠物,五种颜色,五种国籍,五种香烟和五种饮料,将将它们互相得到一个每个房子的可能结果集,再对这个结果集进行五次自身的笛卡尔积,最终的结果筛去不满足前提条件的结果(有相同的颜色或宠物)。
    第一个程序很快做出来了,结果跑了几十分钟还没有结果,猜想是结果集太大了。
    其实用脚趾头想想都知道这个结果集的大小几乎是天文数字,我的笔记本是不可能算出来的,看来不能有侥幸心理。。。。

    改,这一次使用一种先进的算法,计算五种宠物、五种颜色等的全排列,然后计算这些全排列的结果的笛卡尔积,这样得出来的结果不存在有相同颜色的房子这样的问题,结果集可以缩小一个很大的数量级。
    计算全排列我使用了一种非常简单不通用的算法,难看就难看点,先解决问题吧:
    public static IEnumerable<T[]> GetCustomPermutation<T>( this T[] array )
    {
      return from item1 in array
             from item2 in array.Where( item => !item.Equals( item1 ) )
             from item3 in array.Where( item => !( item.Equals( item1 ) || item.Equals( item2 ) ) )
             from item4 in array.Where( item => !( item.Equals( item1 ) || item.Equals( item2 ) || item.Equals( item3 ) ) )
             from item5 in array.Where( item => !( item.Equals( item1 ) || item.Equals( item2 ) || item.Equals( item3 ) || item.Equals( item4 ) ) )
             select new T[] { item1, item2, item3, item4, item5 };
    
    }
    
     
    第二个程序就这样诞生了,运行。。。。。还是没有结果。。。。。
    优化,避免延迟执行。。。。
    还是没戏。。。。
    看来还是结果集太大了。
    我算算,五个东西的全排列,嗯,是5!=120,五个这样全排列的笛卡尔积的结果集大小是120^5,心算一下就知道这至少是个百亿级别的数了。

    所以暴力的穷举就这样可耻的失败了。。。。。。

    好吧,看来不给程序加上智能是不可能做到的了。

    要给程序加上智能,就要将原来纯粹的判断改为智能的判断,原来的判断是这样的:
    public static IEnumerable<Predicate<Conjecture>> GetKnownConditions()
    {
    
      //1、The Brit lives in the red house 
      yield return conjecture => conjecture.Any( house => house.Nationality == "Brit" && house.Color == "red" );
    
    
      //2、The Swede keeps dogs as pets 
      yield return conjecture => conjecture.Any( house => house.Nationality == "Swede" && house.Pet == "dogs" );
    
    
      //3、The Dane drinks tea 
      yield return conjecture => conjecture.Any( house => house.Nationality == "Dane" && house.Drinks == "tea" );
    
    
      //4、The green house is on the immediate left of the white house as you stare at the front of the 5 houses 
      yield return conjecture => conjecture.Any( house => house.Color == "green" && house.IsImmediateLeftOf( _house => _house.Color == "white" ) );
    
    
      //5、The green house owner drinks coffee 
      yield return conjecture => conjecture.Any( house => house.Color == "green" && house.Drinks == "coffee" );
    
    
      //6、The person who smokes Pall Mall raises birds 
      yield return conjecture => conjecture.Any( house => house.Smokes == "Pall Mall" && house.Pet == "birds" );
    
    
      //7、The owner of the yellow house smokes Dunhill 
      yield return conjecture => conjecture.Any( house => house.Color == "yellow" && house.Smokes == "Dunhill" );
    
    
      //8、The man living in the house right in the center drinks milk 
      yield return conjecture => conjecture.Any( house => house.IsCenter && house.Drinks == "milk" );
    
    
      //9、The Norwegian lives in the first house 
      yield return conjecture => conjecture.Any( house => house.Nationality == "Norwegian" && house.Index == 0 );
    
    
      //10、The man who smokes Blends lives next to the one who keeps cats 
      yield return conjecture => conjecture.Any( house => house.Smokes == "Blends" && house.IsImmediateOf( _house => _house.Pet == "cats" ) );
    
    
      //11、The man who keeps horses lives next to the one who smokes Dunhill 
      yield return conjecture => conjecture.Any( house => house.Pet == "horses" && house.IsImmediateOf( _house => _house.Smokes == "Dunhill" ) );
    
    
      //12、The owner who smokes Bluemaster drinks juice 
      yield return conjecture => conjecture.Any( house => house.Smokes == "Bluemaster" && house.Drinks == "juice" );
    
    
      //13、The German smokes Prince 
      yield return conjecture => conjecture.Any( house => house.Nationality == "German" && house.Smokes == "Prince" );
    
    
      //14、The Norwegian lives next to the blue house 
      yield return conjecture => conjecture.Any( house => house.Nationality == "Norwegian" && house.IsImmediateOf( _house => _house.Color == "blue" ) );
    
    
      //15、The man who smokes Blend has a neighbor who drinks water. 
      yield return conjecture => conjecture.Any( house => house.Smokes == "Blends" && house.IsImmediateOf( _house => _house.Drinks == "water" ) );
    
    }
    

     

    现在不能再这样判断了,要能够智能的提出断言,例如,如果发现一个房子是红色的,就断言里面住着英国人。好吧,这真是一个大工程,我只摘取部分代码:

    public class Question
    {
    
    
      private class Clue0 : ClueBase
      {
        protected override bool Apply( Conjecture conjecture )
        {
          if ( conjecture.ExistColors.Distinct().Count() != conjecture.ExistColors.Count() )
            throw new Exception();
          if ( conjecture.ExistNationalities.Distinct().Count() != conjecture.ExistNationalities.Count() )
            throw new Exception();
          if ( conjecture.ExistDrinks.Distinct().Count() != conjecture.ExistDrinks.Count() )
            throw new Exception();
          if ( conjecture.ExistSmokes.Distinct().Count() != conjecture.ExistSmokes.Count() )
            throw new Exception();
          if ( conjecture.ExistPets.Distinct().Count() != conjecture.ExistPets.Count() )
            throw new Exception();
    
          if ( conjecture.ExistColors.Count() == 4 )
            conjecture.First( house => house.Color == null ).Color = Conjecture.allColors.Except( conjecture.ExistColors ).First();
    
          if ( conjecture.ExistNationalities.Count() == 4 )
            conjecture.First( house => house.Nationality == null ).Nationality = Conjecture.allNationalities.Except( conjecture.ExistNationalities ).First();
    
          if ( conjecture.ExistDrinks.Count() == 4 )
            conjecture.First( house => house.Drinks == null ).Drinks = Conjecture.allDrinks.Except( conjecture.ExistDrinks ).First();
    
          if ( conjecture.ExistSmokes.Count() == 4 )
            conjecture.First( house => house.Smokes == null ).Smokes = Conjecture.allSmokes.Except( conjecture.ExistSmokes ).First();
    
          if ( conjecture.ExistPets.Count() == 4 )
            conjecture.First( house => house.Pet == null ).Pet = Conjecture.allPets.Except( conjecture.ExistPets ).First();
    
          return false;
        }
      }
    
    
    
      //1、The Brit lives in the red house 
      private class Clue1 : ClueBase
      {
        protected override bool Apply( Conjecture conjecture )
        {
          var house = conjecture.FirstOrDefault( item => item.Color == "red" );
          if ( house != null )
          {
            house.Nationality = "Brit";
            return true;
          }
    
          house = conjecture.FirstOrDefault( item => item.Nationality == "Brit" );
          if ( house != null )
          {
            house.Color = "red";
            return true;
          }
    
          return false;
        }
      }
    
    
      //2、The Swede keeps dogs as pets 
      private class Clue2 : ClueBase
      {
        protected override bool Apply( Conjecture conjecture )
        {
          var house = conjecture.FirstOrDefault( item => item.Nationality == "Swede" );
          if ( house != null )
          {
            house.Pet = "dogs";
            return true;
          }
    
          house = conjecture.FirstOrDefault( item => item.Pet == "dogs" );
          if ( house != null )
          {
            house.Nationality = "Swede";
            return true;
          }
    
          return false;
        }
      }
    
      //...
    
    
      //15、The man who smokes Blend has a neighbor who drinks water. 
      private class Clue15 : ClueBase
      {
        protected override bool Apply( Conjecture conjecture )
        {
    
          var house = conjecture.FirstOrDefault( item => item.Smokes == "Blends" );
    
          if ( house != null )
          {
    
            Func<HouseConditon, HouseConditon, bool> check = ( left, right ) =>
            {
              if ( left == null )
              {
                right.Drinks = "water";
                return true;
              }
    
              if ( left.Drinks == "water" )
                return true;
    
              if ( left.Drinks != null )
              {
                right.Drinks = "water";
                return true;
              }
    
              return false;
            };
    
            if ( check( house.GetImmediateLeft(), house.GetImmediateRight() ) )
              return true;
            if ( check( house.GetImmediateRight(), house.GetImmediateLeft() ) )
              return true;
          }
    
          house = conjecture.FirstOrDefault( item => item.Drinks == "water" );
    
          if ( house != null )
          {
    
            Func<HouseConditon, HouseConditon, bool> check = ( left, right ) =>
            {
              if ( left == null )
              {
                right.Smokes = "Blends";
                return true;
              }
    
              if ( left.Smokes == "Blends" )
                return true;
    
              if ( left.Smokes != null )
              {
                right.Smokes = "Blends";
                return true;
              }
    
              return false;
            };
    
            if ( check( house.GetImmediateLeft(), house.GetImmediateRight() ) )
              return true;
            if ( check( house.GetImmediateRight(), house.GetImmediateLeft() ) )
              return true;
          }
    
    
          return false;
        }
      }
    
    
    
      public static IEnumerable<IClue> GetClues()
      {
        yield return new Clue1();
        yield return new Clue2();
        yield return new Clue3();
        yield return new Clue4();
        yield return new Clue5();
        yield return new Clue6();
        yield return new Clue7();
        yield return new Clue8();
        yield return new Clue9();
        yield return new Clue10();
        yield return new Clue11();
        yield return new Clue12();
        yield return new Clue13();
        yield return new Clue14();
        yield return new Clue15();
    
        yield return new Clue0();
      }
    
    }
    

    请注意Clue0,这个其实是写在题目开头的已知条件,即所有房子颜色、宠物等都不相同。

    Clue的基类型ClueBase负责捕获异常,并实现接口。

    public interface IClue
    {
      bool IsApplied { get; }
      bool TryApply( Conjecture conjecture );
    }
    
    
    
    public abstract class ClueBase : IClue
    {
    
      public bool IsApplied
      {
        get;
        set;
      }
    
      public virtual bool TryApply( Conjecture conjecture )
      {
        try
        {
          IsApplied = Apply( conjecture );
          return true;
        }
        catch
        {
          return false;
        }
      }
    
      protected abstract bool Apply( Conjecture conjecture );
    
      public override string ToString()
      {
        if ( IsApplied )
          return "Applied";
        else
          return "NotApply";
      }
    }
    



    好了,断言的冲突的问题采用异常来解决,即如果一个断言说A房子里住的是挪威人,而另一个断言已经宣布这个里面住的是英国人了,这样就造成了冲突,让它抛异常吧。这个逻辑写在了房子的属性里:

    /// <summary>
    /// 代表一个房子的所有情况
    /// </summary>
    public class HouseConditon
    {
    
      private string _color = null;
      public string Color
      {
        get { return _color; }
        set
        {
          if ( _color != null && _color != value )
            throw new Exception();
          _color = value;
        }
      }
    
      private string _nationality = null;
      public string Nationality
      {
        get { return _nationality; }
        set
        {
          if ( _nationality != null && _nationality != value )
            throw new Exception();
          _nationality = value;
        }
      }
    
      private string _drinks = null;
      public string Drinks
      {
        get { return _drinks; }
        set
        {
          if ( _drinks != null && _drinks != value )
            throw new Exception();
          _drinks = value;
        }
      }
    
      private string _smokes = null;
      public string Smokes
      {
        get { return _smokes; }
        set
        {
          if ( _smokes != null && _smokes != value )
            throw new Exception();
          _smokes = value;
        }
      }
    
      private string _pet = null;
      public string Pet
      {
        get { return _pet; }
        set
        {
          if ( _pet != null && _pet != value )
            throw new Exception();
          _pet = value;
        }
      }
    
    
      internal HouseConditon( Conjecture conjectur, int index )
      {
        Conjecture = conjectur;
        Index = index;
      }
    
    
    
      public Conjecture Conjecture { get; private set; }
    
      public int Index { get; private set; }
    
    
    
    
      /// <summary>
      /// 获取紧挨在左边的房子
      /// </summary>
      /// <returns></returns>
      public HouseConditon GetImmediateLeft()
      {
        if ( Index <= 0 )
          return null;
        else
          return Conjecture[Index - 1];
    
      }
    
    
      /// <summary>
      /// 获取紧挨在右边的房子
      /// </summary>
      /// <returns></returns>
      public HouseConditon GetImmediateRight()
      {
        if ( Index >= 4 )
          return null;
        else
          return Conjecture[Index + 1];
    
      }
    
      public override string ToString()
      {
        return string.Format( "{0,10}; {1,7}; {2,7}; {3,10}; {4,7}", Nationality, Color, Drinks, Smokes, Pet );
      }
    }
    

    那个Conjecture其实就是房子状态的集合,具体如下:

    /// <summary>
    /// 代表一种假设
    /// </summary>
    public class Conjecture : IEnumerable<HouseConditon>
    {
    
      private HouseConditon[] _houses;
    
      public Conjecture()
      {
        _houses = new HouseConditon[5].Initialize( i => new HouseConditon( this, i ) );
      }
    
    
      public HouseConditon this[int index]
      {
        get { return _houses[index]; }
      }
    
    
    
      #region IEnumerable<House> 成员
    
      public IEnumerator<HouseConditon> GetEnumerator()
      {
        return ( (IEnumerable<HouseConditon>) _houses ).GetEnumerator();
      }
    
      #endregion
    
      #region IEnumerable 成员
    
      System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
      {
        return GetEnumerator();
      }
    
      #endregion
    
    
      public override string ToString()
      {
        return string.Format( "First: {0}\nSecond:{1}\nThird: {2}\nForth: {3}\nFifth: {4}\n", this[0], this[1], this[2], this[3], this[4] );
      }
    
      public Conjecture Clone()
      {
        var instance = new Conjecture();
    
        for ( int i = 0; i < 5; i++ )
        {
          instance[i].Nationality = this[i].Nationality;
          instance[i].Color = this[i].Color;
          instance[i].Drinks = this[i].Drinks;
          instance[i].Pet = this[i].Pet;
          instance[i].Smokes = this[i].Smokes;
        }
        return instance;
      }
    
      public static readonly string[] allColors = new string[] { "red", "green", "white", "yellow", "blue" };
      public static readonly string[] allDrinks = new string[] { "tea", "coffee", "milk", "juice", "water" };
      public static readonly string[] allSmokes = new string[] { "Pall Mall", "Blends", "Bluemaster", "Dunhill", "Prince" };
      public static readonly string[] allNationalities = new string[] { "Brit", "Swede", "Dane", "Norwegian", "German" };
      public static readonly string[] allPets = new string[] { "dogs", "cats", "birds", "horses", "fish" };
    
    
      public IEnumerable<string> ExistColors
      {
        get { return from house in this where house.Color != null select house.Color; }
      }
    
      public IEnumerable<string> ExistNationalities
      {
        get { return from house in this where house.Nationality != null select house.Nationality; }
      }
    
      public IEnumerable<string> ExistDrinks
      {
        get { return from house in this where house.Drinks != null select house.Drinks; }
      }
    
      public IEnumerable<string> ExistSmokes
      {
        get { return from house in this where house.Smokes != null select house.Smokes; }
      }
    
      public IEnumerable<string> ExistPets
      {
        get { return from house in this where house.Pet != null select house.Pet; }
      }
    
    }
    

    好了,万事俱备,写一个程序跑跑。

    但是发现在一片空白之下,通过这样的推理,程序只能找出三个房屋属性的确定位置,比如说喝牛奶的房子在中间。

    解决的办法很简单,当电脑发现不能通过规则得到进一步的结果的时候,就猜一个。

    猜测之后再去匹配规则,如果还不能得到进一步的结果,那么就再猜。

    猜测的函数很简单:

    private static void Guess( Conjecture conjecture )
    {
    
      Console.WriteLine( "Hmmmmmm...Guess!..." );
    
    begin:
    
      var house = conjecture[random.Next( 5 )];
    
      switch ( random.Next( 4 ) )
      {
        case 0:
          if ( house.Nationality == null )
          {
            var nationalities = Conjecture.allNationalities.Except( conjecture.ExistNationalities ).ToArray();
            house.Nationality = nationalities[random.Next( nationalities.Length )];
            return;
          }
          else
            goto begin;
    
        case 1:
          if ( house.Color == null )
          {
            var colors = Conjecture.allColors.Except( conjecture.ExistColors ).ToArray();
            house.Color = colors[random.Next( colors.Length )];
            return;
          }
          else
            goto begin;
    
        case 2:
          if ( house.Drinks == null )
          {
            var drinks = Conjecture.allDrinks.Except( conjecture.ExistDrinks ).ToArray();
            house.Drinks = drinks[random.Next( drinks.Length )];
            return;
          }
          else
            goto begin;
    
        case 3:
          if ( house.Smokes == null )
          {
            var smokes = Conjecture.allSmokes.Except( conjecture.ExistSmokes ).ToArray();
            house.Smokes = smokes[random.Next( smokes.Length )];
            return;
          }
          else
            goto begin;
    
      }
    }
    

    这里没有写猜测宠物并不是出于什么特别的原因,而是因为懒的缘故,电脑猜这么几个东西已经足够得到结论了。

    最终的程序样子是这样的:

    Conjecture conjecture = new Conjecture();
    
    var clues = Question.GetClues().ToArray();
    
    int applies = 0;
    
    Stack<Conjecture> stack = new Stack<Conjecture>();
    
    while ( applies < 15 )
    {
    
      var statusString = conjecture.ToString() + applies;
    
      if ( !clues.All( item => item.TryApply( conjecture ) ) || ( applies = clues.Count( item => item.IsApplied ) ) == 0 )
      {
        conjecture = new Conjecture();
        clues = Question.GetClues().ToArray();
        Console.WriteLine( "ooooooooops......my god....." );
        continue;
      }
    
    
    
      applies = clues.Count( item => item.IsApplied );
      if ( statusString == conjecture.ToString() + applies )
      {
        Guess( conjecture );
        clues = Question.GetClues().ToArray();
      }
    
      Console.WriteLine( conjecture );
    
    }
    
    
    Console.WriteLine( "Found a result!!!" );
    Console.WriteLine( conjecture );
    Console.WriteLine();
    
    Console.ReadLine();
    

    哈哈,执行一把,在我的破本本上平均十几秒钟就能找到结果。很不错了,最关键的是,这是用人脑子的方式思考问题的程序。

    性能不是这个程序的主要诉求,这个程序的革命性意义在于,15条线索(规则),都是独立于主逻辑之外的,我们可以随时新增和修改线索,这个程序同样能找到合理的解答。

    当然这个程序还狠不完美,断言的代码冗余很严重。而这个问题,在表达式树出现后,呵呵,,,,,显然用表达式树来描述线索,再分析表达式树来创建断言函数。将使得这个程序更上一层楼。但我很懒,有兴趣的同学可以自己试试看哦。。。。

  • 相关阅读:
    TEN
    out.println()、document.write()、document.getelementbyid()
    正则表达式
    DOM与BOM
    伪类和伪元素
    Grid(未完全完成)
    position
    表单
    API,WEB API
    Event Flow
  • 原文地址:https://www.cnblogs.com/Ivony/p/1576290.html
Copyright © 2011-2022 走看看