要在 C# 中把一份清單隨機排序,您通常會寫一個使用 System.Random 的 Fisher-Yates 迴圈、呼叫 OrderBy(x => Guid.NewGuid()),或在 .NET 6 以上版本中仰賴 Random.Shared——而 Fisher-Yates 迴圈能以 O(n) 的時間複雜度產生公平、無偏差的洗牌結果,OrderBy 這類 LINQ 做法則是 O(n log n) 的比較排序。每一種做法都假設您正身處一個能存取 System.Collections.Generic、且編譯器隨時待命的 C# 專案中。對許多讀者來說,「如何在 C# 中把清單隨機排序」這個搜尋背後真正的意圖,其實與這門語言本身無關,而是與底層的任務有關:把一份任意的項目清單,變成一個公平、無法預測的順序。老師、抽獎主辦人、作家與研究人員遇到這個問題的頻率,遠比軟體開發者高得多,而他們通常也不想只是為了抽個獲獎者,就特地開啟 Visual Studio。List Randomizer 正是為這種情境而打造的。它在您的瀏覽器中執行同一套 Fisher-Yates 演算法,因此輸出結果與手寫的 C# 實作一樣不偏不倚,而您需要做的,就只是把項目貼進文字框、點擊 Shuffle。不需要專案檔、不需要 NuGet 套件、不需要偵錯階段——只要點一下,清單就會以新的順序回來。

how to randomize list in c#
如何在 c# 中把清單隨機排序

C# 中三種常見的清單洗牌方法

經典的 Fisher-Yates 模式,是多數開發者第一個想到的方法。您宣告一個新的 Random 執行個體,從清單的最後一個索引往索引零的方向走訪,每一步都把目前的項目,與從尚未洗牌的前段中選出的一個項目互換。這個迴圈會執行 n − 1 次,每次互換都使用 Random.Next 從剩餘位置中均勻地挑選一個位置,最終結果是一個每一種可能排列都同樣有可能出現的排列。這正是 Python、JavaScript、Java,以及 .NET 基礎類別庫本身,這些受信任的洗牌函式庫所使用的同一套無偏差演算法,也是 List Randomizer 在底層所使用的方法。

第二種做法,是 LINQ 的單行寫法。您取得原始清單,對它呼叫 OrderBy,並帶入一個對每個元素都回傳 Guid.NewGuid() 的鍵值選取器,再用 ToList() 具體化結果。由於每個 GUID 實際上都是唯一且均勻隨機的,依它們排序就能在一個運算式中,產生一個穩定但無法預測的排列。這麼做的代價在於效能:Guid.NewGuid 會為每個元素配置一個 16 位元組的結構,比 Random.Next 產生的單一整數要重得多。對於少於數千項的清單,這個差異幾乎察覺不到。

第三種做法在 .NET 6 中登場。Random.Shared 是一個執行緒安全的單例執行個體,處理序中任何程式碼都能直接取用,不需要自行建構自己的 Random 物件,因此您不必再擔心舊時代的那個地雷:在同一個系統時脈中緊接著建立的兩個 Random 物件,會產生完全相同的序列。在 OrderBy(_ => Random.Shared.Next()) 中使用它,就能用單獨一行程式碼,得到一次乾淨、現代、幾乎不配置額外記憶體的洗牌。

為什麼多數讀者其實並不需要 C# 程式碼

現實世界中的清單洗牌問題,很少真的屬於某個程式內部。老師想隨機點名學生;抽獎活動主辦人想公平地選出得獎者;一場站立會議需要一個不偏袒最先舉手者的發言順序。這些任務都不涉及部署編譯過的程式碼,但全都是靠 C# 開發者所使用的同一套演算法解決的。List Randomizer 徹底省去了寫程式的步驟。不需要搭建專案架構、不需要記住 using 指示詞,也不需要糾結陣列還是 List<T> 的取捨。您只要貼上項目、點擊 Shuffle,同一套 Fisher-Yates 迴圈,就會在您原本已經開著的瀏覽器分頁中,對您的資料執行一次。

對於偶爾真的需要把結果放進程式碼中的 C# 開發者來說,這款瀏覽器工具同樣有幫助:在網頁上把清單洗牌、複製結果,再把重新排列後的每一行貼回您的原始碼檔案中,當成一個字面初始化陣列。相較於為了一次性的排列,特地撰寫、編譯並偵錯一個自訂輔助方法,這是一種更快的工作流程。

如何在瀏覽器中把清單洗牌

  1. 開啟 List Randomizer,把您的清單貼上或輸入到文字框中,每一行放一個項目。
  2. 如果您的清單中含有不想要的重複項目,勾選「Remove duplicate lines」核取方塊,這樣每個唯一的項目就只會依其第一次出現的位置保留一次。
  3. 點擊 Shuffle 按鈕。即使您的清單有數千行,Fisher-Yates 演算法都會走訪整份清單,產生一個公平、無偏差的新順序。
  4. 在結果框中閱讀洗牌後的清單。任何時候想要不同的順序,都可以再按一次 Shuffle——每次點擊都會產生一個全新的排列。
  5. 點擊 Copy,把隨機排序後的清單傳送到您的剪貼簿,再貼進電子郵件、文件、試算表,或程式碼檔案中。

空白行以及每個項目周圍多餘的空格,都會被自動清理,因此即使是從試算表複製貼上而來、格式凌亂的內容,也能產生一份整齊的洗牌結果,完全不需要手動編輯。

日常工作中隨機順序何時派得上用場

比您想像中更多的場合,人們會用到清單隨機排序工具。老師把學生姓名洗牌,公平地點名,或在不偏袒任何人的情況下分組。主管把站立會議與回顧會議的發言順序隨機打亂,讓同一個人不會每次都排在最前面。抽獎與摸彩主辦人,貼上一整欄參加者名單,透過洗牌以一種站得住腳、公平的方式選出得獎者。作家與設計師把提示詞、色彩,或點子清單隨機排序,藉此打破創意瓶頸。玩家隨機決定出手順序、選秀順序,或挑戰清單。研究人員把問卷題目或測試條件的順序隨機打亂,藉此降低順序效應對資料的影響。任何要抽籤、分派家事、安排賓客座位,或解決一場友好爭論的人,都可以放進一份清單、讓機率來決定。

隱私這一點,讓瀏覽器做法在其中幾種情境下格外具有吸引力。由於整個洗牌過程都在您的瀏覽器中執行,像員工姓名、客戶紀錄、面試候選人,或私人獎品參加者名單這類敏感清單,永遠不會離開您的裝置。不會有任何內容被上傳到伺服器、被記錄,或被儲存,而且一旦您關閉或重新整理分頁,資料就會隨之消失。這項保證,是內建在一款用戶端工具的運作方式之中,而不是寫在一份您必須信任的隱私政策裡。

為什麼 Fisher-Yates 是值得信任的演算法

把清單隨機排序聽起來很簡單,但要做到公平,卻出乎意料地容易出錯。Fisher-Yates 洗牌,是數學家與工程師仰賴、能達成無偏差排序的演算法。它從清單的最後一項走到第一項,每一步都把目前的項目,與尚未固定下來的位置中選出的一個項目互換。結果是,無論清單有多長,每一種可能的排列方式,出現的機率都完全相同。天真的做法,例如用一個隨機的比較函式來排序,會在不知不覺中偏袒某些順序;Fisher-Yates 則不會。如果您在意結果是否真正公平,這個差異就很重要,無論您是在寫 C#,還是在一款瀏覽器工具裡點一下按鈕。若想更深入了解為什麼一款瀏覽器工具能與編譯過的程式碼一樣公平,請參閱這篇關於 如何公平地把清單隨機排序 的指南。

有一項重要的但書,同樣適用於 C# 程式碼與 List Randomizer:兩者都仰賴同一類通用亂數產生器類別。瀏覽器版本使用內建的 Math.random 產生器,而典型的 C# 實作則使用 System.Random。這兩者在挑選姓名、決定出手順序,或舉辦抽獎活動時,統計上都是公平的,但兩者都不具備密碼學等級的安全性,因此都不應該用於高風險賭博、安全金鑰,或具法律約束力的樂透抽獎。

C# 與瀏覽器工具一覽表

維度 手寫的 C# 洗牌 List Randomizer(瀏覽器)
設置時間 依專案設置狀況,從數分鐘到數小時不等 不需要——開啟頁面就能開始
演算法 看您自己實作了什麼,理想上是 Fisher-Yates 預設就是 Fisher-Yates
隱私 取決於程式碼在哪裡執行 完全在本機處理,不會上傳任何內容
重複項目處理 手動使用 Distinct() 或 HashSet 一鍵選項,保留第一次出現的項目
輸出目的地 記憶體中的變數,或您自行寫出的檔案 畫面上的結果與剪貼簿
最適合的情境 會出貨、內建洗牌功能的軟體 日常任務中的一次性洗牌

如果您的任務是位在一個較大的 C# 應用程式內部,就寫 Fisher-Yates 迴圈,或使用 LINQ 做法,繼續往下做即可。如果您的任務,是更常見的「我現在就是需要一個公平的隨機順序」,List Randomizer 能讓您一鍵得到同樣的演算法公平性,不需要編譯器,也完全不用擔心您的私人資料離開這台機器。

如果您還在權衡選項,Turn a Column Into a Semicolon Separated List Safely 一文對此有詳細說明。