美文网首页
[underscore 源码学习] 乱序数组 - 洗牌算法

[underscore 源码学习] 乱序数组 - 洗牌算法

作者: 小黄人get徐先生 | 来源:发表于2020-01-28 18:34 被阅读0次

    洗牌算法

    • 算法思路在宏观上可以概括为:将集合视为牌堆,不停地从牌堆中抽牌构成新的牌堆,直至新牌堆的牌数到达预设数量。
    • underscore 1.9 版本开始,洗牌算法通过 _.sample 实现。
    • _.sample(array, n):从 array 随机取出 n 个样本。
    • underscore 中的抽样函数正基于洗牌算法。
    // 源码
    _.sample = function (array, n, guard) {
      ...
    }
    

    下面我们开始源码学习:

    test.js

    (function(root) {
    
        const toString = Object.prototype.toString;
        const push = Array.prototype.push;
    
        const _ = function(obj) {
    
            if (obj instanceof _) {
                return obj;
            }
    
            if (!(this instanceof _)) {
                return new _(obj);
            }
            this._wrapped = obj;
        };
    
        // 返回一个 [min, max] 区间内的任意整数
        _.random = function(min, max) {
            if (max == null) {
                max = min;
                min = 0;
            }
            // 这里 + 1 的原因是因为 Math.random() 的值永远 (0,1) ; 大于 0 小于 1 。
            // 假设 min 为 3,max 为 6;所以是 min + x;x 要想取到 3,则必须 + 1。(0.99 * 4 = 3.96,向下取整为 3)。
            return min + Math.floor((Math.random() * (max - min + 1)));
        };
    
        _.clone = function(obj) {
            return _.isArray(obj)? obj.slice() : Object.assign({}, obj);
        };
    
        _.sample = function(array, n) {
            if (n == null) {
                return array[_.random(array.length - 1)]
            }
            const sample = _.clone(array);
            const length = sample.length;
            const last = length - 1;
            n = Math.max(Math.min(n, length), 0);
            for (let index = 0; index < n; index++) {
                // 抽取 [index, last] 中某一位
                // 例如这里随机取 [0, 10] 中的 5,交换 sample[0] 和 sample[5] 的值。下一次迭代取 [1, 10] 之间的值。所以不会重复的值
                const rand = _.random(index, last);
                const temp = sample[index];
                sample[index] = sample[rand]; // 交换
                sample[rand] = temp;
            }
            return sample.slice(0, n);
        };
    
        //  以上 ---------------------
    
        const createPredicateIndexFinder = function(dir) {
            return function (array, predicate, context) {
                predicate = cb(predicate, context);
                const length = array.length;
                let index = dir > 0? 0 : length - 1;
                for (index; index >= 0 && index < length; index += dir) {
                    if (predicate(array[index], index, array)) {
                        return index;
                    }
                }
                return -1;
            };
        };
    
        _.findIndex = createPredicateIndexFinder(1);
        _.findLastIndex = createPredicateIndexFinder(-1);
    
        _.sortedIndex = function(array, obj, iteratee, context) {
            iteratee = cb(iteratee, context, 1);
            const value = iteratee(obj);
            let low = 0;
            let high = array.length;
            while(low < high) {
                let mid = Math.floor((low + high) / 2);
                if (iteratee(array[mid]) < value) {
                    low = mid + 1;
                } else {
                    high = mid;
                }
            }
            return low;
        };
    
        _.isBoolean = function(obj) {
            return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
        };
    
        // 判断数据类型为 NaN
        _.isNaN = function(obj) {
            return _.isNumber(obj) && isNaN(obj);
        };
    
        const createIndexFinder = function(dir, predicateFind, sortedIndex) {
            return function(array, item, idx) {
                let i = 0;
                let length = array.length;
                // idx 布尔值代表是否是已经排序好的数组
                if (sortedIndex && _.isBoolean(idx) && length) {
                    // 满足条件则使用二分查找
                    idx = sortedIndex(array, item);
                    return array[idx] === item ? idx : -1;
                }
    
                // 特殊情况 如果要查找的元素是 NaN 类型 NaN !== NaN
                if (item != item) {
                    idx = predicateFind(slice.call(array, i, length), _.isNaN);
                    return idx >= 0 ? idx + i : -1;
                }
    
                // 非上述情况正常遍历
                for (idx = dir > 0? i : length -1; idx >=0 && idx < length; idx += dir) {
                    if (array[idx] === item) return idx;
                }
    
                return -1;
            };
        };
    
        _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
        _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
    
    
        _.filter = function(obj, predicate, context) {
            predicate = cb(predicate, context);
    
            const results = [];
    
            _.each(obj, function(value, index, list) {
                if (predicate(value, index, list)) {
                    results.push(value);
                }
            });
    
            return results;
        };
    
        // (direction)dir 为 1,从左到右累加;dir 为 -1,从右到左累加。
        const createReduce = function(dir) {
    
            const reducer = function(obj, iteratee, memo, initial) {
                const keys = !_.isArray(obj) && Object.keys(obj);
                const length = (keys || obj).length;
                let index = dir > 0? 0 : length - 1;
    
                // 如果不包含初始值,则使用 第一个或最后一个值 作为初始化值,并相应移动 index dir 步。
                if (!initial) {
                    memo = obj[keys? keys[index] : index];
                    index += dir;
                }
    
                for (index; index >= 0 && index < length; index += dir) {
                    const currentKey = keys? keys[index] : index;
                    memo = iteratee(memo, obj[currentKey], currentKey, obj);
                }
                return memo;
            };
    
    
            return function(obj, iteratee, memo, context) {
                // 如果值的个数大于等于 3,说明存在初始化值
                const initial = arguments.length >= 3;
                return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
            };
        };
    
        _.reduce = createReduce(1);
    
        _.reduceRight = createReduce(-1);
    
        // rest 参数
        _.restArguments = function(func) {
            // rest 参数位置
            const startIndex = func.length - 1;
            return function() {
                const length = arguments.length - startIndex;
                const rest = Array(length);
                // rest 数组中的成员
                for (let index = 0; index < length; index++) {
                    rest[index] = arguments[index + startIndex];
                }
                // 非 rest 参数成员的值一一对应
                const args = Array(startIndex + 1);
                for (let index = 0; index < startIndex; index++) {
                    args[index] = arguments[index];
                }
    
                args[startIndex] = rest;
                return func.apply(this, args);
            };
        };
    
    
        _.isFunction = function(obj) {
            return typeof obj === 'function';
        };
    
        const cb = function(iteratee, context, count) {
            if (iteratee === void 0) {
                return _.identity;
            }
    
            if (_.isFunction(iteratee)) {
                return optimizeCb(iteratee, context, count);
            }
        };
    
        const optimizeCb = function(func, context, count) {
            if (context === void 0) {
                return func;
            }
    
            switch (count == null ? 3 : count) {
                case 1:
                    return function(value) {
                        return func.call(context, value);
                    };
                case 3:
                    return function(value, index, obj) {
                        return func.call(context, value, index, obj);
                    };
                case 4:
                    return function(memo, value, index, obj) {
                        return func.call(context, memo, value, index, obj);
                    }
            }
        };
    
        _.identity = function(value) {
            return value;
        };
    
        _.map = function(obj, iteratee, context) {
            // 生成不同功能迭代器
            const cbIteratee = cb(iteratee, context);
            const keys = !_.isArray(obj) && Object.keys(obj);
            const length = (keys || obj).length;
            const result = Array(length);
    
            for (let index = 0; index < length; index++) {
                const currentKey = keys? keys[index] : index;
                result[index] = cbIteratee(obj[currentKey], index, obj);
            }
    
            return result;
        };
    
        _.unique = function(obj, callback) {
            const res = [];
            for (let i = 0; i < obj.length; i++) {
                const val = callback? callback(obj[i]) : obj[i];
                if (res.indexOf(val) === -1) {
                    res.push(val);
                }
            }
            return res;
        };
    
        _.isArray = function(obj) {
            return toString.call(obj) === "[object Array]";
        };
    
        _.functions = function(obj) {
            const res = [];
            for (let key in obj) {
                res.push(key);
            }
            return res;
        };
    
        _.each = _.forEach = function(obj, iteratee, context) {
            iteratee = optimizeCb(iteratee, context);
            if (_.isArray(obj)) {
                for (let i = 0;i < obj.length; i++) {
                    iteratee(obj[i], i, obj);
                }
            } else {
                for (let key in obj) {
                   iteratee(obj[key], key, obj);
                }
            }
            return obj;
        };
    
        _.chain = function(obj) {
            const instance = _(obj);
            instance._chain = true;
            return instance;
        };
    
        const result = function(instance, obj) {
            return instance._chain? _(obj).chain() : obj;
        };
    
        _.prototype.value = function() {
            return this._wrapped;
        };
    
        _.mixin = function(obj) {
            _.each(_.functions(obj), (name) => {
                const func = obj[name];
    
                _.prototype[name] = function() {
                    let args = [this._wrapped];
                    push.apply(args, arguments);
                    return result(this, func.apply(this, args));
                };
            });
        };
    
        _.mixin(_);
    
        root._ = _;
    })(this);
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>underscore</title>
    </head>
    <body>
        <script src="./test.js"></script>
        <script>
            console.log(_.sample([1,2,3,4,5,6,7,8,9]));
            console.log(_.sample([1,2,3,4,5,6,7,8,9], 8));
        </script>
    </body>
    </html>
    

    显示结果如下:


    相关文章

      网友评论

          本文标题:[underscore 源码学习] 乱序数组 - 洗牌算法

          本文链接:https://www.haomeiwen.com/subject/oplmthtx.html