05.28 使用 JavaScript 遞歸算法,彙總累加題目的分數值

所謂遞歸算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的算法去解決。一般來說,一個遞歸算法就是函數調用自身去解決它的子問題。

使用 JavaScript 遞歸算法,彙總累加題目的分數值

使用遞歸算法實現:大題的分數值等於其下各小題的分數值之和。

使用 JavaScript 遞歸算法,彙總累加題目的分數值

本文講解所用到的數據集合(已是樹形結構)。

一、節點數據集合

  1. var nodes = [{
  2. "id": "EQ06-9006C8BBE68A475BAC04858F60E0249D",
  3. "name": "題目1",
  4. "type": "解答題",
  5. "children": [{
  6. "id": "EQ06-CC80CFD8702F47E4A42AF63B0BA10569",
  7. "name": "題目1.1",
  8. "type": "解答題",
  9. "score": 5
  10. }, {
  11. "id": "EQ06-06F7EBA807D445EB937B920ED2250016",
  12. "name": "題目1.2",
  13. "type": "解答題",
  14. "score": 6
  15. }],
  16. "score": 10
  17. }, {
  18. "id": "EQ06-0F0A8DA4B5834D1BB96504546EA47DA9",
  19. "name": "題目2",
  20. "type": "解答題",
  21. "children": [{
  22. "id": "EQ06-660D9E79B5054EA296D15EFA6D1F5462",
  23. "name": "題目2.1",
  24. "type": "解答題",
  25. "score": 10
  26. }, {
  27. "id": "EQ06-C5D5191018B94E8C8D1E9AB3AD5E4C61",
  28. "name": "題目2.2",
  29. "type": "解答題",
  30. "score": 20,
  31. "children": [{
  32. "id": "EQ06-037CBCDFD897434A839619D4AF07E030",
  33. "name": "題目2.2.1",
  34. "type": "解答題",
  35. "score": 5
  36. }, {
  37. "id": "EQ06-FC7CAE767F4E48D6B3E0B78D5E4BC473",
  38. "name": "題目2.2.2",
  39. "type": "解答題",
  40. "score": 6
  41. }, {
  42. "id": "EQ06-FC7CAE767F4E48D6B3E0B78D5E4BC474",
  43. "name": "題目2.2.3",
  44. "type": "解答題",
  45. "score": 7
  46. }]
  47. }],
  48. "score": 40
  49. }];

起初朋友給我發了一段數據,輸出的是題目節點信息,其中包含題目的分數值:score,原始數據已經過樹形結構處理了。

他的需求是:題目的分數值 score 是創建題目時預估定義的一個分數值,如20分;實際情況是在某個大題的下面還可以創建小題,小題也會定義一個分數值 score,這樣一來,某個大題原先預估定義的分數值 score 可能與它下面所有小題的分數值 score 加起來的和並不相等,這樣大題的分數值 score 就顯得不準確,希望我在前端能幫忙處理一下,使得大題的分數值等於彙總了其下所有小題的分數值之和,以符合題目分數在實際展現時的準確無誤。

接到朋友的這個 issue,我想了一下,大致要求就是針對於某個節點的屬性(這裡是 score),彙總累加其下所有子節點的這個屬性(score)的數值,然後將這一過程中累加得到的數值賦到父節點的這個屬性(score)上。

為了實現樹型結構數據的遍歷與處理,免不了要想到使用遞歸函數來實現。我們先來看一下定義,所謂遞歸算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的算法去解決。一般來說,一個遞歸算法就是函數調用自身去解決它的子問題。

遞歸算法的特點:

在函數過程中調用自身。

在遞歸過程中,必須有一個明確的條件判斷遞歸的結束,既遞歸出口。

遞歸算法簡潔但效率低,通常不作為推薦算法。

上面這是原理性的解釋,講的也是十分明確,下面我們結合實例來細細琢磨。

二、編寫一個遞歸算法,實現彙總累加節點的分數值

  1. var getScoreSum = function (data, options){
  2. //默認參數配置
  3. var options = {
  4. scoreField: options.scoreField || "score", //分數屬性名
  5. childrenField: options.childrenField || "children", //子節點集合屬性名
  6. spread: options.spread || false
  7. };
  8. var scoreSum = 0; //定義父級節點彙總分數的局部變量
  9. if(data && data.length > 0){
  10. for(var i = 0, item; i < data.length; i++){
  11. item = data[i];
  12. var itemScore = item[options.scoreField] || 0, itemChildren = item[options.childrenField] || []; //定義本次循環中的局部變量
  13. var tempSum = 0; //定義當前條目彙總分數的臨時變量
  14. //是否有子節點
  15. if(itemChildren && itemChildren.length > 0){
  16. var tempCldSum = getScoreSum(itemChildren, options) || 0; //遞歸調用 getScoreSum() 方法,獲取當前節點下的子節點集合的彙總分數 tempCldSum
  17. tempSum += tempCldSum; //子節點集合的彙總分數 tempCldSum,追加到當前節點彙總分數 tempSum 上
  18. }else{
  19. tempSum += itemScore; //沒有子節點,以當前節點的分數值追加到當前節點彙總分數 tempSum 上
  20. delete item[options.childrenField]; //刪除屬性
  21. }
  22. item["scoreSum"] = tempSum; //已有或一個新的節點屬性:scoreSum,並使用當前節點彙總分數 tempSum 賦值
  23. item["name"] = item.name + '(' + (item.scoreSum || item.score) + ' 分)' //其他屬性設置
  24. item["spread"] = options.spread;
  25. scoreSum += tempSum; //當前節點彙總分數追加到父級節點彙總分數上
  26. }
  27. }
  28. return scoreSum;
  29. }

