3 回答

TA貢獻1877條經驗 獲得超6個贊
可重復使用的模塊
這是了解可重用模塊的絕佳機會。您的GroupMessages函數幾乎超過 100 行,并且與您的數據結構緊密耦合。此答案中的解決方案解決了您的特定問題,無需對之前編寫的模塊進行任何修改。
我將在這個答案的末尾提供一些代碼審查,但現在我們重命名,因為schools數組grades中的每個項目代表特定學校單個學生的單個成績 -
const grades =
[ {id: 1, school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false}
, {id: 2, school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false}
, {id: 3, school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false}
, {id: 4, school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}
, {id: 5, school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}
, {id: 6, school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}
, {id: 7, school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}
, {id: 8, school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}
, {id: 9, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}
, {id: 10, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}
, {id: 11, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}
, {id: 12, school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false}
, {id: 13, school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false}
, {id: 14, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}
, {id: 15, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}
, {id: 16, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}
, {id: 17, school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true}
]
正如您所知,JavaScript 沒有關聯數組。它也沒有任何支持使用復合鍵查找(選擇具有多個鍵的值)的本機數據結構。我們將從二叉樹模塊導入一些函數,btree為您的記錄創建一個標識符,myIdentifier并使用它來初始化您的樹,myTree-
import { nil, fromArray, inorder } from "./btree.js"
const myIdentifier = record =>
[ record?.school ?? "noschool" // if school property is blank, group by "noschool"
, record?.grade ?? "NA" // if grade property is blank, group by "NA"
, record?.isMan ?? false // if isMan property is blank, group by false
]
const myTree =
nil(myIdentifier)
二叉樹根據可定制的標識符自動處理分組,并且可以使用任意數量的分組鍵。我們將使用 basicfilter來選擇與查詢匹配的所有成績gender。fromArray選定的成績數組與處理樹更新的合并函數一起傳遞。inorder用于從樹中提取分組值 -
function groupMessages (grades, gender)
{ const t =
fromArray
( myTree
, grades.filter(x => !x.isMan || gender === "man")
, ({ messages = [] } = {}, { message = "", ...r }) =>
({ ...r, messages: [ ...messages, message ]})
)
return Array.from(inorder(t))
}
現在讓我們看看輸出 -
console.log(groupMessages(grades, 'woman'))
[
{
"id": "3",
"school": "SCHOOL_1",
"grade": "A",
"isMan": false,
"messages": [
"Congratulations!",
"Good work!",
"Ok"
]
},
{
"id": "11",
"school": "SCHOOL_2",
"grade": "A",
"isMan": false,
"messages": [
"Congratulations!",
"Congratulations!",
"Congratulations!"
]
},
{
"id": "13",
"school": "SCHOOL_2",
"grade": "B",
"isMan": false,
"messages": [
"Good work!",
"Nice!"
]
}
]
為了完成這篇文章,我們將展示以下的實現btree,
// btree.js
import { memo } from "./func.js"
import * as ordered from "./ordered.js"
const nil =
memo
( compare =>
({ nil, compare, cons:btree(compare) })
)
const btree =
memo
( compare =>
(value, left = nil(compare), right = nil(compare)) =>
({ btree, compare, cons:btree(compare), value, left, right })
)
const isNil = t =>
t === nil(t.compare)
const compare = (t, q) =>
ordered.all
( Array.from(t.compare(q))
, Array.from(t.compare(t.value))
)
function get (t, q)
{ if (isNil(t))
return undefined
else switch (compare(t, q))
{ case ordered.lt:
return get(t.left, q)
case ordered.gt:
return get(t.right, q)
case ordered.eq:
return t.value
}
}
function update (t, q, f)
{ if (isNil(t))
return t.cons(f(undefined))
else switch (compare(t, q))
{ case ordered.lt:
return t.cons(t.value, update(t.left, q, f), t.right)
case ordered.gt:
return t.cons(t.value, t.left, update(t.right, q, f))
case ordered.eq:
return t.cons(f(t.value), t.left, t.right)
}
}
const insert = (t, q) =>
update(t, q, _ => q)
const fromArray = (t, a, merge) =>
a.reduce
( (r, v) =>
update
( r
, v
, _ => merge ? merge(_, v) : v
)
, t
)
function* inorder (t)
{ if (isNil(t)) return
yield* inorder(t.left)
yield t.value
yield* inorder(t.right)
}
export { btree, fromArray, get, inorder, insert, isNil, nil, update }
可重用性至關重要。模塊可以導入其他模塊!上面,從和btree導入- 下面包含部分模塊 -funcordered
// func.js
function memo (f)
{ const r = new Map
return x =>
r.has(x)
? r.get(x)
: (r.set(x, f(x)), r.get(x))
}
export { memo }
// ordered.js
const lt =
-1
const gt =
1
const eq =
0
const empty =
eq
const compare = (a, b) =>
a < b
? lt
: a > b
? gt
: eq
const all = (a = [], b = []) =>
a.reduce
( (r, e, i) =>
concat(r, compare(e, b[i]))
, eq
)
const concat = (a, b) =>
a === eq ? b : a
export { all, compare, concat, empty, eq, gt, lt }

TA貢獻1806條經驗 獲得超8個贊
我想提供一種稍微不同的方法。它同樣構建在現有功能之上,但不像該方法那樣低級別。
首先,這是我對問題的初步解決方案:
const call = (fn, ...args) => fn (...args)
const groupBy = (fn) => (xs) =>
? xs .reduce ((a, x) => call (key => ((a [key] = [... (a [key] || []), x]), a), fn (x)), {})
const groupMessages = (students, gender) =>?
? Object .values (groupBy (x => `${x.school}|${x.grade}`) (Object .values (students)?
? ? .flat ()
? ? .filter (({isMan}) => isMan == (gender == 'man'))))
? ? .map ((students) => ({
? ? ? ... students [0],
? ? ? message: students .map (s => s.message) .join ('|')
? ? }))
const students = {SCHOOL_1: [/* girls */ {id: '1', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '2', school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false}, {id: '3', school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false}, /* boys */ {id: '4', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '5', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '6', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '7', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '8', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}], SCHOOL_2: [/* girls */ {id: '9', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '10', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '11', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '12', school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false}, {id: '13', school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false}, /* boys */ {id: '14', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '15', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '16', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '17', school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true}]}
? ? ? ? ?
console .log (groupMessages (students, 'woman'))
.as-console-wrapper {max-height: 100% !important; top: 0}
它使用了groupBy我經常使用的一個功能。這又取決于call,它接受一個函數和參數列表,并使用這些參數調用該函數。(我在這里使用它只是為了將局部變量保持在最低限度。)
這是有效的,并且顯然比原始代碼短得多。但它有一個真正的丑陋之處,這是許多此類 JS 代碼所共有的。它很緊湊,但操作順序很難看出。需要深入理解代碼才能看到這一點:
? ? const groupMessages = (students, gender) =>?
? ? ? Object .values (groupBy (x => `${x.school}|${x.grade}`) (Object .values (students)
? ? ? ? /* ^-- 5 */? ?/* ^-- 4 */? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/* ^-- 1 */?
? ? ? ? .flat ()? /* <-- 2 */
? ? ? ? .filter (({isMan}) => isMan == (gender == 'man'))))? /* <-- 3 */
? ? ? ? .map ((students) => ({? /* <-- 6 */
? ? ? ? ? ... students [0],
? ? ? ? ? message: students .map (s => s.message) .join ('|')
? ? ? ? }))
我傾向于使用一種pipe函數,它將一個函數的結果傳遞給下一個函數,并將該函數的結果傳遞給下一個函數,依此類推。這可以清理很多東西。但它確實需要具有具體化數組方法等內容的柯里化函數。
所以,我發現這個細分更清晰:
// reusable utility functions
const pipe = (...fns) => (arg) => fns .reduce ((a, f) => f (a), arg)
const map = (fn) => (xs) => xs .map (x => fn (x))
const filter = (fn) => (xs) => xs .filter (x => fn (x))
const call = (fn, ...args) => fn (...args)
const groupBy = (fn) => (xs) =>
? xs .reduce ((a, x) => call (key => ((a [key] = [... (a [key] || []), x]), a), fn (x)), {})
const flat = (xs) => xs .flat ()
// helper function
const groupStudentMessages = (students) => ({
? ... students [0],
? message: students .map (s => s.message) .join ('|')
})
// main function, as a pipeline
const groupMessages = (students, gender) => pipe (
? Object .values,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/* <-- 1 */
? flat,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/* <-- 2 */?
? filter (({isMan}) => isMan == (gender == 'man')),? ?/* <-- 3 */
? groupBy (x => `${x.school}|${x.grade}`),? ? ? ? ? ? /* <-- 4 */
? Object .values,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/* <-- 5 */
? map (groupStudentMessages)? ? ? ? ? ? ? ? ? ? ? ? ? /* <-- 6 */
) (students)
const students = {SCHOOL_1: [/* girls */ {id: '1', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '2', school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false}, {id: '3', school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false}, /* boys */ {id: '4', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '5', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '6', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '7', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '8', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}], SCHOOL_2: [/* girls */ {id: '9', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '10', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '11', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '12', school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false}, {id: '13', school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false}, /* boys */ {id: '14', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '15', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '16', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '17', school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true}]}
console .log (groupMessages (students, 'woman'))
.as-console-wrapper {max-height: 100% !important; top: 0}
我希望即使沒有注釋,主函數中的操作順序也足夠清晰。為了使這一點更加清晰,我們提取了groupStudentMessages
輔助函數,并使每個管道步驟成為單行代碼。
請注意,前六個函數是非常有用的、可重用的函數。
添加回答
舉報