無私分享兩道百度做業幫的測試開發面試題!整理不易,請給贊~面試
【第一題】一共有二十五匹馬,五個賽道,每一個賽道每次只能跑一匹馬。問:最少多少次能選出3匹最快的馬?(不能記錄每匹馬跑徹底程所用的時間,只能經過比較誰先到達終點來判斷兩匹馬的孰快孰慢)shell
思路以下:數組
一、前五次:25匹馬,分紅5組,每組賽1次,共賽5次——這樣就能得出5匹馬(各組的第一);安全
二、第六次:讓這五匹馬賽一次,取前3,淘汰這五匹第一馬中的第四和第五(假設是第四組第一和第五族第一),比這兩匹馬慢的也都淘汰,共淘汰10匹馬(第四組和第五組全部馬)。數據結構
三、剩下的15匹馬(第一組、第二組、第三組),由於取前3,因此淘汰每一組中的第四和第五,共淘汰6匹馬。併發
四、如今還剩下9匹馬,他們是:dom
由於第六次比賽的時候已經得出了各組第一馬的快慢順序。假設馬的速度——第一組第一>第二組第一>第三組第一。則第一組第一是最快的,因此這匹馬無需再比,它就是第一快的。ide
剩下的八匹馬中:測試
第二組第1、第一組第二,他們倆須要比一次,看看誰是第二快的。大數據
第一組第3、第二組第2、第三組第一,他們仨須要比一次,看看誰是第三快的。
這五匹馬正好佔五個賽道,比一次,也就是第七次。
因此得出答案:至少需七次比賽,才能選出最快的三匹馬。
【第二題】假設一小時內接口共返回500萬條數據,數據假設爲{id='xxx' info='xxx' kk='xxx' target='' dd='xxx'}這種格式,不一樣的字段名和字段值間用空格隔開。問:怎麼得出target字段返回次數最多的值。
個人思路:先按空格切割字符串,把範圍鎖定在target字段的值域,把值域存到數組中。而後對數組進行一次去重和計數並保存到一個數據結構中,最後從這個數據結構中找出計數最多的分類,也就是target字段返回次數最多的值。
個人解法以下,分別用了三種方法(正常法、非線程安全集合的併發法、線程安全集合的併發法):
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq;
using System.Threading.Tasks; namespace FuckThatPussy { class Program { public static string data; public static List<string> dataSource = new List<string>(); public static List<string> results = new List<string>(); public static Hashtable dataHst = new Hashtable(); public static int maxCount; public static Object obj = new Object(); public static string theLock = "I'm a lock"; public static DateTime startTime; public static DateTime endTime; //Use the concurrent collection to store results. public static ConcurrentDictionary<string, int> dataDic = new ConcurrentDictionary<string, int>(); static void Main(string[] args) { CreateData(); SolveData("Solve the data normally:", SolveDataNormally, FindResultsFromHst); SolveData("Solve the data concurrently:", SolveDataParallel, FindResultsFromHst); SolveData("Solve the data using concurrent collection:", SolveDataConcurrently, FindResultsFromDic); Console.Read(); } private static void SolveData(string v, Action solveDataMethod, Func<List<string>> findResults) { Console.WriteLine(v); startTime = DateTime.Now; solveDataMethod.Invoke(); endTime = DateTime.Now; foreach (var result in findResults.Invoke()) { Console.WriteLine(result + " " + maxCount); } Console.WriteLine("Time use: " + (endTime - startTime).ToString()); } private static List<string> FindResultsFromHst() { //Find the results from dataHst. maxCount = 0; results.Clear(); Parallel.ForEach(dataHst.Values.Cast<int>(), value => { lock (obj) { if (value > maxCount) { maxCount = value; } } }); IDictionaryEnumerator item = dataHst.GetEnumerator(); while (item.MoveNext()) { if (int.Parse(item.Value.ToString()) == maxCount) { results.Add(item.Key.ToString()); } } return results; } private static List<string> FindResultsFromDic() { //Find the results from dataDic. maxCount = 0; results.Clear(); Parallel.ForEach(dataDic.Values.Cast<int>(), value => { lock (obj) { if (value > maxCount) { maxCount = value; } } }); foreach (var data in dataDic) { if (data.Value == maxCount) { results.Add(data.Key); } } return results; } private static void CreateData() { Random ran = new Random(); Parallel.For(0, 5000000, i => { lock (dataSource) { data = "{ id = 'xxx' info = 'xxx' kk = 'xxx' target = '" + ran.Next(1, 200) + "' dd = 'xxx'}"; dataSource.Add(data); } }); Console.WriteLine(dataSource.Count + " data has been created."); } public static void SolveDataNormally() { dataHst.Clear(); int num; foreach (string data in dataSource) { if (!dataHst.Contains(data.Split()[12])) { dataHst.Add(data.Split()[12], 1); } else { num = int.Parse(dataHst[data.Split()[12]].ToString()); num += 1; dataHst[data.Split()[12]] = num; } } } public static void SolveDataParallel() { dataHst.Clear(); int num; Parallel.ForEach(dataSource, data => { lock (dataHst) { if (!dataHst.Contains(data.Split()[12])) { dataHst.Add(data.Split()[12], 1); } else { num = int.Parse(dataHst[data.Split()[12]].ToString()); num += 1; dataHst[data.Split()[12]] = num; } } }); } private static void SolveDataConcurrently() { dataDic.Clear(); Parallel.ForEach(dataSource, data => { lock (obj) { if (!dataDic.ContainsKey(data.Split()[12])) { dataDic.TryAdd(data.Split()[12], 1); } else { dataDic[data.Split()[12]]++; } } }); } } }
運行結果以下:
能夠看出用C#的線程安全集合是最快的,本身手動加lock作併發的時間比普通的foreach還要慢。由於併發的時候會有判斷,判斷集合中是否會有相應的元素,若是沒有則進行初始化,這裏若是不加lock的話將會有併發初始化的事情發生,這樣獲得的結果值就會比預期結果低。下面是初始化部分的代碼:
if (!dataHst.Contains(data.Split()[12])) { dataHst.Add(data.Split()[12], 1); }
目前我沒想到什麼好的辦法能夠摘掉鎖,由於摘到鎖就作不到線程安全了,據我測試線程安全集合類ConcurrentDictionary<TKey, TValue>的TryAdd方法在併發的狀況下是不安全的,若是併發字典中不存在咱們TryAdd的數據,此時若是併發多個TryAdd方法,TryAdd的返回值均爲true,因此仍是會致使併發初始化的發生,是不安全的。若是能夠摘掉這層鎖,時間能夠提高一倍,處理500萬條數據僅需7秒左右。
通過園友@Eric Liao的指點!最後學會了使用AddOrUpdate這一專業處理併發狀況下的Key、Value的線程安全方法!優化後的SolveDataConcurrently方法核心代碼以下:
Parallel.ForEach(dataSource, data => { dataDic.AddOrUpdate(data.Split()[12].ToString(), 1, (a, b) => b + 1); });
這一方法的含義是:對data.Split()[12].ToString()這一Key進行判斷——
一、若是在dataDic中不存在,則執行Add操做,Add操做所添加的Value值爲1;
二、若是在dataDic中存在,則執行Update操做,Update操做所將原有Key的Value b更新爲b+1.
因而優化後的SolveDataConcurrently就異常簡潔明瞭:
private static void SolveDataConcurrently() { dataDic.Clear(); Parallel.ForEach(dataSource, data => { dataDic.AddOrUpdate(data.Split()[12].ToString(), 1, (a, b) => b + 1); }); }
執行速度也提高了好幾倍,運行兩次,測試的結果以下——
第一次:
第二次:
從兩次的運算結果能夠看出,併發處理速度是正常處理速度的五倍!
通過園友的提示,這種數據處理的任務還能夠換用腳本進行,評論中有園友寫出了一段Linux下的shell腳本(詳見評論9樓),我再加一種Windows下的PowerShell腳本,一樣能夠解決這個任務,代碼以下:
$testFile = "C:\Users\Administrator\Desktop\test3.txt" $hashTable = @{} Write-Host "Processing..." -ForegroundColor yellow Get-Content $testFile | %{$_.Split()[12]} | %{ if(!$hashTable.ContainsKey($_)){ $hashTable.Add($_ , 1) } else{ $hashTable[$_]++; #$hashTable } } $count = $hashTable.Values|measure -Maximum|%{$_.Maximum} $hashTable|%{if($_.Value -eq $count){$_}} $hashTable.Keys|%{if($hashTable[$_] -eq $count){$_+" "+$count}}
相比之下,這種腳本的解法是很是簡便的,並且更加靈活。可是也存在必定的問題,好比處理大數據的時候,一個txt文件中包含500萬+數據,文件達到300+mb的時候,經過腳原本處理文本信息是很是緩慢的!這是因爲Get-Content這個cmdlet致使的。因此當文本數據量較大時,想要高效的併發處理文本信息,仍是要用上面的C#併發式解法更好。在文本數據量不大的狀況下,用腳本處理更加靈活,便捷。歡迎你們繼續討論!不斷昇華。