zoukankan      html  css  js  c++  java
  • diff.js 列表对比算法 源码分析

    diff.js列表对比算法 源码分析

    npm上的代码可以查看 (https://www.npmjs.com/package/list-diff2) 源码如下:

      1 /**
      2  * 
      3  * @param {Array} oldList   原始列表
      4  * @param {Array} newList   新列表 
      5  * @param {String} key 键名称
      6  * @return {Object} {children: [], moves: [] }
      7  * children 是源列表 根据 新列表返回 移动的新数据,比如 oldList = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}];
      8  newList = [{id: 2}, {id: 3}, {id: 1}]; 最后返回的children = [
      9   {id: 1},
     10   {id: 2},
     11   {id: 3},
     12   null,
     13   null,
     14   null
     15  ]
     16  moves 是源列表oldList 根据新列表newList 返回的操作,children为null的话,依次删除掉掉,因此返回的是
     17  moves = [
     18   {type: 0, index:3},
     19   {type: 0, index: 3},
     20   {type: 0, index: 3},
     21   {type: 0, index: 0},
     22   {type: 1, index: 2, item: {id: 1}}
     23  ]
     24  注意:type = 0 是删除操作, type = 1 是新增操作
     25 */
     26 function diff(oldList, newList, key) {
     27   var oldMap = makeKeyIndexAndFree(oldList, key);
     28   var newMap = makeKeyIndexAndFree(newList, key);
     29   var newFree = newMap.free;
     30 
     31   var oldKeyIndex = oldMap.keyIndex;
     32   var newKeyIndex = newMap.keyIndex;
     33 
     34   var moves = [];
     35   var children = [];
     36   var i = 0;
     37   var freeIndex = 0;
     38   var item;
     39   var itemKey;
     40 
     41   while(i < oldList.length) {
     42     item = oldList[i];
     43     itemKey = getItemKey(item, key);
     44     if(itemKey) {
     45       if(!newKeyIndex.hasOwnProperty(itemKey)) {
     46         children.push(null);
     47       } else {
     48         var newItemIndex = newKeyIndex[itemKey];
     49         children.push(newList[newItemIndex]);
     50       }
     51     } else {
     52       var freeItem = newFree[freeIndex++];
     53       children.push(freeItem || null);
     54     }
     55     i++;
     56   }
     57   // 删除不存在的项
     58   var simulateList = children.slice(0);
     59   i = 0;
     60   while (i < simulateList.length) {
     61     if (simulateList[i] === null) {
     62       remove(i);
     63       // 调用该方法执行删除
     64       removeSimulate(i);
     65     } else {
     66       i++;
     67     }
     68   }
     69 
     70   // 
     71   var j = i = 0;
     72   while (i < newList.length) {
     73     item = newList[i];
     74     itemKey = getItemKey(item, key);
     75 
     76     var simulateItem = simulateList[j];
     77     var simulateItemKey = getItemKey(simulateItem, key);
     78     if (simulateItem) {
     79       if (itemKey === simulateItemKey) {
     80         j++;
     81       } else {
     82         // 新的一项,插入
     83         if (!oldKeyIndex.hasOwnProperty(itemKey)) {
     84           insert(i, item);
     85         } else {
     86           var nextItemKey = getItemKey(simulateList[j + 1], key);
     87           if (nextItemKey === itemKey) {
     88             remove(i);
     89             removeSimulate(j);
     90             j++;
     91           } else {
     92             insert(i, item);
     93           }
     94         }
     95       }
     96     } else {
     97       insert(i, item);
     98     }
     99     i++;
    100   }
    101 
    102   function remove(index) {
    103     var move = {index: index, type: 0};
    104     moves.push(move);
    105   }
    106 
    107   function insert(index, item) {
    108     var move = {index: index, item: item, type: 1};
    109     moves.push(move);
    110   }
    111 
    112   function removeSimulate(index) {
    113     simulateList.splice(index, 1);
    114   }
    115   return {
    116     moves: moves,
    117     children: children
    118   }
    119 }
    120 /*
    121  * 列表转化为 keyIndex 对象
    122  * 比如如下代码:
    123  var list = [{key: 'id1'}, {key: 'id2'}, {key: 'id3'}, {key: 'id4'}]
    124  var map = diff.makeKeyIndexAndFree(list, 'key');
    125  console.log(map); 
    126 // {
    127   keyIndex: {id1: 0, id2: 1, id3: 2, id4: 3},
    128   free: []
    129 }
    130  * @param {Array} list
    131  * @param {String|Function} key
    132 */
    133 function makeKeyIndexAndFree(list, key) {
    134   var keyIndex = {};
    135   var free = [];
    136   for (var i = 0, len = list.length; i < len; i++) {
    137     var item = list[i];
    138     var itemKey = getItemKey(item, key);
    139     if (itemKey) {
    140       keyIndex[itemKey] = i;
    141     } else {
    142       free.push(item);
    143     }
    144   }
    145   return {
    146     keyIndex: keyIndex,
    147     free: free
    148   }
    149 }
    150 
    151 function getItemKey(item, key) {
    152   if (!item || !key) {
    153     return;
    154   }
    155   return typeof key === 'string' ? item[key] : key[item]
    156 }
    157 exports.makeKeyIndexAndFree = makeKeyIndexAndFree;
    158 exports.diff = diff;
    View Code

    该js的作用是:深度遍历两个列表数据,每层的节点进行对比,记录下每个节点的差异。并返回该对象的差异。
    @return {Object} {children: [], moves: [] }
    children 是源列表 根据 新列表返回 移动或新增的数据。

    比如 

    oldList = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}];
    newList = [{id: 2}, {id: 3}, {id: 1}]; 

    最后返回的

    children = [
      {id: 1},
      {id: 2},
      {id: 3},
      null,
      null,
      null
     ]

    moves 是源列表oldList 根据新列表newList 返回的操作,children为null的话,依次删除掉掉,因此返回的是

    moves = [
      {type: 0, index:3},
      {type: 0, index: 3},
      {type: 0, index: 3},
      {type: 0, index: 0},
      {type: 1, index: 2, item: {id: 1}}
    ]

    注意:type = 0 是删除操作, type = 1 是新增操作
    因为 

    oldList = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}]; 
    newList = [{id: 2}, {id: 3}, {id: 1}];

    所以oldList根据newList来对比,{id: 4} 和 {id: 5} 和 {id: 6} 在新节点 newList没有找到,因此在moves设置为 {type:0, index:3},
    所以oldList数据依次变为 [{id: 1}, {id: 2}, {id: 3}, {id: 5}, {id: 6}] 和  [{id: 1}, {id: 2}, {id: 3}, {id: 6}] 和  [{id: 1}, {id: 2}, {id: 3}]
    每次在moves存储了一次的话,原数组会删掉当前的一项,因此oldList 变为 [{id: 1}, {id: 2}, {id: 3}], newList 为 [{id: 2}, {id: 3}, {id: 1}],
    然后各自取出该值进行比较,也就是 oldList变为 [1, 2, 3], newList变为 [2, 3, 1]; 因此oldList相对于 newList来讲的话,第一项不相同就删掉该项 所以moves新增一项{type: 0, index:0}, index从0开始的,表示第一项被删除,然后第二项1被添加,因此moves再加一项 {type: 1, index:2, item: {id: 1}};
    代码理解如下:

    该方法需要传入三个参数 oldLsit, newList, key;
    oldList 和 newList 是原始数组 和 新数组, key是根据键名进行匹配。

    现在分别对oldList 和 newList 传值如下数据:
    var oldLsit = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}];
    var newList = [{id: 2}, {id: 3}, {id: 1}];

    因此 var oldMap = makeKeyIndexAndFree(oldList, key);
    makeKeyIndexAndFree代码如下:

    function makeKeyIndexAndFree(list, key) {
      var keyIndex = {};
      var free = [];
      for (var i = 0, len = list.length; i < len; i++) {
        var item = list[i];
        var itemKey = getItemKey(item, key);
        if (itemKey) {
          keyIndex[itemKey] = i;
        } else {
          free.push(item);
        }
      }
      return {
        keyIndex: keyIndex,
        free: free
      }
    }

    getItemKey 代码如下:

    function getItemKey(item, key) {
      if (!item || !key) {
        return;
      }
      return typeof key === 'string' ? item[key] : key[item]
    }

    执行代码变成如下:

    var oldMap = {
      keyIndex: {
        1: 0, 
        2: 1,
        3: 2,
        4: 3,
        5: 4, 
        6: 5
      },
      free: []
    }
    var newMap = makeKeyIndexAndFree(newList, key); 输出如下:
    var newMap = {
      free: [],
      keyIndex: {
        1: 2,
        2: 0,
        3: 1
      }
    }

    注意:上面的是把{id: xx} 中的xx当做键, 但是当xx是数字的话,他会把数字当做索引位置来存储。

    var newFree = newMap.free = [];
    var oldKeyIndex = oldMap.keyIndex;
    var newKeyIndex = newMap.keyIndex;
    
    var moves = [];
    var children = [];
    var i = 0;
    var freeIndex = 0;
    var item;
    var itemKey;
    
    while(i < oldList.length) {
      item = oldList[i];
      itemKey = getItemKey(item, key);
      if(itemKey) {
        if(!newKeyIndex.hasOwnProperty(itemKey)) {
          children.push(null);
        } else {
          var newItemIndex = newKeyIndex[itemKey];
          children.push(newList[newItemIndex]);
        }
      } else {
        var freeItem = newFree[freeIndex++];
        children.push(freeItem || null);
      }
      i++;
    }

    while循环旧节点oldList,获取其某一项,比如 {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}, 然后根据键名获取某一项的值,分别为:1,2,3,4,5,6。
    然后判断 新节点中的 newKeyIndex 是否有该属性键名,newKeyIndex = {1: 2, 2: 0, 3: 1}, 判断newKeyIndex 是否有属性 1, 2, 3, 4, 5, 6, 如果没有的话,把null放到children数组里面去,如果有的话,存入children数组里面去,因此children的值变为如下:

    children = [
      {id: 1},
      {id: 2},
      {id: 3},
      null,
      null,
      null
    ];
    
    // 删除不存在的项
    var simulateList = children.slice(0);
    i = 0;
    while (i < simulateList.length) {
      if (simulateList[i] === null) {
        remove(i);
        // 调用该方法执行删除
        removeSimulate(i);
      } else {
        i++;
      }
    }

    把children数组的值赋值到 simulateList列表中,如果某一项等于null的话,调用 remove(i)方法,把null值以对象的形式保存到moves数组里面去,
    同时删除simulateList列表中的null数据。
    代码如下:

    function remove(index) {
      var move = {index: index, type: 0};
      moves.push(move);
    }
    function removeSimulate(index) {
      simulateList.splice(index, 1);
    }
    simulateList 数据变成如下:
    simulateList = [
      {id: 1},
      {id:  2},
      {id:  3}
    ];

    因此 moves 变成如下数据:

    var moves = [
      {index: 3, type: 0},
      {index: 3, type: 0},
      {index: 3, type: 0}
    ];

    再执行如下代码:

    var j = i = 0;
    while (i < newList.length) {
      item = newList[i];
      itemKey = getItemKey(item, key);
    
      var simulateItem = simulateList[j];
      var simulateItemKey = getItemKey(simulateItem, key);
      if (simulateItem) {
        if (itemKey === simulateItemKey) {
          j++;
        } else {
          // 新的一项,插入
          if (!oldKeyIndex.hasOwnProperty(itemKey)) {
            insert(i, item);
          } else {
            var nextItemKey = getItemKey(simulateList[j + 1], key);
            if (nextItemKey === itemKey) {
              remove(i);
              removeSimulate(j);
              j++;
            } else {
              insert(i, item);
            }
          }
        }
      } else {
        insert(i, item);
      }
      i++;
    } 

    遍历新节点数据newList var newList = [{id: 2}, {id: 3}, {id: 1}]; 然后 itemKey = getItemKey(item, key); 那么itemKey=2, 3, 1
    var simulateItem = simulateList[j];
    simulateList的值如下:

    simulateList = [
      {id: 1},
      {id:  2},
      {id:  3}
    ];

    获取simulateList数组中的某一项,然后
    var simulateItemKey = getItemKey(simulateItem, key);
    因此 simulateItemKey值依次变为1, 2, 3; 先循环最外层的 新数据 2, 3,1,然后在循环内层 旧数据 1, 2 ,3,
    判断 itemKey === simulateItemKey 是否相等,相等的话 什么都不做, 执行下一次循环,j++; 否则的话,先判断是否在旧节点oldKeyIndex
    能否找到新节点的值;oldKeyIndex 数据如下:

    {
      1: 0, 
      2: 1,
      3: 2,
      4: 3,
      5: 4, 
      6: 5
    }

    如果没有找到该键名的话,说明该新节点数据项就是新增的,那就新增一项,新增的代码如下:

    function insert(index, item) {
      var move = {index: index, item: item, type: 1};
      moves.push(move);
    }

    因此moves代码继续新增一项,type为1就是新增的。否则的话,获取simulateList中的下一个数据值,进行对比,如果能找到的话,执行remove(i)方法,因此moves再新加一项
    {type:0, index: i}; 此时 j = 0; 删除原数组的第一项,然后继续循环上面一样的操作。

    整个思路重新整理一遍:

    var before = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}];
    var after = [{id: 4}, {id: 3}, {id: 2},{id: 1}];
    var diffs = diff.diff(before, after, 'id');

    上面的代码初始化,原数据 before, 新数据 after,key键为id,
    oldMap 值为:

    oldMap = {
      keyIndex: {
        1: 0,
        2: 1,
        3: 2,
        4: 3, 
        5: 4,
        6: 5
      }
    }

    newMap的值为

    newMap = {
      keyIndex: {
        1: 3,
        2: 2,
        3: 1,
        4: 0
      }
    }
    oldKeyIndex = oldMap.keyIndex = {
      1: 0,
      2: 1,
      3: 2,
      4: 3, 
      5: 4,
      6: 5
    }
    var newKeyIndex = newMap.keyIndex = {
      1: 3,
      2: 2,
      3: 1,
      4: 0
    };

    遍历 before,获取某一项的值,因此分别为1,2,3,4,5,6;判断newKeyIndex是否有该值,如果没有的话,该它置为null,保存到 children数组里面去;
    因此

    children = [
      {id: 1},
      {id: 2},
      {id: 3},
      {id: 4},
      null,
      null
    ]

    把children赋值到 simulateList 数组里面去,然后对simulateList数组去掉null值,因此simulateList值变为如下:

    simulateList = [
      {id: 1},
      {id: 2},
      {id: 3},
      {id: 4}
    ]
    moves = [
      {
        type: 0,
        index: 4
      },
      {
        type: 0,
        index: 4
      }
    ]

    最后遍历新节点 newList = [{id: 4}, {id: 3}, {id: 2},{id: 1}]; 获取该键值分别为:4, 3, 2, 1;
    获取源数组simulateList里面的键值为 1, 2 , 3, 4;

    所以 4, 3, 2, 1 遍历 和 1, 2, 3, 4 遍历判断是否相等思路如下:
    1. 遍历newList键值 为 4, 先和 1比较,如果相等的话,j++,跳到下一个内部循环,否则的话,先判断该键是否在oldKeyIndex里面,如果不存在的话,说明是新增的,否则的话就进入else语句,判断simulateList下一个值2 是否和 4 相等,不相等的话,直接插入值到数组的第一个位置上去,因此 moves的值变为如下:

    moves = [
        {
          type: 0,
          index: 4
        },
        {
          type: 0,
          index: 4
        },
        {
          type: 1,
          index: 0,
          item: {id: 4}
        }
    ]

    2. 同样的道理 ,把 遍历newList的第二项 3, 和第一步一样的操作,最后3也是新增的,如下moves的值变为如下:

    moves = [
        {
          type: 0,
          index: 4
        },
        {
          type: 0,
          index: 4
        },
        {
          type: 1,
          index: 0,
          item: {id: 4}
        },
        {
          type: 1,
          index: 1,
          item: {id: 3}
        }
    ]

    3. 同样,遍历newList的第三项值为2, 和第一步操作,进入else语句,第一个值不符合,接着遍历第二个值,相等,就做删除操作,因此moves变为如下值:

    moves = [
        {
          type: 0,
          index: 4
        },
        {
          type: 0,
          index: 4
        },
        {
          type: 1,
          index: 0,
          item: {id: 4}
        },
        {
          type: 1,
          index: 1,
          item: {id: 3}
        },
        {
          type: 0,
          index: 2
        }
    ]

    且 oldList被删除一项,此时j = 0, 所以被删除掉第一项 因此 oldList = [2, 3, 4];

    4. 同样,遍历 newList的第四项值为 1, 和第一步操作一样,值都不相等,因此做插入操作,因此moves值变为

    moves = [
        {
          type: 0,
          index: 4
        },
        {
          type: 0,
          index: 4
        },
        {
          type: 1,
          index: 0,
          item: {id: 4}
        },
        {
          type: 1,
          index: 1,
          item: {id: 3}
        },
        {
          type: 0,
          index: 2
        },
        {
          type: 1,
          index: 3,
          item: {id: 1}
        }
    ]

    最后以对象的方式 返回 moves 和 children。

  • 相关阅读:
    requests+lxml+xpath爬取豆瓣电影
    hisi出的H264码流结构
    单片机复位电路原理介绍
    二极管与、或门,三极管非门电路原理
    Windows Route 路由表命令整理
    理解Windows中的路由表和默认网关
    Windows路由表详解
    linux 路由表设置 之 route 指令详解
    linux中service *** start与直接运行/usr/bin/***的区别
    Linux运行与控制后台进程的方法:nohup, setsid, &, disown, screen
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/7507526.html
Copyright © 2011-2022 走看看