数组运算

_.uniq = _.unique

_.uniq(array, isSorted, iteratee):根据 iteratee 设置的重复标准,对 array 进行去重。通过 isSorted,提高对有序数组的去重效率。

源码

_.uniq = _.unique = function (array, isSorted, iteratee, context) {
    // 如果第二个参数不是 bool, 则应当理解为是比较函数, 且默认是没有排序的数组
    if (!_.isBoolean(isSorted)) {
        context = iteratee;
        iteratee = isSorted;
        isSorted = false;
    }
    if (iteratee != null) iteratee = cb(iteratee, context);
    var result = [];
    var seen = []; // 标识数组
    for (var i = 0, length = getLength(array); i < length; i++) {
        var value = array[i],
            computed = iteratee ? iteratee(value, i, array) : value;
        // 如果排好序了, 直接通过比较操作!==
        if (isSorted) {
            // 如果已经排序, seen 只需要反映最近一次见到的元素
            // !i: 第一个元素放入结果数组
            // seen !== computed 没有见过的元素放入结果数组
            if (!i || seen !== computed) result.push(value);
            // 刷新最近一次所见
            seen = computed;
        } else if (iteratee) {
            // 如果尚未排序, 且存在比较函数, 亦即不能直接通过 === 判断
            // 那么我们无法直接通过_.contains(result, value) 判断 value 是否已经存在
            // 例如_.unique([{age:13, name:"tom"},{age:15, name:"jack"},{age:13, name:"bob"}], 'age']
            // 这种情况下就需要借助于 seen 这个辅助数组存储计算后的数组元素
            if (!_.contains(seen, computed)) {
                seen.push(computed);
                result.push(value);
            }
        } else if (!_.contains(result, value)) {
            // 否则直接通过 contains 进行判断
            result.push(value);
        }
    }
    return result;
};

underscore 提供了一个的数组去重功能十分健壮,他完成了如下优化:

  • 通过指定 iteratee,让我们灵活定义 “重复标准”,而不简单只是值比较 (===)。
  • 通过指定 isSorted,提高对排序后的数组去重性能。

用例

// 一般去重
_.uniq([1, 1, 1, 1, 2, 2, 2, 3, 3]);
// => [1,2,3]

// 指定 `iteratee`
_.unique([{ age: 13, name: 'tom' }, { age: 15, name: 'jack' }, { age: 13, name: 'bob' }], 'age']);
// => [{age:13, name:"tom"}, {age:15, name: "jack"}]

// 性能对比
var arr = [];
for (i = 0; i < 100000; i++) {
    arr.push(i);
    arr.push(i);
}

console.time('未声明已排序');
_.uniq(arr);
console.timeEnd('未声明已排序');
// => "未声明已排序: 72054.154ms"

console.time('声明已排序');
_.uniq(arr, true);
console.timeEnd('声明已排序');
// => "声明已排序: 5.667ms"

_.union

_.union(...arrays):求数组并集。

源码

// master
_.union = restArgs(function(arrays) {
  return _.uniq(flatten(arrays, true, true));
});

// 1.8.3
_.union = function() {
  return _.uniq(flatten(arguments, true, true));
};

可以看到,在 master 分支上,_.union 函数支持 rest 参数,由于 rest 参数最终会被处理成数组,所以,假定我们在调用 _.union 的时候传入两个数组 [1, 2, 3, [4, 5]][6, [7]],那么 arrays 实际上是:[[1, 2, 3, [4, 5], [6, [7]]]。所以,我们先要 浅展开 arrays,然后去重,得到数组的并集。

用例

_.union([1, 2, 3, [4, 5]], [6, [7]]);
// => [1, 2, 3, [4, 5], 6, [7]]

// 由于设置了 `flatten` 的 `strict` 为 `true`,所以,只能传递数组求并集
_.union({name: 'wxj'}, [6, [7]]);
// => [6, [7]]

_.intersection

_.intersection(...arrays):求取数组交集

源码

_.intersection = function (array) {
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = getLength(array); i < length; i++) {
        var item = array[i];
        if (_.contains(result, item)) continue;
        var j;
        //
        for (j = 1; j < argsLength; j++) {
            if (!_.contains(arguments[j], item)) break;
        }
        if (j === argsLength) result.push(item);
    }
    return result;
};

数组交集的求取思路为:遍历第一个数组的每个元素,在之后的所有数组中找寻是否有该元素,有则放入结果数组。

用例

_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
// => [1, 2]

_.difference

_.difference(...array):求取数组差集

源码

// master
_.difference = restArgs(function(array, rest) {
  rest = flatten(rest, true, true);
  return _.filter(array, function(value){
    return !_.contains(rest, value);
  });
});

// 1.8.3
_.difference = function(array) {
  var rest = flatten(arguments, true, true, 1);
  return _.filter(array, function(value){
    return !_.contains(rest, value);
  });
};

数组差集的求取思路为:令剩余的数组为 rest,遍历第一个数组 array,保留 array 中存在的,而 rest 中不存在的值。

用例

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
// => [1, 3, 4]

_.unzip

_.unzip(array):解压 array

假设我们有三个数组:

  • names['moe', 'larry', 'curly']
  • ages[18, 23, 30]
  • sexes['male', 'female', 'male']

他们构成了一个新的数组 infos

var infos = [names, ages, sex]

数组解压就是将多个数组的对应位置的元素抽离出来,组成新的数组:

[['moe', 18, 'male'], ['larry', 23, 'female'], ['curly', 30, 'male']]

源码

_.unzip = function (array) {
    // 原数组的长度反映了最后分的组数目
    var length = array && _.max(array, getLength).length || 0;
    // 结果数组与原数组等长
    var result = Array(length);
    // 分别抽离元素放到新数组
    for (var index = 0; index < length; index++) {
        result[index] = _.pluck(array, index);
    }
    return result;
};

用例

var names = ['moe', 'larry', 'curly'];
var ages = [18, 23, 30];
var sexes = ['male', 'female', 'male'];

var students = _.unzip([names, ages, sexes]);
// => students: [['moe', 18, 'male'], ['larry', 23, 'female'], ['curly', 30, 'male']]

_.zip

_.zip(array):压缩 array

给定若干个数组,对他们进行的压缩过程为:

  1. 各个数组对应位置抽离元素,组成新的数组
  2. 将这些新数组再合并为一个数组

比如我们给定若干个数组:

var student1 = ['moe', 18, 'male'];
var student2 = ['larry', 23, 'female'];
var student3 = ['curly', 30, 'male'];

那么 _.zip 操作的结果为:

var infos = [['moe', 'larry', 'curly'], [18, 23, 30], ['male', 'female', 'male']]

所以,_.zip 操作的过程也十分类似 _.unzip,只是传入参数由单个数组变为多个数组,那么我们只需要将这多个数组先合并一下就可以了,underscore 也正是这么做的。

源码

// master
_.zip = restArgs(_.unzip);

// 1.8.3
_.zip = function() {
    return _.unzip(arguments);
};

用例

var student1 = ['moe', 18, 'male'];
var student2 = ['larry', 23, 'female'];
var student3 = ['curly', 30, 'male'];

var infos = _.unzip(student1, student2, student3);
// => [['moe', 'larry', 'curly'], [18, 23, 30], ['male', 'female', 'male']]

results matching ""

    No results matching ""