在方法中用到了調用方法自身的執行語句,目的是遍歷所有子節點,做相同的累加處理。

三、調用遞歸函數,並測試題目的分數值在實際數據展現時是否準確無誤

  1. //layui 模塊化組件引用
  2. layui.use(['jquery', 'layer', 'tree'], function () {
  3. var $ = layui.$, layer = layui.layer, tree = layui.tree;
  4. //分數彙總處理
  5. getScoreSum(nodes, {scoreField: "score", childrenField: "children", spread: true});
  6. //console.log(nodes);
  7. //構建樹節點
  8. var buildTreeNodes = function (options) {
  9. options = options || {};
  10. var treeNodes = [{ name: "全部分組", spread: true, children: nodes }];
  11. $("#treeGroup").empty(); //先清空DOM
  12. layui.tree({
  13. elem: '#treeGroup',
  14. nodes: treeNodes,
  15. click: function (node) {
  16. var self = $(this.elem);
  17. var nodeLinks = self.find("li>a");
  18. nodeLinks.removeClass("active");
  19. nodeLinks.filter(function (index) {
  20. return $('cite', this).text() == node.name;
  21. }).addClass('active');
  22. nodeLinks.splice(0, 1); //刪除數組第一項:全部分組
  23. console.log(node); //node即為當前點擊的節點數據
  24. document.getElementById("nodeContent").innerHTML = JSON.stringify(node, null, 2); //使用JSON.stringify() 格式化輸出JSON字符串
  25. }
  26. });
  27. if (options.selectedNodeName) {
  28. var elem = $("#treeGroup");
  29. var nodeLinks = elem.find("ul>li>a");
  30. nodeLinks.removeClass("active");
  31. nodeLinks.filter(function (index) {
  32. return $('cite', this).text() == options.selectedNodeName;
  33. }).trigger('click');
  34. }
  35. }
  36. buildTreeNodes();
  37. });

在這裡我用的是 layui 框架中的 tree 組件來展現遞歸處理後的數據。新建一個 scoreSum.html 和 scoreSum.js 文件,在 scoreSum.html 中放一個 id="tree" 的元素,用來加載遞歸處理彙總累加分數值之後的樹形節點數據。

scoreSum.html 頁面的代碼如下:

  1. <title>使用遞歸彙總累加題目分數值/<title>
  2. <link>
  3. <style>
  4. body{
  5. margin: 12px;
  6. }
  7. .layui-tree li a.active{
  8. border-color: lightblue;
  9. background-color: #b1deff;
  10. }
  11. <fieldset>
  12. <legend>題目索引/<legend>
  13. 大題下面有小題,大題的分數是彙總其下各小題的分數

    • 正在加載...
  • <fieldset>
  • <legend>題目信息/<legend>
  • 題目的分數值以“scoreSum”為準,“score”是從數據庫中讀出來的,僅作參考。

  • 點擊左邊節點,以加載節點信息……
  • scoreSum.js 存放的就是上面幾段腳本:

    1. //節點數據集合
    2. var nodes = [{
    3. ……
    4. }];
    5. //使用遞歸算法,實現彙總累加節點的分數值
    6. var getScoreSum = function (data, options){
    7. ……
    8. }
    9. //layui 模塊化組件引用
    10. layui.use(['jquery', 'layer', 'tree'], function () {
    11. var $ = layui.$, layer = layui.layer, tree = layui.tree;
    12. ……
    13. buildTreeNodes();
    14. });

    四、效果截圖

    使用 JavaScript 遞歸算法,彙總累加題目的分數值

    效果截圖:大題的分數值是其下小題分數值的累計之和(11=5+6)。

    使用 JavaScript 遞歸算法,彙總累加題目的分數值

    本案例完整的項目文件目錄截圖。


    分享到:

    閱讀更多 班馬文章BMWZ 的文章

    關鍵字: 題目 CSS JSON


    相關文章: