Search This Blog

Sunday, March 27, 2016

萬丈高樓由地起

話說 Facebook 度出現咗一條小學生嘅題目:

   AB
-  CD
-----
   EF
+  GH
-----
  PPP


有人寫 Code 去計答案出嚟。趁假期得閒,貪得意玩埋一份。屋企唔想開 Visual Studio, 就走咗去 https://dotnetfiddle.net/.

因為多過一個答案,Brute force 難免,問題只係點樣解決得靚啲。觀察所得,係 AB-CD=EF 同 EF+GH=PPP 嘅解法一樣,Divide and Conquer, 即係解到 EF+GH=PPP, 就基本上解咗題。所以得出第一個版本係咁:
using System;
using System.Collections.Generic;
public class Program {
public static void Main() {
var level1 = Calc(111);
foreach (var i in level1) {
Console.WriteLine(string.Format("{0} + {1} = 111", i, 111 - i));
}
}
public static List<int> Calc(int n) {
var result = new List<int>();
for (int i = 10; i < 100; i++) {
int j = n - i;
var x = new HashSet<int>();
x.Add(i / 10);
x.Add(i % 10);
x.Add(j / 10);
x.Add(j % 10);
if (x.Count == 4) {
result.Add(i);
}
}
return result;
}
}
view raw ABCDEFGPPP.cs hosted with ❤ by GitHub


好快就可以意識到,只係 j 嘅計法唔同,加上
x.Add(n / 10);
x.Add(n % 10);
呢兩句太過累贅,於是就變成咁:
using System;
using System.Collections.Generic;
public class Program {
public static void Main() {
var level1 = Calc(111, (n, i, x) => {
AddDigits(x, i);
AddDigits(x, n - i);
}, 4);
foreach (var i in level1) {
var level2 = Calc(i, (n, j, x) => {
AddDigits(x, j);
AddDigits(x, n + j);
AddDigits(x, i);
AddDigits(x, 111 - i);
}, 8);
foreach (var j in level2) {
Console.WriteLine(string.Format("{2} - {3} = {0} + {1} = 111", i, 111 - i, j, i + j));
}
}
}
public static List<int> Calc(int n, Action<int, int, HashSet<int>> fn, int d) {
var result = new List<int>();
for (int i = 10; i < 100; i++) {
var x = new HashSet<int>();
fn(n, i, x);
if (x.Count == d) {
result.Add(i);
}
}
return result;
}
public static void AddDigits(HashSet<int> x, int n) {
x.Add(n / 10);
x.Add(n % 10);
}
}
view raw ABCDEFGPPP.cs hosted with ❤ by GitHub


之後就見到擺入 HashSet 嗰嘅數字,只係作為 Validation 嘅準備,可以 Refactor 成一個 IsValid 嘅 Delegate; 然後再將 AddDigits() 簡化為 CountDigits():
using System;
using System.Collections.Generic;
public class Program {
public static void Main() {
var level1 = Calc(111, (n, i) => CountDigits(i, n - i) == 4);
foreach (var i in level1) {
var level2 = Calc(i, (n, j) => CountDigits(j, n + j, i, 111 - i) == 8 && n + j > 9 && n + j < 100);
foreach (var j in level2) {
Console.WriteLine(string.Format("{2} - {3} = {0} + {1} = 111", i, 111 - i, j, i + j));
}
}
}
public static List<int> Calc(int n, Func<int, int, bool> isValid) {
var result = new List<int>();
for (int i = 10; i < 100; i++) {
if (isValid(n, i)) {
result.Add(i);
}
}
return result;
}
public static int CountDigits(params int[] numbers) {
var x = new HashSet<int>();
foreach (var n in numbers) {
x.Add(n / 10);
x.Add(n % 10);
}
return x.Count;
}
}
view raw ABCDEFGPPP.cs hosted with ❤ by GitHub


睇住個 Calc(), 覺得 value 不大,因為只係一個 Loop 入面有個 if, 於是就直接將佢 Inline 入個 Main() 度; CountDigits() 用 LINQ 一句 KO 埋:
using System;
using System.Linq;
public class Program {
public static void Main() {
for (int i = 10; i < 100; i++) {
if (CountDigits(i, 111 - i) == 4) {
for (int j = 10; j < 100; j++) {
if (CountDigits(j, i + j, i, 111 - i) == 8 && i + j > 9 && i + j < 100) {
Console.WriteLine(string.Format("{2} - {3} = {0} + {1} = 111", i, 111 - i, j, i + j));
}
}
}
}
}
public static int CountDigits(params int[] numbers) {
return numbers.SelectMany(o => new int[]{o / 10, o % 10}).Distinct().Count();
/*
var x = new HashSet<int>();
foreach (var n in numbers) {
x.Add(n / 10);
x.Add(n % 10);
}
return x.Count;
*/
}
}
view raw ABCDEFGPPP.cs hosted with ❤ by GitHub


最後,想用最少嘅 Code 做嘢; 掉走埋 CountDigits(); 改返 ab, cd, ef, gh, ppp 做 variable 名; 執走第一個 if; fix 埋 p = 1 呢個已知數:


再貪玩少少,用 LINQ 代替兩個 Loop:


或者好多人會覺得寫一大段 Code 去解決一條小學生嘅題目係無聊,唔夠 Cost Effective. 但係每個 Software Engineer, 都要有解決問題嘅熱誠。呢個 Exercise, 由一開始 Divide and Conquer 嘅想法,Refactor 成一條 LINQ 攪掂,係完全意想不到。可能做唔到傳說中嘅 Python 咁5行攪掂 (到呢一刻都未搵到個傳說响邊),但其實 C# 一啲都唔失禮。Ceremony vs Essence 嚟講,C# 都算係咁 (斜視 Java 中)。

大慨,呢個世界上有多過一個方法去解決問題,而大家都想搵最好嘅方法,最好可以係最快、最少 Code, 最 readable, 最 reusable, 最 maintainable 等等,無絕對標準的 (最快都重要分寫得快同行得快),响各方面達致平衡,就係 Software Engineering 引人入勝嘅地方。

既然平時返工要趕 Project 無時間要左抄右抄,放假有時間偶爾寫吓完全屬於自己認為最好嘅 Code, 何樂而不為?

P.S. 有無人識將呢句 Fluent style 嘅 LINQ 變成 Query style: new int[]{ef, gh, ab, cd}.SelectMany(o => new int[]{ o / 10, o % 10}).Distinct().Where(o => o != 1)

No comments:

Popular Posts