分享最近學到的LINQ小技巧一則。有時我們會需求將資料物件分組擺放,方便後續查詢處理,例如:將散亂的銷售資料依客戶分群,同一客戶的所有資料變成一個List<T>。
過去面對這種問題,我慣用的做法先定義一個Dictionary<string, List<T>>,使用 foreach 逐筆抓取來源資料,從中取出鍵值(例如:客戶編號),先檢查鍵值是否已存在於Dictionary,若無則新増一筆並建立空的List<T>,確保Dictionary有該鍵值專屬List<T>,將資料放入List<T>。執行完畢得到以鍵值分類的List<T>,再進行後續處理。
foreach + Dictionary寫法用了好幾年,前幾天才忽然想到,這不就是SQL語法中的GROUP BY嗎?加上LINQ有ToDictionary, GroupBy(o => o.客戶編號).ToDictionary(o => o.Key, o => o.ToList()) 一行就搞定了呀!阿呆。
來個應景的程式範例吧!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LinqTip
{
class Program
{
publicenum Teams
{
Valor, Mystic, Instinct, Dark
}
publicclass Trainer
{
public Teams Team;
publicstring Name;
public Trainer(Teams team, string name)
{
Team = team; Name = name;
}
}
staticvoid Main(string[] args)
{
//來源資料如下
List<Trainer> trainers = new List<Trainer>()
{
new Trainer(Teams.Valor, "Candela"),
new Trainer(Teams.Valor, "Bob"),
new Trainer(Teams.Mystic, "Blanche"),
new Trainer(Teams.Valor, "Alice"),
new Trainer(Teams.Instinct, "Spark"),
new Trainer(Teams.Mystic, "Tom"),
new Trainer(Teams.Dark, "Jeffrey")
};
//目標:以Team分類,將同隊的訓練師集合成List<Trainer>,
//最終產出Dictionary<Teams, List<Trainer>>
//以前的寫法,跑迴圈加邏輯比對
var res1 = new Dictionary<Teams, List<Trainer>>();
foreach (var t in trainers)
{
if (!res1.ContainsKey(t.Team))
res1.Add(t.Team, new List<Trainer>());
res1[t.Team].Add(t);
}
//新寫法,使用LINQ GroupBy
var res2 =
trainers.GroupBy(o => o.Team)
.ToDictionary(o => o.Key, o => o.ToList());
}
}
}
就醬,又學會一招~
不過,GroupBy().ToDictionary() 做法適用分類現有資料,若之後要陸續接收新增資料,仍可回歸 foreach + Dictionary<string, List<T>> 寫法。
[2016-08-24補充] 感謝Phoenix補充,LINQ還有更簡潔的做法:ToLookup(o > o.Teams, o => o),其產出的型別為ILookup,以Key分組的Value集合,與Dictionary最大的差異是ILookup屬唯讀性質,事後不能變更或修改集合項目。