給定以下資料結構:
const data = [
{ A: 1, B: 12, C: 123 },
{ A: 1, B: 122, C: 1233 },
{ A: 2, B: 22, C: 223 }
];
我想實作一個groupBy使用以下簽名呼叫的函式:
function groupBy<T, By extends keyof T>(object: T[], by: ...By[]): Map<By[0], Map<By[1], ..., Map<number, T[]>...>
我可以這樣稱呼:
const A = groupBy(data, "A"); // Map<number, { A: number, B: number, C: number }[]>;
const AB = groupBy(data, "A", "B"); // Map<number, Map<number, { A: number, B: number, C: number }[]>>;
const ABC = groupBy(data, "A", "B", "C"); // Map<number, Map<number, Map<number, ...>>;
不幸的是,我只能用一個級別來實作groupBy:
function groupBy<T extends object, K extends keyof T>(collection: T[], iteratee: K): Map<T[K], T[]> {
const map: Map<T[K], T[]> = new Map();
for (const item of collection) {
const accumalated = map.get(item[iteratee]);
if (accumalated === undefined) {
map.set(item[iteratee], [item]);
} else {
map.set(item[iteratee], [...accumalated, item]);
}
}
return map;
}
uj5u.com熱心網友回復:
首先讓我們groupBy()在型別級別進行描述,通過給它一個強型別呼叫簽名:
type GroupedBy<T, K> = K extends [infer K0, ...infer KR] ?
Map<T[Extract<K0, keyof T>], GroupedBy<T, KR>> : T[];
// call signature
function groupBy<T, K extends Array<keyof T>>(
objects: readonly T[], ...by: [...K]
): GroupedBy<T, K>;
所以groupBy()是通用的T,所述的元件的型別objects陣列,以及K,一個元組的鍵的T對應于...by 其余引數。函式回傳GroupedBy<T, K>。
那是什么GroupedBy<T, K>?這是一個遞回條件型別。如果元組K為空,那么它就是空的T[](因為將陣列按任何內容分組應該產生相同的陣列)。否則,我們使用可變引數元組型別將K元組拆分為第一個元素K0和其余元素KR。然后,GroupedBy<T, K>將是Map其主要型別是屬性的型別T,在鍵K0(在概念上這只是一個索引訪問型別 T[K0],但是編譯器不知道K0將是一個關鍵T,所以我們使用的Extract<T, U>實用型,以說服它.. . so T[Extract<K0, keyof T>]) 并且其值型別是, 遞回, GroupedBy<T, KR>.
讓我們確保編譯器做正確的事情:
const A = groupBy(data, "A");
// Map<number, { A: number, B: number, C: number }[]>;
const AB = groupBy(data, "A", "B");
// Map<number, Map<number, { A: number, B: number, C: number }[]>>;
const ABC = groupBy(data, "A", "B", "C"); /* Map<number, Map<number, Map<number, {
A: number;
B: number;
C: number;
}[]>>> */
看起來挺好的。為確定起見,讓我們換個number東西:
const otherData = [
{ str: "a", num: 1, bool: true },
{ str: "a", num: 1, bool: false },
{ str: "a", num: 2, bool: true }
];
const grouped = groupBy(otherData, "str", "num", "bool")
/* const grouped: Map<string, Map<number, Map<boolean, {
str: string;
num: number;
bool: boolean;
}[]>>> */
看起來也不錯。
現在讓我們實作groupBy(). 編譯器不可能遵循實作內部的遞回條件型別(請參閱microsoft/TypeScript#33912),因此讓我們通過使用單個強型別呼叫簽名和松散實作簽名使其成為多載函式來放松一下該any型別。我們必須小心做對,因為編譯器不會捕獲型別錯誤。
無論如何,這是一個可能的實作:
// implementation
function groupBy(objects: readonly any[], ...by: Array<PropertyKey>) {
if (!by.length) return objects;
const [k0, ...kr] = by;
const topLevelGroups = new Map<any, any[]>();
for (const obj of objects) {
let k = obj[k0];
let arr = topLevelGroups.get(k);
if (!arr) {
arr = [];
topLevelGroups.set(k, arr);
}
arr.push(obj);
}
return new Map(Array.from(topLevelGroups, ([k, v]) => ([k, groupBy(v, ...kr)])));
}
如果by陣列為空,則回傳objects陣列不變;這對應于GroupedBy<T, K>where Kis的基本情況[]。否則,我們分裂by成第一個元素k0和它的其余部分kr。然后我們通過鍵中objects的值對其進行頂級分組k0。這涉及正確確保我們在將事物推入陣列之前初始化陣列。最后,最后,我們轉換頂級分組(使用此技術),遞回,應用于groupBy()每個物件陣列。
讓我們看看它是否有效:
console.log(A);
/* Map (2) {1 => [{
"A": 1,
"B": 12,
"C": 123
}, {
"A": 1,
"B": 122,
"C": 1233
}], 2 => [{
"A": 2,
"B": 22,
"C": 223
}]} */
console.log(AB);
/* Map (2) {1 => Map (2) {12 => [{
"A": 1,
"B": 12,
"C": 123
}], 122 => [{
"A": 1,
"B": 122,
"C": 1233
}]}, 2 => Map (1) {22 => [{
"A": 2,
"B": 22,
"C": 223
}]}} */
看起來也不錯。
Playground 鏈接到代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/392709.html
上一篇:無法在打字稿中使用來自js的函式
