目錄
- 需求;
- 需求該如何分析呢,怎么划分成小需求呢?
- 如何把小需求編排成完整需求;
學有所得
- 學會分析需求,由哪些組成(規則,邏輯等);
- 能把的需求分解成很多子需求、或孫需求、或童孫需求,直到每個需求很清晰可實施地為止
- 學會把各種子孫需求,通過組合編排,最終成為一個完整的大需求解決方案
需求
需求:任意1-10中的4個數字,使用加減乘除計算得出24結果的可能組合;
通過初步分析,我們可以得到如下規則:
規則:1、任意1-10中的4個數字;
2、使用加減乘除計算得出24;
3、在任何一次計算中不能出現小數,
比如:(4.0 + 8.0) / (3.0 / 6.0) = 24.0,這種是不算的,雖然最終結果是24,因為3/6=0.5
(8.0 / 3.0) * (4.0 + 5.0) = 24.0,雖然最終為24,但是在第一步結果卻是小數,所以不成立;
View Code
4、整個運算中應該使用double類型,因為整數相除,使用int類型,在計算機中,會把余數抹掉,直接取整,這樣就會造成結果不正確;
那需求該如何分析呢,怎么划分成小需求呢?
一般來說我們都會通過案例來分析,比如:這個需求,我們使用傳統的計算,假設我們已經有這四個數[3,4,8,6],可能會有如下組合:
方案:((4.0 + 8.0) * 6.0) / 3.0=24.0;
方案:((3.0 * 4.0) - 8.0) * 6.0=24.0;
方案:((8.0 - 6.0) * 3.0) * 4.0=24.0;
方案:((4.0 + 8.0) / 3.0) * 6.0=24.0;
方案:(4.0 * 3.0) * (8.0 - 6.0) = 24.0;
方案:(6.0 / 3.0) * (4.0 + 8.0) = 24.0;
我們暫時先分析這個幾個方案,大家看到這里,可以先思考一下有什么規律,有什么規則;
....................................................................................................Thinking..............................
從這些方案中,我們可以得出如下蹊蹺之處:
1、所有的方案中,都在這四個數的所有可能排列組合中(我記憶之中,應該是高中數學的知識點);
2、我們可以把計算法則歸納為兩種,所有的都可以歸納到一下兩種中去;
第一、從左到右的依次計算;
第二、兩兩組合,前兩個數計算結果和后兩個數的計算結果再次計算;
第三、每個方案都有3個運算符;
不知道大家是不是和我發現的一樣不,或者說有更多的發現;我認為不管什么發現都可以先列出來,然后在逐個去去除一些太離譜的發現;
我們再繼續順藤摸瓜,到此我們可以把需求分解如下:
我們繼續分析需求,看看是否可以再次分解
從上面的需求中我們可以進一步進行分解
第一、如何獲取四個數的所有排列組合?
1、舉例,我們繼續使用案列來分析,比如 [3,4,8,6]
[3,4,8,6](基准)
[4,3,8,6](第二和第一調換)
[3,8,4,6] [8,3,4,6](第三和第二調換,第二和第一調換)
[3,4,6,8] [3,6,4,8] [6,3,4,8] (第四和第三調換, 第三和第二調換,第二和第一調換)
這樣是不是所有的排列組合呢?顯然不是?因為還有三種基准進行上面的排列組合,也就是上面每行最后一列
[4,3,8,6](基准2)
[8,3,4,6](基准3)
[6,3,4,8](基准4)
2、通過上面的舉例,我們就可以先獲取所有的基准組合;
3、通過上面,我們可以知道每種基准的所有組合;
4、通過上面的方法獲取的組合會有重復,需要前需要去重;
這樣我們就能獲取4個數的所有排列組合;我感覺這種獲取所有排列組合的算法很笨重(有沒有感覺有點想冒泡排序),不優雅,肯定有更優的方案,只是我不知道而已,如果知道的可以留言,謝謝;
所有排列分析到此,是不是還需要繼續分析,可以繼續思考;本人感覺可以落地了;如果覺得需要繼續分析的,可以繼續分解,知道自己很清晰,知道怎么干為止(這個因人而異);請看代碼;
獲取所有基准代碼:
List<double[]> resultAllList = new List<double[]>(); List<double[]> list = new List<double[]>(); list.Add(array); list.Add(new double[] { array[1], array[2], array[3], array[0] }); list.Add(new double[] { array[2], array[3], array[0], array[1] }); list.Add(new double[] { array[3], array[0], array[1], array[2] });
獲取每個基准的所有排列組合:
/// <summary> /// 獲取array的所有可能組合 /// </summary> /// <param name="list"></param> /// <param name="array"></param> public static void GetAllArray(List<double[]> list, double[] array) { if (!Exists(list, array)) { list.Add(array); } for (int i = 1; i < 4; i++) { double[] arrayCopy = DeepCopyByBin<double[]>(array); List<double[]> newList = GetArrayList(arrayCopy, i); newList.ForEach((item) => { if (!Exists(list, item)) { list.Add(item); } }); } } /// <summary> /// 獲取array下標遇到i的位置左右組合 /// </summary> /// <param name="array"></param> /// <param name="i"></param> /// <returns></returns> public static List<double[]> GetArrayList(double[] array, int i) { List<double[]> list = new List<double[]>(); for (int j = i; j > 0; j--) { double temp = array[j]; array[j] = array[j - 1]; array[j - 1] = temp; list.Add(array); array = DeepCopyByBin<double[]>(array); } return list; } /// <summary> /// array是否存啊在list中 /// </summary> /// <param name="list"></param> /// <param name="array"></param> /// <returns></returns> public static bool Exists(List<double[]> list, double[] array) { bool isExist = false; for (int i = 0; i < list.Count; i++) { var item = list[i]; if (item[0] == array[0] && item[1] == array[1] && item[2] == array[2] && item[3] == array[3]) { isExist = true; break; } } return isExist; }
第二,對於算法法則該如何繼續分析呢?我們可以繼續使用舉例
從上面隨意獲取一種排列組合,比如:[3,4,8,6]
1、從左到右的組合,在上面四個數字中,任意兩個數中,我們可以有+,- ,*,/這四種算法,這又是一種計算的所有排列組合,並把結果和24對比,如果相等,那就是可行方案;
那我們是不是繼續使用上面獲取組合的方式呢?顯然不是,這里關鍵點在於:任意兩個數中都有+-*/的算法,這里我們可以使用三個for循環解決;
舉例:((3.0 * 4.0) - 8.0) * 6.0=24.0;
/// <summary> /// 計算array能算24點的所有組合,從左到右的順序 /// </summary> /// <param name="array"></param> /// <returns></returns> public static int Caculate24Point(double[] array) { int count = 0; if (array.Length != 4) { throw new Exception("不是四個數"); } for (int i = 0; i < operators.Length; i++) { string op = operators[i]; String expressionStr = ""; double result = GetTwoNumCaculate(array[0], array[1], op); if (!IsValidResult(result)) { continue; } expressionStr = $"({array[0]}{op}{array[1]})"; for (int j = 0; j < operators.Length; j++) { string op2 = operators[j]; double result1 = GetTwoNumCaculate(result, array[2], op2); if (!IsValidResult(result1)) { continue; } String expressionStr2 = $"({expressionStr}{op2}{array[2]})"; for (int k = 0; k < operators.Length; k++) { string op3 = operators[k]; double result2 = GetTwoNumCaculate(result1, array[3], op3); String expressionStr3 = $"{expressionStr2}{op3}{array[3]}";//String.format("%s %s %s", expressionStr2, op3, array[3]); if (result2 == 24.0d) { count++; Console.WriteLine($"方案:{expressionStr3}={result2}"); } } } } return count; }
2、兩兩組合,思路和上面有些相似,
前兩個數的任意計算結果1,
后兩個數的任意計算結果2,
結果1和結果2的任意計算結果3,
結果3和24對比,如果相等,那就是可行方案;
舉例:(3.0 * 4.0) * (8.0 - 6.0) = 24.0;
/// <summary> /// 計算array能算24點的所有組合 ,兩兩組合 /// </summary> /// <param name="array"></param> /// <returns></returns> public static int Caculate24Point2(double[] array) { int count = 0; if (array.Length != 4) { throw new Exception("不是四個數"); } for (int i = 0; i < operators.Length; i++) { string op = operators[i]; double result1 = GetTwoNumCaculate(array[0], array[1], op); if (!IsValidResult(result1)) { continue; } String expressionStr1 = $"({array[0]}{op}{array[1]})"; for (int j = 0; j < operators.Length; j++) { string op2 = operators[j]; double result2 = GetTwoNumCaculate(array[2], array[3], op2); if (!IsValidResult(result2)) { continue; } String expressionStr2 = $"({array[2]}{op2}{array[3]})"; for (int k = 0; k < operators.Length; k++) { string op3 = operators[k]; double result3 = GetTwoNumCaculate(result1, result2, op3); String expressionStr3 = $"{expressionStr1} {op3} {expressionStr2}"; if (result3 == 24.0d) { count++; Console.WriteLine($"方案: {expressionStr3} = {result3}"); } } } } return count; }
某一種四個數的所有運算排列通過上面的方式我們可以全部獲取,分析到此,我覺得代碼可以落地,當然,如果你覺得還不夠清晰,可以繼續分析,直到自己清晰為止,這里我只是提供分解需求的思路而已;在軟件工程中,我們必定先分析需求,然后分解需求,我們有四色分析方式,我們還有DDD領域的分析方式等,都是需要通過逐步分解成更小的需求來反復驗證的;
到目前為止我們可以得出如下思維導圖

