程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 用程序解密愛因斯坦經典難題

用程序解密愛因斯坦經典難題

編輯:C#入門知識

愛因斯坦曾在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分鐘,答案就是

 

\

\

 

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved