愛因斯坦曾在20世紀初提過一個經典問題,據說世界上有98%的人回答不出來
問題:在一條街上,有5座房子,噴了5中顏色。每個房子住著不同國籍的人。每個人喝不同的飲料,抽不同品牌的香煙,養不同的寵物。
問題是:誰養魚?
提示:1.英國人住紅色房子
2.瑞典人養狗
3.丹麥人喝茶
4.綠色房子在白色房子左邊
5.綠房子主人喝咖啡
6.抽PallMall香煙的人養鳥
7.黃色房子的主人抽Dunhill香煙
8.住在中間房子的人喝牛奶
9.挪威人住第一間房
10.抽Bleeds香煙的人住在養貓的人隔壁
11.養馬的人住抽Dunhill香煙的人隔壁
12.抽BlueMaster的人喝啤酒
13.德國人抽Prince香煙
14.挪威人住藍色房子隔壁
15.抽Bleeds香煙的人有一個喝水的鄰居
作為程序員我並不想用推理的方式解密,而是通過程序來計算出答案,大致思路是,得到所有可能情況的組合,遍歷所有的組合,找到滿足所有條件的組合。這樣養魚的人就一目了然,難點就在於如何把這些條件轉換成計算機可以識別的代碼。(注:程序使用語言C#)
假設5個房子從左到右的編號為1,2,3,4,5,每個房子擁有一個顏色,而5個國籍的人分別和5個房子綁定,再加上人本身的3個習慣(抽煙,喝,寵物),所以可以看做每個國籍的人都擁有5個屬性,構造一個“人”的類
[csharp]
class Person
{
public int index { get; set; }
public Color color { get; set; }
public Drink drink { get; set; }
public Cigarette cigarette { get; set; }
public Pet pet { get; set; }
}
index表示這個人的位置,也就是他在第幾間房子裡,index的范圍在1-5之間,color表示這個房子的顏色,剩下的4個屬性用枚舉定義
[csharp]
enum Color
{
Red,
White,
Yellow,
Green,
Blue
}
enum Drink
{
Tea,
Milk,
Coffee,
Bear,
Water
}
enum Cigarette
{
PallMall,
Dunhill,
Bleeds,
BlueMaster,
Prince
}
enum Pet
{
Fish,
Dog,
Bird,
Cat,
Horse
}
這樣一來,由這5個屬性就可以組合成5^5=3125種不同的情況,接下來定義一個“人”的集合,這個集合包括所有可能的情況,用5個循環嵌套賦值
[csharp]
static void Foreach(ref List<Person> list)
{
for (int i_index = 0; i_index < 5; i_index++)
{
for (int i_color = 0; i_color < 5; i_color++)
{
for (int i_drink = 0; i_drink < 5; i_drink++)
{
for (int i_cig = 0; i_cig < 5; i_cig++)
{
for (int i_pet = 0; i_pet < 5; i_pet++)
{
list.Add(new Person()
{
index = i_index + 1,
color = (Color)i_color,
drink = (Drink)i_drink,
cigarette = (Cigarette)i_cig,
pet = (Pet)i_pet
});
}
}
}
}
}
}
觀察條件就會發現其實有些情況根本就不會存在,比如綠房子主人喝咖啡所以如果某人的Color屬性等於Green的話那麼他的Drink屬性一定等於Coffee,所以Color=Green和Drink=Bear或其他Drink這些組合都是不存在的。也就是說綠房主人不可能喝除咖啡以外的飲料,反過來說,喝咖啡的人也不可能不住綠房子,再看看這些類似的條件:5.綠房子主人喝咖啡6.抽PallMall香煙的人養鳥 7.黃色房子的主人抽Dunhill香煙 8.住在中間房子的人喝牛奶 12.抽BlueMaster的人喝啤酒 9.挪威人住第一間房和14.挪威人住藍色房子隔壁由9和14這兩2個條件可以推導出2號房的顏色是藍色,根據這些約束條件畫了個草圖
連線表示2個屬性是關聯的,通過這個可以在開始構造的集合裡面,排除掉這些不可能存在的情況,比如2-Blue,則表示不可能存在index=2並且color!=Blue的情況,或者
color=Blue並且index!=2的情況,依次類推,觀察草圖的連線,將不符合條件的情況全部從集合裡排除
[csharp]
//9.挪威人住第一間房
//14.挪威人住藍色房子隔壁
list = list.Except(list.Where(p => (p.index == 2 && p.color != Color.Blue) || (p.index != 2 && p.color == Color.Blue))).ToList();
//8.住在中間房子的人喝牛奶
list = list.Except(list.Where(p => (p.index == 3 && p.drink != Drink.Milk) || (p.index != 3 && p.drink == Drink.Milk))).ToList();
//7.黃色房子的主人抽Dunhill香煙
list = list.Except(list.Where(p => (p.color == Color.Yellow && p.cigarette != Cigarette.Dunhill) || (p.color != Color.Yellow && p.cigarette == Cigarette.Dunhill))).ToList();
//5.綠房子主人喝咖啡
list = list.Except(list.Where(p => (p.color == Color.Green && p.drink != Drink.Coffee) || (p.color != Color.Green && p.drink == Drink.Coffee))).ToList();
//12.抽BlueMaster的人喝啤酒
list = list.Except(list.Where(p => (p.drink == Drink.Bear && p.cigarette != Cigarette.BlueMaster) || (p.drink != Drink.Bear && p.cigarette == Cigarette.BlueMaster))).ToList();
//6.抽PallMall香煙的人養鳥
list = list.Except(list.Where(p => (p.cigarette == Cigarette.PallMall && p.pet != Pet.Bird) || (p.cigarette != Cigarette.PallMall && p.pet == Pet.Bird))).ToList();
這個時候list的數量已經由之前的3125驟減至227了,等等,還有個約束條件綠色房子在白色房子左邊也就是說index屬性等於1並且color屬性等於white的情況是不可能出現的,因為index=1&&color=white表示白色房子是第一間房子,這樣它的左邊就不可能有房子了,進一步過濾
[csharp]
//4.綠色房子在白色房子左邊
list = list.Except(list.Where(p => p.index == 1 && p.color == Color.White)).ToList();
暫時就只發現這麼多了, 如果你還可以推理出更多的過濾條件就再好不過了,約束越多,list的數量就會越少,接下來的遍歷就會越輕松。最終目的是要得到滿足所有條件的5個國籍人的組合,每一個組合就是一個解答,再來分析1.英國人住紅色房子 2.瑞典人養狗 3.丹麥人喝茶 9.挪威人住第一間房 13.德國人抽Prince香煙
英國人住紅色房子,那麼我們只要從集合裡面過濾出color=red的就可以了,值得注意的是瑞典人養狗,那麼英國人就不可能養狗了,同理英國人也不可能喝茶了,而瑞典人的房子也不可能是紅色的了……以此類推,
[csharp]
List<Person> England = list.Where(p => p.index != 1 && p.color == Color.Red && p.pet != Pet.Dog && p.drink != Drink.Tea && p.cigarette != Cigarette.Prince).ToList();
List<Person> Sweden = list.Where(p => p.index != 1 && p.color != Color.Red && p.pet == Pet.Dog && p.drink != Drink.Tea && p.cigarette != Cigarette.Prince).ToList();
List<Person> Denmark = list.Where(p => p.index != 1 && p.color != Color.Red && p.pet != Pet.Dog && p.drink == Drink.Tea && p.cigarette != Cigarette.Prince).ToList();
List<Person> Norway = list.Where(p => p.index == 1 && p.color != Color.Red && p.pet != Pet.Dog && p.drink != Drink.Tea && p.cigarette != Cigarette.Prince).ToList();
List<Person> Germany = list.Where(p => p.index != 1 && p.color != Color.Red && p.pet != Pet.Dog && p.drink != Drink.Tea && p.cigarette == Cigarette.Prince).ToList();
到這裡得到了滿足條件的英國人,瑞典人,丹麥人,挪威人和德國人的集合,他們的數量分別是18,12,18,7,18,共18*12*18*7*18=489888種組合,接下來的工作就是遍歷每個組合,在剩下的條件裡去驗證。遍歷5個國籍人的集合,從每個集合裡面取一個人出來,把這5個人存到一個List<Person>裡面,表示一個組合,如果驗證成功則表示這個組合是解答
[csharp]
static List<List<Person>> Work(List<Person> England, List<Person> Sweden, List<Person> Denmark, List<Person> Norway, List<Person> Germany)
{
List<List<Person>> Result = new List<List<Person>>();
int count = 1;
long total = England.Count * Sweden.Count * Denmark.Count * Norway.Count * Germany.Count;
foreach (var e in England)
{
foreach (var s in Sweden)
{
foreach (var d in Denmark)
{
foreach (var n in Norway)
{
foreach (var g in Germany)
{
List<Person> l = new List<Person>();
l.Add(e);
l.Add(s);
l.Add(d);
l.Add(n);
l.Add(g);
if (TestDistinct(l) && Test(l))
{
Result.Add(l);
}
Console.WriteLine(string.Format("測試第{0}/{1}個結果,百分比:{2}%", count, total, count * 100 / total));
count++;
}
}
}
}
}
return Result;
}
通過剩下的條件驗證:
[csharp]
static bool Test(List<Person> list)
{
//4.綠色房子在白色房子左邊
if (list.Single(p => p.color == Color.Green).index >= list.Single(p => p.color == Color.White).index) return false;
//10.抽Bleeds香煙的人住在養貓的人隔壁
var p1 = list.Single(p => p.cigarette == Cigarette.Bleeds);
var p2 = list.Single(p => p.pet == Pet.Cat);
if (p1.index != p2.index - 1 && p1.index != p2.index + 1) return false;
//11.養馬的人住抽Dunhill香煙的人隔壁
var p3 = list.Single(p => p.pet == Pet.Horse);
var p4 = list.Single(p => p.cigarette == Cigarette.Dunhill);
if (p3.index != p4.index - 1 && p3.index != p4.index + 1) return false;
//15.抽Bleeds香煙的人有一個喝水的鄰居
var p5 = list.Single(p => p.cigarette == Cigarette.Bleeds);
var p6 = list.Single(p => p.drink == Drink.Water);
if (p5.index != p6.index - 1 && p5.index != p6.index + 1) return false;
return true;
}
先從集合中找到相應特征的2個人,通過比較2個人的index屬性判斷是否為鄰居關系。當然在驗證之前必須要保證每個人擁有的屬性是唯一的,因為之前只是判斷了每個國籍的人可能出現的所有情況,並沒有組合起來判斷,假設在這個集合中有2個人的color屬性都是Green,這種情況肯定是不存在的,所以在驗證前就應該排除掉,像這樣
[csharp]
if (list.Count(p => p.index == 1) != 1) return false;
if (list.Count(p => p.index == 2) != 1) return false;
if (list.Count(p => p.index == 3) != 1) return false;
if (list.Count(p => p.index == 4) != 1) return false;
if (list.Count(p => p.index == 5) != 1) return false;
if (list.Count(p => p.color == Color.Red) != 1) return false;
if (list.Count(p => p.color == Color.White) != 1) return false;
if (list.Count(p => p.color == Color.Yellow) != 1) return false;
……
return true;
最後,將所有滿足條件的List<Person>放到一起,輸出結果:
[csharp]
if (Result.Count == 0)
Console.WriteLine("未得到任何結果!");
else
{
Console.WriteLine("得到" + Result.Count + "個結果");
for (int i = 0; i < Result.Count; i++)
{
Console.WriteLine("******************第" + (i + 1) + "個解答******************");
Console.WriteLine(string.Format("英國人住第{0}間房子,顏色:{1},喝{2},抽{3},養{4}", Result[i][0].index, Result[i][0].color, Result[i][0].drink, Result[i][0].cigarette, Result[i][0].pet));
Console.WriteLine(string.Format("瑞典人住第{0}間房子,顏色:{1},喝{2},抽{3},養{4}", Result[i][1].index, Result[i][1].color, Result[i][1].drink, Result[i][1].cigarette, Result[i][1].pet));
Console.WriteLine(string.Format("丹麥人住第{0}間房子,顏色:{1},喝{2},抽{3},養{4}", Result[i][2].index, Result[i][2].color, Result[i][2].drink, Result[i][2].cigarette, Result[i][2].pet));
Console.WriteLine(string.Format("挪威人住第{0}間房子,顏色:{1},喝{2},抽{3},養{4}", Result[i][3].index, Result[i][3].color, Result[i][3].drink, Result[i][3].cigarette, Result[i][3].pet));
Console.WriteLine(string.Format("德國人住第{0}間房子,顏色:{1},喝{2},抽{3},養{4}", Result[i][4].index, Result[i][4].color, Result[i][4].drink, Result[i][4].cigarette, Result[i][4].pet));
}
}
好了,到這裡程序已經編寫完成了,激動人心的時刻來了,開始運行,遍歷所有組合大約用時2分鐘,答案就是