把各種子孫需求,通過組合編排,最終成為一個完整的大需求解決方案
最后,我們把每個小的需求加上一些規則邏輯組合成完整的大需求,我們暫時叫做編排吧;
這里其實也是一個難點,很多人希望一次性把代碼寫完整,寫正確,其實這種思路是不正確的,這樣只會增加代碼的難度,一次性能把代碼寫的有多完整多正確,這個跟每個人的編程經驗熟練度有關;
不管編程多牛,從無到有的敲代碼方向不是一次性把所有的代碼完成,重點方向把核心邏輯思路編寫上,其次才逐步把一些細節邏輯規則加上去,這個就和我們小時候學畫畫一樣,畫一顆樹先畫主干然后畫葉子最后添加果子和花之類的;
到目前為止是否完整呢?其實還差一點,任意的1-10的數字從哪里獲取,不過需求沒有明確,可以是用戶輸入,數據庫獲取,其他接口的傳入,我們這里就定位用戶輸入吧
獲取用戶輸入代碼如下:
double[] array = new double[4]; int index = 0; while (index < 4) { Console.WriteLine(String.Format("請輸入第{0}個1-10的整數", index + 1)); String tempNumStr = Console.ReadLine(); if (!int.TryParse(tempNumStr, out int tmpNum)) { Console.WriteLine("你輸入的不是一個整數"); continue; } if (tmpNum < 0 || tmpNum > 10) { Console.WriteLine("你輸入的數字不是1-10的數字"); continue; } array[index++] = (double)tmpNum; } Console.WriteLine($"你輸入的4個1-10的整數為{array[0]},{array[1]},{array[2]},{array[3]}");
最終完整代碼如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Threading.Tasks; namespace Point24Caculate { class Program { private static string[] operators = new string[] { "+", "-", "*", "/" }; static void Main(string[] args) { double[] array = new double[4]; int index = 0; while (index < 4) { Console.WriteLine(String.Format("請輸入第{0}個1-10的整數", index + 1)); String tempNumStr = Console.ReadLine(); if (!int.TryParse(tempNumStr, out int tmpNum)) { Console.WriteLine("你輸入的不是一個整數"); continue; } if (tmpNum < 0 || tmpNum > 10) { Console.WriteLine("你輸入的數字不是1-10的數字"); continue; } array[index++] = (double)tmpNum; } Console.WriteLine($"你輸入的4個1-10的整數為{array[0]},{array[1]},{array[2]},{array[3]}"); Console.WriteLine("結果如下:"); List<double[]> resultAllList = new List<double[]>(); List<double[]> list = new List<double[]>(); list.Add(array); list.Add(new double[] { array[1], array[2], array[3], array[0] }); list.Add(new double[] { array[2], array[3], array[0], array[1] }); list.Add(new double[] { array[3], array[0], array[1], array[2] }); for (int i = 0; i < list.Count; i++) { GetAllArray(resultAllList, DeepCopyByBin(list[i])); } int sum = 0; resultAllList.ForEach(itemArray => { sum += Caculate24Point(itemArray); sum += Caculate24Point2(itemArray); }); Console.WriteLine("總共方案數量:" + sum); Console.ReadLine(); } /// <summary> /// 獲取array的所有可能組合 /// </summary> /// <param name="list"></param> /// <param name="array"></param> public static void GetAllArray(List<double[]> list, double[] array) { if (!Exists(list, array)) { list.Add(array); } for (int i = 1; i < 4; i++) { double[] arrayCopy = DeepCopyByBin<double[]>(array); List<double[]> newList = GetArrayList(arrayCopy, i); newList.ForEach((item) => { if (!Exists(list, item)) { list.Add(item); } }); } } /// <summary> /// 獲取array下標遇到i的位置左右組合 /// </summary> /// <param name="array"></param> /// <param name="i"></param> /// <returns></returns> public static List<double[]> GetArrayList(double[] array, int i) { List<double[]> list = new List<double[]>(); for (int j = i; j > 0; j--) { double temp = array[j]; array[j] = array[j - 1]; array[j - 1] = temp; list.Add(array); array = DeepCopyByBin<double[]>(array); } return list; } /// <summary> /// array是否存啊在list中 /// </summary> /// <param name="list"></param> /// <param name="array"></param> /// <returns></returns> public static bool Exists(List<double[]> list, double[] array) { bool isExist = false; for (int i = 0; i < list.Count; i++) { var item = list[i]; if (item[0] == array[0] && item[1] == array[1] && item[2] == array[2] && item[3] == array[3]) { isExist = true; break; } } return isExist; } /// <summary> /// 深拷貝 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="obj"></param> /// <returns></returns> public static T DeepCopyByBin<T>(T obj) { object retval; using (MemoryStream ms = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); //序列化成流 bf.Serialize(ms, obj); ms.Seek(0, SeekOrigin.Begin); //反序列化成對象 retval = bf.Deserialize(ms); ms.Close(); } return (T)retval; } /// <summary> /// 計算array能算24點的所有組合,從左到右的順序 /// </summary> /// <param name="array"></param> /// <returns></returns> public static int Caculate24Point(double[] array) { int count = 0; if (array.Length != 4) { throw new Exception("不是四個數"); } for (int i = 0; i < operators.Length; i++) { string op = operators[i]; String expressionStr = ""; double result = GetTwoNumCaculate(array[0], array[1], op); if (!IsValidResult(result)) { continue; } expressionStr = $"({array[0]}{op}{array[1]})"; for (int j = 0; j < operators.Length; j++) { string op2 = operators[j]; double result1 = GetTwoNumCaculate(result, array[2], op2); if (!IsValidResult(result1)) { continue; } String expressionStr2 = $"({expressionStr}{op2}{array[2]})"; for (int k = 0; k < operators.Length; k++) { string op3 = operators[k]; double result2 = GetTwoNumCaculate(result1, array[3], op3); String expressionStr3 = $"{expressionStr2}{op3}{array[3]}";//String.format("%s %s %s", expressionStr2, op3, array[3]); if (result2 == 24.0d) { count++; Console.WriteLine($"方案:{expressionStr3}={result2}"); } } } } return count; } /// <summary> /// 計算array能算24點的所有組合 ,兩兩組合 /// </summary> /// <param name="array"></param> /// <returns></returns> public static int Caculate24Point2(double[] array) { int count = 0; if (array.Length != 4) { throw new Exception("不是四個數"); } for (int i = 0; i < operators.Length; i++) { string op = operators[i]; double result1 = GetTwoNumCaculate(array[0], array[1], op); if (!IsValidResult(result1)) { continue; } String expressionStr1 = $"({array[0]}{op}{array[1]})"; for (int j = 0; j < operators.Length; j++) { string op2 = operators[j]; double result2 = GetTwoNumCaculate(array[2], array[3], op2); if (!IsValidResult(result2)) { continue; } String expressionStr2 = $"({array[2]}{op2}{array[3]})"; for (int k = 0; k < operators.Length; k++) { string op3 = operators[k]; double result3 = GetTwoNumCaculate(result1, result2, op3); String expressionStr3 = $"{expressionStr1} {op3} {expressionStr2}"; if (result3 == 24.0d) { count++; Console.WriteLine($"方案: {expressionStr3} = {result3}"); } } } } return count; } /// <summary> /// 是否為合法的計算結果 /// </summary> /// <param name="result"></param> /// <returns></returns> public static bool IsValidResult(double result) { if (result < 1) { return false; } return result == Math.Floor(result); } private static double GetTwoNumCaculate(double num1, double num2, string operatorStr) { switch (operatorStr) { case "+": return num1 + num2; case "-": return num1 - num2; case "*": return num1 * num2; case "/": return num1 / num2; default: throw new Exception("運算符不符合規范"); } } } }
學有所得
- 是否學會了這種分析思路
- 是否這種需求分析思路可以運用在地方?
- 這種把每個原子需求編排成一個完整大需求,是否可以在他地方使用?
最后效果圖:

