您的位置:首页 > 其它

让电脑像人脑一样思考,谁养鱼问题断言推理解法

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

我设计的第一个版本的思路是这样的,穷举所有的情况,然后将已知线索作为筛选器来检查每一种情况,如果有一种情况符合所有的线索,则就是最终答案。
类似于排列组合这种运算,老实说还真没有什么现成的方案,还是老老实实的笛卡尔积再筛选吧。
利用多重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条线索(规则),都是独立于主逻辑之外的,我们可以随时新增和修改线索,这个程序同样能找到合理的解答。

当然这个程序还狠不完美,断言的代码冗余很严重。而这个问题,在表达式树出现后,呵呵,,,,,显然用表达式树来描述线索,再分析表达式树来创建断言函数。将使得这个程序更上一层楼。但我很懒,有兴趣的同学可以自己试试看哦。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