以 generator 遍歷大型陣列
以前一直覺得 js 的 generator 很雞肋, 看起很炫,但其實沒什麼派得上用場的地方。 之前想窮舉所有排列組合,把組合當一個陣列回傳, 結果因為太大 stack 不夠裝。 改成 generator 或 callback 的形式就可以了。
generator
function *randomSortGenerator(list) {
if (list.length == 1) yield list
else for (const item of list) {
const elseList = list.filter(s => s != item)
for (const subList of randomSortGenerator(elseList)) {
yield [item, ...subList]
}
}
}
generator 的意義有二個,一是狀態機, 像 async await 就能用 generator 實現; 二就是用來模擬鏈表。 在鏈表很長或無窮的時候,例如模擬無窮數列。
再加上新的 of 關鍵字, 讓任何提供 Symbol.iterator 方法的物件, 都能被當成鏈表遍歷。
雖然 js 好像比較傾向 functional programing,
比起 for (... of ...)
更偏好 ...forEach(...)
。
但因為用 .next()
.done
遍歷列表太麻煩了,
我就用了比較簡單的 of 關鍵字。
callback
另一種是用 callback, 可以說是把原本 generator 的 yield 換成要接該項回呼函數。
function randomSortCallback(list, sendList) {
if (list.length == 1) sendList(list)
else for (const item of list) {
randomSortCallback(
list.filter(s => s != item),
(subList) => sendList([item, ...subList])
)
}
}
發現這樣跑起來, 在 node v6.12.3 竟然比 generator 快, js 果然是函數式語言,generator 你自盡吧。
測試
function show1of(count) {
const gate = 1 / count
return (item) => {
if (Math.random() < gate) console.log(item)
}
}
function time(todo) {
console.time('todo')
todo()
console.timeEnd('todo')
}
time(() => {
randomSortCallback([1,2,3,4,5,6,7,8,9,10,11], show1of(100000))
})
time(() => {
const show1of100000 = show1of(100000)
for (const set of randomSortGenerator([1,2,3,4,5,6,7,8,9,10,11])) {
show1of100000(set)
}
})
函數呼叫層數
之前好像不是因為回呼疊太多層爆 stack, 而是因為 list 太大,內存存不下。 因為排序多是要對列表中每個項目都呼叫, 也就列表有幾項就疊幾層而已, 而不是重覆呼叫一直往上疊。 所以改成這種方式就不會爆了。