underScore专题-偏函数,缓存函数

偏函数

先来看定义:在计算机科学中,局部应用是指固定一个函数的一些参数,然后产生另一个更小元的函数。

什么是元?元是指函数参数的个数,比如一个带有两个参数的函数被称为二元函数。

这是一本书的定义,看起来跟柯里化函数有点像。

对比柯里化函数:柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。

偏函数则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。

举个简单的例子:

    function isType(type, obj) {
        return {}.toString.call(obj) == "[object " + type + "]"
    }
    var isObject = isType("Object", { a: 'maile' })
    var isObject = isType("Object", { b: 'maile' })

如果要判断一个参数obj是不是对象,是不是需要调用很多次isObject,掉用多次是没有问题的,但是其中一个参数却会一直重复出现,这个时候就可以使用偏函数的思想,来固定一个参数。

    function isType(type) {
        return function(obj) {
            return {}.toString.call(obj) == "[object " + type + "]"
        }
    }

    var isObject = isType("Object")
    var isString = isType("String")
    var isArray = Array.isArray || isType("Array")
    var isFunction = isType("Function")
    var isUndefined = isType("Undefined")

这样就不需要每次都传递同一个参数'Object'了。再需要判断是不是对象的时候,只需要isObject({a: 'maile'})这样调用就可以了。

这样需要更改原函数isType,如果说我想固定2个参数,或者3个参数,是不是又要去更改isType函数,这样会显得很不智能,能不能实现一个函数,让我们不必更改isType函数,可以任意固定参数?

underScore中就实现了这样一个方法,partial方法,先来看看使用方法:

    var obj = { name: '麦乐' }
    function isType(type, obj) {
        return {}.toString.call(obj) == "[object " + type + "]"
    }

    var isObject = _.partial(isType, 'Object')
    console.log(isObject(obj)) // true

固定两个参数,下面这个函数用来判断某个对象是否函数某个属性: 

    var obj = { name: '麦乐' }
    function hasKey(type, obj, key) {
        return {}.toString.call(obj) == "[object " + type + "]" && obj.hasOwnProperty(key)
    }

    var nameClass = _.partial(hasKey, 'Object', obj)
    console.log(nameClass('name')) // true

只要调用一个partial函数就可以任意的固定参数,下面来实现一下这个函数:

    function partial(func) {
        var args = Array.prototype.slice.call(arguments, 1)
        var bound = function() {
            var index = 0,
                length = args.length,
                rest = Array(length)
            for (var i = 0; i < length; i++) {
                rest[i] = args(i)
            }
            for (; index < arguments.length; index++) {
                rest.push(arguments[index])
            }
            func.apply(this, args)
        }
    }

调用partial函数,返回了bound函数,看上面的例子,再调用isObject函数实际就是调用bound函数,partial函数接受的func函数,就是isType函数,最后需要调用这个函数并把所有的参数传递给这个参数,func.apply(this, args),可以看出,ages里面包含了所有的参数,bound函数内部主要做的就是收集参数并且调用,而收集参数离不开arguments,这里就是运用arguments这个属性来完成的。

ags收集到是调用partial函数过着中传递进来的参数,不包含第一个,因为第一个是一个函数:

var args = Array.prototype.slice.call(arguments, 1)

rest就是复制了args数组,并且把isObject调用的过程中传递的参数收集。这样就实现了一个偏函数。

underScore源码中是借助restArguments这个函数来实现的偏函数

在前面章节中《underScore专题-剩余参数-restArguments》中有关于这个函数的详细讲解,这里就略过了,直接看偏函数:

var partial = restArguments(function(func, boundArgs) {
        var placeholder = partial.placeholder;
        var bound = function() {
            var position = 0, length = boundArgs.length;
            var args = Array(length);
            for (var i = 0; i < length; i++) {
                args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
            }
            while (position < arguments.length) args.push(arguments[position++]);
            return executeBound(func, bound, this, this, args);
        };
        return bound;
    });

    partial.placeholder = _;

