数组的高级用法 (4)

137 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

上篇,我们一起学些了querystring, compose , promise的顺序执行,今天我们继续数组的奇妙之旅。

大家,动起来。

数组数据合并

这里数组合并说的两个数组,各有所需的数据的一部分,然后合并起来。 更多详情参见:两个数组数据的高效合并方案

其里面巧妙的应用了 Array.from , reduce, includes等等数组方法以及迭代器等等思想。

贴一段代码:

export function mergeArray<S = any, T = any, R = any>(targetArr: T[] = [], sourceArr: S[] = [], options: MergeOptions<S, T> = DEFAULT_MERGE_OPTIONS): R[] {

    // 有一个不是数组
    if (!Array.isArray(sourceArr) || !Array.isArray(targetArr)) {
        // return [...targetArr] as any[];
        return targetArr as any;
    }

    const opt: MergeOptions = { ...DEFAULT_MERGE_OPTIONS, ...options };

    // 判断sourceKey和sourceProperties
    if (typeof opt.sourceKey !== "string") {
        console.error("无效的soureKey");
        return targetArr as any[];
    }

    const wTypes = ["string", "number"];

    // TODO:: 更安全的检查
    const getSKeyFn = typeof opt.sourceKey === "function" ? opt.sourceKey : (s: S) => s[opt.sourceKey as string];

    let { targetKey } = opt;
    if (targetKey == null) {
        targetKey = opt.sourceKey;
    }

    const getTKeyFn = typeof targetKey === "function" ? targetKey : (t: T) => t[opt.targetKey as string];

    const objMap: Record<string, S> = sourceArr.reduce((obj: Record<string, S>, cur: S) => {
        const key = getSKeyFn(cur);
        if (wTypes.includes(typeof key)) {
            obj[cur[key]] = cur
        }
        return obj;
    }, Object.create(null));

    const { desc, sourceKey, sKMap = null } = opt;

    const sourceLen = sourceArr.length;
    let hitCounts = 0;
    let walkCounts = 0;

    let resultArr = Array.from(targetArr);
    const targetLen = targetArr.length;
    let tempTObj, keyValue, tempSObj;

    const stepIter = getStepIter(0, targetLen - 1, desc);

    while (stepIter.hasNext()) {

        const index = stepIter.current;

        walkCounts++

        if (walkCounts > MAX_WLAK_COUNT) {
            console.error(`mergeArray 遍历次数超过最大遍历次数 ${MAX_WLAK_COUNT}, 终止遍历,请检查程序逻辑`);
            break;
        }

        tempTObj = resultArr[index];

        // 目标比对的键值
        keyValue = tempTObj[getTKeyFn(tempTObj)];
        if (keyValue == null || (tempSObj = objMap[keyValue]) == null || tempSObj[sourceKey] != keyValue) {
            stepIter.next()
            continue;
        }

        resultArr[index] = mergeObject(tempTObj, tempSObj, undefined, sKMap);
        hitCounts++
        if (hitCounts >= sourceLen) {
            break;
        }

        stepIter.next()
    }

    console.log(`mergeArray:: sourceArr(${sourceLen}), 统计:遍历次数${walkCounts}, 命中次数${hitCounts}`);
    return resultArr as any[];
}

分组

就是把某些属性值相同值得项分为为一组:

const hasOwn = Object.prototype.hasOwnProperty;
function group(arr, fn) {
  // 不是数组
  if (!Array.isArray(arr)) {
    return arr;
  }
  // 不是函数
  if (typeof fn !== "function") {
    return Array.from(arr);
  }
  var result = {};
  var v;
  arr.forEach((val, index) => {
    v = fn(val, index);
    if (!hasOwn.call(result, v)) {
      result[v] = []
    }
    result[v].push(val);
  });
  return result;
}
let result = group(["apple", "pear", "orange", "peach"],
  v => v.length);

console.log(result); 
// { '4': [ 'pear' ], '5': [ 'apple', 'peach' ], '6': [ 'orange' ] }

result = group([{
  name: "tom",
  score: 88
},{
  name: "Jim",
  score: 80
},{
  name: "Nick",
  score: 88
}], v=> v.score)

console.log(result);
//{
//  '80': [ { name: 'Jim', score: 80 } ],
//  '88': [ { name: 'tom', score: 88 }, { name: 'Nick', score: //88 } ]
//}

目前这种实现是有一些缺点的,如果你第二个参数函数最后返回值是对象,因为对象作为键的时候,会被转为 [object Object]字符串,所以啊,都是相同的项。

result = group([{
  name: "tom",
  score: 88
},{
  name: "Jim",
  score: 80
},{
  name: "Nick",
  score: 88
}], v=> v)

console.log(result);

{
  '[object Object]': [
    { name: 'tom', score: 88 },
    { name: 'Jim', score: 80 },
    { name: 'Nick', score: 88 }
  ]
}

不过这也算是合理的,如果计算返回是对象,大概率都是不同的对象,即使使用Set来排他,也是不合理的。

小结

今天学习了数组的分组和合并。 今天你收获了吗?

引用

数组 MDN
JS数组奇巧淫技
25个你不得不知道的数组reduce高级用法
13 个 JS 数组精简技巧,一起来看看
JavaScript数组操作_专题_脚本之家