面試題:控制依賴任務(wù)執(zhí)行

遇到一個有意思的面試題,在這里留下思路。

題目

給定一個任務(wù)隊列,然后執(zhí)行,如果有依賴,就將依賴的結(jié)果執(zhí)行返回,最終得到隊列中每個任務(wù)的結(jié)果并返回。

const input = [
    {
        id: 'task1',
        deps: [],
        runTask: () => 3,
    },
    {
        id: 'task2',
        deps: ['task1','task3'],
        runTask: (res1, res3) => 1 + res1 + res3,
    },
    {
        id: 'task3',
        deps: ['task1'],
        runTask: (res1) => 5 + res1,
    },
    {
        id: 'task4',
        deps: ['task1', 'task2'],
        runTask: (res1, res2) => 3 + res1 + res2,
    },
];

function runAllTask(list, cb) {
    // TODO
}

runAllTask(input, (err, res) => {
    console.log(err);
    console.log(res);
    /** 
        res應(yīng)該為:
        { task1: 3, task2: 12, task3: 8, task4: 18 }
    */
});

解法一:遇到?jīng)]有值的依賴,放入隊尾等待執(zhí)行

思考:

1. 循環(huán)隊列,執(zhí)行任務(wù)(每次執(zhí)行任務(wù),先將隊頭任務(wù)彈出執(zhí)行)
2. 依賴檢查
    - 待執(zhí)行的任務(wù)檢查依賴如果遇到?jīng)]有依賴的直接返回結(jié)果,并將結(jié)果保存
    - 遇到有依賴的,遍歷依賴先從結(jié)果中匹配看是否有已經(jīng)算出的結(jié)果,如果沒有匹配到執(zhí)行優(yōu)先級繞后
    - 遍歷依賴完成判斷結(jié)果數(shù)組是否和依賴數(shù)組長度一樣(每一個依賴都拿到的結(jié)果),執(zhí)行結(jié)果保存
3. 循環(huán)上面的情況,直到隊列中沒有任務(wù)即可
4. 錯誤處理,如果上面執(zhí)行代碼出問題,返回err

代碼

function runAllTask(list, cb) {
    // 創(chuàng)建結(jié)果數(shù)組
    let obj = {}
    // 錯誤處理
    try {
        // 循環(huán)執(zhí)行任務(wù)
        while(list.length > 0) {
            // 彈出要執(zhí)行的任務(wù)
            let task = list.shift();
            // 依賴判斷
            if (!task.deps.length) {
                obj[task.id] = task.runTask()
            } else {
                // 有依賴,創(chuàng)建依賴結(jié)果數(shù)組
                let arr = []
                task.deps.every(val => {
                    // 有結(jié)果直接放入,繼續(xù)循環(huán)
                    if (obj[val] !== undefined) {
                        arr.push(obj[val])
                        return true
                    // 無結(jié)果說明依賴內(nèi)容沒執(zhí)行,放入隊尾等待執(zhí)行,循環(huán)結(jié)束
                    } else {
                        list.push(task);
                        return false
                    }
                })
                // 判斷依賴是否全部執(zhí)行完畢
                if (arr.length === task.deps.length) {
                    obj[task.id] = task.runTask(...arr)
                }
            }
        }
        // 返回結(jié)果
        cb(null, obj);
    } catch(e) {
        cb(e, null);
    }
}

// 執(zhí)行結(jié)果
// { task1: 3, task3: 8, task2: 12, task4: 18 }

解到到這里基本問題是解決了,但是目前的這個方案,存在一些漏洞:

  1. 如果遇到依賴不存在的情況循環(huán)無法終止
  2. 如果遇到依賴循環(huán)的情況,隊列循環(huán)也無法終止

優(yōu)化解法一:加入依賴錯誤檢測

思路是如果當前任務(wù)在循環(huán)中執(zhí)行了第二次仍失敗,那么就可以命定為該任務(wù)隊列中有錯誤,需要終止執(zhí)行,拋出錯誤。

第一種:假設(shè)最后一個任務(wù)才是沒有依賴的任務(wù),那么正常情況下,最大需要執(zhí)行l(wèi)ength的階乘,如果進入循環(huán)都判斷不能超過這個次數(shù)也是可以解決,但是這樣性能消耗太大,有很多不必要的循環(huán)次數(shù)。

第二種:在當前執(zhí)行的任務(wù)中,要記錄一下上一個成功執(zhí)行的任務(wù)名稱是什么,如果上一次成功執(zhí)行的名稱一樣,那么就判定進入到了死循環(huán)。

function runAllTask(list, cb) {
    let obj = {}
    // 記錄一下上一個運行的id,避免沒有匹配的task出現(xiàn)
    let last_task = null;
    try {
        // 如果都運行完,就有了結(jié)果
        while(list.length > 0) {
            let task = list.shift();
            if (!task.deps.length) {
                obj[task.id] = task.runTask()
                // 記錄一下當前的task名稱
                last_task = task.id
            } else {
                let arr = []
                // 遍歷依賴,如果obj中有結(jié)果直接獲取,沒有結(jié)果返回到隊尾
                task.deps.every(val => {
                    // 如果運行成功的上一個和當前一樣,表示一個任務(wù)執(zhí)行了兩次,避免陷入死循環(huán)
                    if (task.last_task === last_task) throw new Error('no match')
                    if (obj[val] !== undefined) {
                        arr.push(obj[val])
                        return true
                    } else {
                        //將上一次成功的任務(wù)id記錄到當前任務(wù)中
                        task.last_task = last_task
                        list.push(task);
                        return false
                    }
                })
                // 如果當前長度和任務(wù)依賴長度一樣,說明執(zhí)行成功
                if (arr.length === task.deps.length) {
                    // 將參數(shù)打散
                    obj[task.id] = task.runTask(...arr)
                    // 記錄成功的任務(wù)id
                    last_task = task.id
                }
            }
        }
        // 返回成功的結(jié)果
        cb(null, obj);
    } catch(e) {
        // 返回錯誤的結(jié)果
        cb(e, null);
    }
}

這個方案解決了一些依賴錯誤導(dǎo)致的無法終止的問題,不過存在一些漏洞:
有問題的任務(wù),還是循環(huán)了兩三次以上,不必要的性能浪費

解法二:遞歸調(diào)用

如果不移動到隊尾,直接遞歸調(diào)用依賴返回結(jié)果,這樣就完美解決了上面的問題。

function runAllTask(list, cb) {
    // 數(shù)組改鍵值對(為了定義遞歸函數(shù)的時候,不用每次遍歷數(shù)組)
    let listObj = {}
    // 結(jié)果數(shù)組
    let obj = {}
    try {
        // 數(shù)組改鍵值對
        list.forEach(val => {
            listObj[val.id] = val
        })
        // 遍歷執(zhí)行任務(wù)
        list.forEach(val => {
            runOneTask(val.id)
        })
        // 成功返回obj
        cb(null, obj)
    } catch(e) {
        cb(e, null)
    }

    // 執(zhí)行單個任務(wù)
    function runOneTask(name) {
        // 如果結(jié)果里面有,直接讀取結(jié)果,使用緩存
        if(obj[name]) return obj[name]
        // 當前依賴數(shù)組結(jié)果
        let depResult = [];
        // 遍歷依賴
        listObj[name].deps.forEach(dep => {
            // 每個依賴遞歸執(zhí)行結(jié)果
            depResult.push(runOneTask(dep))
        })
        // 將結(jié)果執(zhí)行加到結(jié)果對象中
        obj[name] = listObj[name].runTask(...depResult)
        // 返回結(jié)果
        return obj[name]
    }
}

總結(jié)

考點掌握:

  1. 隊列
  2. 遞歸
  3. 數(shù)組改鍵值對
  4. 值存儲(類似緩存)
  5. 錯誤處理 try-catch
  6. 擴展運算符
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. iOS開發(fā)中的加密方式 iOS加密相關(guān)算法框架:CommonCrypto。 對稱加密: DES、3DES、A...
    Dezi閱讀 1,391評論 0 7
  • 最近準備復(fù)習一下面試題,看到了J_Knight_在18年的出一套 iOS 高級面試題嘗試著回答一下題目,由于水平有...
    lkkwxy閱讀 5,924評論 0 24
  • 怎么去設(shè)計一個組件封裝 組件封裝的目的是為了重用,提高開發(fā)效率和代碼質(zhì)量 低耦合,單一職責,可復(fù)用性,可維護性 前...
    凡凡_4e04閱讀 436評論 0 0
  • 目錄 ES5 和 ES6 分別幾種方式聲明變量DOM 事件有哪些階段?談?wù)剬κ录淼睦斫釫S6 的 class ...
    WEB前端含光閱讀 598評論 0 4
  • (掌握)Http和Https的區(qū)別? http協(xié)議和https協(xié)議的區(qū)別:傳輸信息安全性不同、連接方式不同、端口不...
    Grit_1024閱讀 3,457評論 0 2

友情鏈接更多精彩內(nèi)容