这里生成的partial函数就是restArguments函数参数的副本,调用过程中除了第一个参数之后所有的参数都以数组的形式存在了boundArgs中,类似于es6中的剩余参数。

    function isType(type, obj) {
        return {}.toString.call(obj) == "[object " + type + "]"
    }
    var isObject = _.partial(isType, 'Object')

这样就可以随意的固定参数,实现不同的功能。

缓存函数

1 首先来看一个简单的计算函数:

  let count = 0
    function add(a, b) {
      count ++
      return a + b
    }
    add(7, 8)
    add(7, 8)
    add(7, 8)
    add(7, 8)
    console.log('计算的次数', count) // 计算的次数 4

但是,如果传入的值一样, 就会出现重复计算,每调用一次就会计算一次。我们首先来考虑,怎样避免这些重复计算? 
下面代码实现了一个闭包。里面存储了计算的结果,相同的参数避免了重复的计算。 

 

 function cache(fn) {
      var cacheObj = {}
      return function() {
        // 把arguments伪数组变成真数组
        let argument = Array.prototype.slice.apply(arguments)
        // 对象中如果已经有该参数,就直接去取值 不用计算
        if(cacheObj.hasOwnProperty(argument)) {
          return cacheObj[argument]
        }
        // 如果没有 就调用函数去计算,并把结果存储在cacheObj中
        return cacheObj[argument] = fn.apply(this, argument)
      }
    }
    const cacheAdd = cache(add)
    console.log(cacheAdd(7,8)) // 15
    console.log(cacheAdd(7,8)) // 15
    console.log(cacheAdd(7,8)) // 15
    console.log(cacheAdd(7,8)) // 15
    console.log('计算的次数', count) // 计算的次数 1


缓存函数写好了,下面我们就来测试一下这两个函数的性能。理论上讲,缓存函数应该比简单的add函数快很多,事实是这样吗?

 

  let count = 0
    function add(a, b) {
      count ++
      return a + b
    }
    console.time('add')
    for(let i=0; i<10000; i++) {
      add(1, 1)
    }
    console.timeEnd('add') // 执行次数 10000
    console.log('执行次数', count)
    // add: 0.77294921875ms 不太稳定
    let count = 0
    function add(a, b) {
      count ++
      return a + b
    }
    const cacheAdd = cache(add)
    console.time('cacheAdd')
    for(let i=0; i<10000; i++) {
      cacheAdd(1, 1)
    }
    console.timeEnd('cacheAdd') // 执行次数 1
    console.log('执行次数', count)
    // cacheAdd: 17.5810546875ms


对比一下时间发现,缓存函数并没有比非缓存函数快,相反,还会比非缓存函数慢很多。这是为什么呢?

如果我们把函数修改一下,改为 a*b, 传入大数字值,结果会怎样?

   

let count = 0
    function add(a, b) {
      count ++
      return a * b
    }
    const cacheAdd = cache(add)
    console.time('cacheAdd')
    for(let i=0; i<10000; i++) {
      cacheAdd(645, 893)
    }
    console.timeEnd('cacheAdd') // 执行次数 1
    console.log('执行次数', count)
    // cacheAdd: 14.232177734375ms
    let count = 0
    function add(a, b) {
      count ++
      return a * b
    }
    // const cacheAdd = cache(add)
    console.time('add')
    for(let i=0; i<10000; i++) {
      // cacheAdd(645, 893)
      add(645, 893)
    }
    console.timeEnd('add') // 执行次数 10000
    console.log('执行次数', count)
    // add: 0.810791015625ms


然而并没有出现我们想要的结果,那是不是说缓存函数没有呢?看下面的例子

 

 /* 
      这是一个生成斐波纳契数列的函数,传入的参数,就是数列在第几位上的值
      在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=2, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
    */ 
    let count = 0
    function Fibonacci(n) {
      count++
      return n <= 2 ? 1 : Fibonacci(n-1) + Fibonacci(n-2)
    }
    console.time('Fibonacci')
    Fibonacci(30)
    console.timeEnd('Fibonacci') // Fibonacci: 10.988037109375ms
    console.log('执行次数', count) // 执行次数 1664079
    console.log(Fibonacci(30)) // 832040


我传递的是30,大家可以试试如果传递50会怎样?浏览器会卡死,一直转圈圈。使用缓存函数来计算:

   

 let count = 0
    function Fibonacci(n) {
      count++
      return n <= 2 ? 1 : cacheFibonacci(n-1) + cacheFibonacci(n-2)
    }
    function cache(fn) {
      var cacheObj = {}
      return function() {
        // 把arguments伪数组变成真数组
        let argument = Array.prototype.slice.apply(arguments)
        // 对象中如果已经有该参数,就直接去取值 不用计算
        if(cacheObj.hasOwnProperty(argument)) {
          return cacheObj[argument]
        }
        // 如果没有 就调用函数去计算,并把结果存储在cacheObj中
        return cacheObj[argument] = fn.apply(this, argument)
      }
    }
    let cacheFibonacci = cache(Fibonacci)
    console.time('cacheFibonacci')
    cacheFibonacci(30)
    console.timeEnd('cacheFibonacci') // cacheFibonacci: 0.118896484375ms
    console.log('执行次数', count) // 执行次数 30
    console.log(cacheFibonacci(30)) // 832040


区别已经很明显,运行快了很多。计算次数也小了很多,这样我们算出第100位斐波那契数列值也不是问题。

2 上面我们亲自验证了缓存函数的好处,那我们写的缓存函数有没有什么问题呢?是不是百分之百都回返回正确的值呢?看下面的例子

 

  function add(obj) {
      return obj.a
    }
    function cache(fn) {
      var cacheObj = {}
      return function() {
        // 把arguments伪数组变成真数组
        let argument = Array.prototype.slice.apply(arguments)
        // 对象中如果已经有该参数,就直接去取值 不用计算
        if(cacheObj.hasOwnProperty(argument)) {
          return cacheObj[argument]
        }
        // 如果没有 就调用函数去计算,并把结果存储在cacheObj中
        return cacheObj[argument] = fn.apply(this, argument)
      }
    }
    let cacheAdd = cache(add)
    console.log(cacheAdd({a:1})) // 1
    console.log(cacheAdd({a:2})) // 1
    console.log(cacheAdd({a:3})) // 1


参数传入的是对象,每次返回的值都是1 这是为什么?我门来看下cacheObj是什么样的?

         console.log(cacheObj) 
        /* {[object Object]: 1}
          [object Object]: 1
          __proto__: Object
          */


可以看到cacheObj中只有一个属性,[object,object],因为参数会作为key存储,key在对象中以字符串的形式存在的,这里有一个隐式转换,生成key的过程中,调用了对象的toString方法,参数都会转变成[object,object]。每次执行的时候就会去取第一次存下来来的值,所以一直就只能拿到一个结果。

[function aa() {},8].toString()
"function aa() {},8"
[{a:3}].toString()
"[object Object]"
[4,5,6].toString()
"4,5,6"

我们可以在 Array.prototype.slice.apply(arguments)改一下代码,JSON.stringfy(Array.prototype.slice.apply(arguments))就可以了。

   

 let count = 0
    function add(obj) {
      count++
      return obj.a
    }
    function cache(fn) {
      var cacheObj = {}
      return function() {
        // 把arguments伪数组变成真数组
        let argument = Array.prototype.slice.apply(arguments)
        let argumentString = JSON.stringify(Array.prototype.slice.apply(arguments))
        console.log(cacheObj)
        // 对象中如果已经有该参数,就直接去取值 不用计算
        if(cacheObj.hasOwnProperty(argument)) {
          return cacheObj[argumentString]
        }
        // 如果没有 就调用函数去计算,并把结果存储在cacheObj中
        return cacheObj[argumentString] = fn.apply(this, argument)
      }
    }
    let cacheAdd = cache(add)
    console.log(cacheAdd({a:1, b:3, c:4})) // 1
    console.log(cacheAdd({a:1, c:4, b:3})) // 1
    console.log(cacheAdd({c:1, b:3, a:3})) // 1
    console.log('执行次数', count) // 执行次数 3


看下上面的例子,我传递的对象是一样的,就是换了顺序,函数就执行了3次,而我们希望的值执行一次。打印一下cacheObj会发现{[{"a":1,"b":3,"c":4}]: 1, [{"a":1,"c":4,"b":3}]: 1}结果是这样的。所以在使用缓存函数的时候,要注意参数传递的问题。


underScore中也实现了一个缓存函数,支持传递key来标记参数,也直接传递函数生成key,也是address。

function memoize(func, hasher) {
        var memoize = function(key) {
            var cache = memoize.cache;
            // 如果传递hasher则使用hasher函数来标记key,
            var address = '' + (hasher ? hasher.apply(this, arguments) : key);
            if (!_has(cache, address)) cache[address] = func.apply(this, arguments);
            return cache[address];
        };
        memoize.cache = {};
        return memoize;
    }

再来求解一次斐波那契数: 

    function hasher() {
        var n = arguments[0]
        return n + 'mai'
    }
    let count = 0
    function fibonacci(n) {
        count++
        return n <= 2 ? 1 : cacheFibonacci(n - 1) + cacheFibonacci(n - 2)
    }

    let cacheFibonacci = _.memoize(fibonacci, hasher)
    console.time('cacheFibonacci')
    cacheFibonacci(10)
    console.timeEnd('cacheFibonacci') // cacheFibonacci: 0.118896484375ms
    console.log('执行次数', count) // 执行次数 30
    console.log(cacheFibonacci(10)) // 832040
    console.log(cacheFibonacci.cache)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 智慧社区背景与挑战 随着城市化的快速发展,社区面临健康、安全、邻里关系和服务质量等多方面的挑战。华为技术有限公司提出智慧社区解决方案,旨在通过先进的数字化技术应对这些问题,提升城市社区的生活质量。 2. 技术推动智慧社区发展 技术进步,特别是数字化、无线化、移动化和物联化,为城市社区的智慧化提供了可能。这些技术的应用不仅提高了社区的运行效率,也增强了居民的便利性和安全性。 3. 智慧社区的核心价值 智慧社区承载了智慧城市的核心价值,通过全面信息化处理,实现对城市各个方面的数字网络化管理、服务与决策功能,从而提升社会服务效率,整合社会服务资源。 4. 多层次、全方位的智慧社区服务 智慧社区通过构建和谐、温情、平安和健康四大社区模块,满足社区居民的多层次需求。这些服务模块包括社区医疗、安全监控、情感沟通和健康监测等。 5. 智慧社区技术框架 智慧社区技术框架强调统一平台的建设,设立数据中心,构建基础网络,并通过分层建设,实现平台能力及应用的可持续成长和扩展。 6. 感知统一平台与服务方案 感知统一平台是智慧社区的关键组成部分,通过统一的RFID身份识别和信息管理,实现社区服务的智能化和便捷化。同时,提供社区内外监控、紧急救助服务和便民服务等。 7. 健康社区的构建 健康社区模块专注于为居民提供健康管理服务,通过整合医疗资源和居民接入,实现远程医疗、慢性病管理和紧急救助等功能,推动医疗模式从治疗向预防转变。 8. 平安社区的安全保障 平安社区通过闭路电视监控、防盗报警和紧急求助等技术,保障社区居民的人身和财产安全,实现社区环境的实时监控和智能分析。 9. 温情社区的情感沟通 温情社区着重于建立社区居民间的情感联系,通过组织社区活动、一键呼叫服务和互帮互助平台,增强邻里间的交流和互助。 10. 和谐社区的资源整合 和谐社区作为社会资源的整合协调者,通过统一接入和身份识别,实现社区信息和服务的便捷获取,提升居民生活质量,促进社区和谐。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值