1. 两数之和 (leetcode)

今日有朋友问我一些python入门的免费教程。不知怎的就想到了刷题,就想看看是怎么个流程, 然后就搜索了下,网上推荐的网站:https://leetcode-cn.com/

为了看这个检查的流程,就找了个最简单的,语言使用最近经常使用的javascript.刚开始的时候, 无论如何执行,执行结果都没有。后来发现是浏览器的广告拦截插件的原因。于是关闭后刷新总算 执行正常了。

因为两数之合很简单,就随便写了个常规的流程,提交后发现可以看到执行效率的排名,内存占用的排名。 比如下面的常规的流程:

  //方法1:
//执行用时: 100 ms
//内存消耗: 41.3 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    for(let i =0; i < nums.length; i++) {
            for(let j=i+1; j < nums.length; j++) {
                if(nums[i]+nums[j] === target) {
                    return [i,j];
                }
            }
        }
    return [-1,-1];
    };

我的提交显示 执行效率超过46%(100ms),内存超过44%(41.3 MB)

这激发了我的兴趣,就想着如何优化或者负优化吧。于是就有了下面更多的提交:

//方法2:
//执行用时: 520 ms
//内存消耗: 42.6 MB
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  for(let i =0; i < nums.length; i++) {
      let test = target - nums[i];
      let index = nums.findIndex((e, j)=> j != i && e == test );
      if(index >= 0) {
          return [i, index];
      }
  }
  return [-1,-1];
  };

上面这个方法似乎效率超越了2%, 我不知道哪个比这个还要慢了。

  //方法3:
//执行用时: 72 ms
//内存消耗: 41.7 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
        let obj = {};
    for(let i =0; i < nums.length; i++) {
        if(obj[nums[i]] != undefined) {
            return [obj[nums[i]], i];
        } else {
            obj[target-nums[i]] = i;
        }
    }
    return [-1,-1];
    };

上面的方法使用了json的格式,利用判断key是否存在,但json格式的key只能是字符串,利用题目中的 索引都是数字的特性,简单优化了下类型:

    //方法4:
  //执行用时: 64 ms
//内存消耗: 41.6 MB
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  let obj = [];
  for(let i =0; i < nums.length; i++) {
      if(obj[nums[i]] != undefined) {
          return [obj[nums[i]], i];
      } else {
          obj[target-nums[i]] = i;
      }
  }
  return [-1,-1];
  };

数组的方式比object的方式快一些

  //方法5:
//执行用时: 60 ms
//内存消耗: 41.5 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = [];
    for(let i =0, j=nums.length-1; i <= j; i++, j--) {
        if(obj[nums[i]] != undefined) {
            return [i, obj[nums[i]]];
        } else {
            obj[target-nums[i]] = i;
            if(obj[nums[j]] != undefined) return [obj[nums[j]],j];
            else
            obj[target-nums[j]] = j;
        }
    }
    return [-1,-1];
    };

上面的方式还是使用数组的方式,只是将循环减半了。因此复杂度也少了些了。

    //方法6
 //执行用时: 60 ms
//内存消耗: 41.4 MB 
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  let obj = new Map();
  for(let i =0, j=nums.length-1; i <= j; i++, j--) {
      if(obj.has(nums[i])) {
          return [i, obj.get(nums[i])];
      } else {
          obj.set(target-nums[i], i);
          if(obj.has(nums[j])) return [obj.get(nums[j]),j];
          else
          obj.set(target-nums[j], j);
      }
  }
  };

使用map,不影响效率的同时,降低了内存消耗

  //方法7
//执行用时: 64 ms
//内存消耗: 41.4 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = new Map();
    let i = 0;
    let j = nums.length-1; 
    while(i<=j) {
        let left = nums.shift();
        let right = nums.pop();
        if(obj.has(left)) {
            return [i, obj.get(left)];
        }
        obj.set(target-left, i);
        if(obj.has(right)) {
            return [obj.get(right), j];
        }
        obj.set(target-right, j);
        ++i;
        --j;
    }
    };

方法7与方法6基本一样,只是将for循环使用while循环来代替了,并且,旨在为了降低内存使用的shift, pop 操作,从测试结果而言,并没有效果。这应该和javascript的内存分配机制有关系,毕竟变量nums还在不断的使用中。

  //方法8
//执行用时: 64 ms
//内存消耗: 41.6 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = [];
    let i = 0;
    let j = nums.length-1; 
    while(i<=j) {
        let left = nums.shift();
        let right = nums.pop();
        if(obj[left] != undefined) {
            return [i, obj[left]];
        }
        obj[target-left] = i;
        if(obj[right] != undefined) {
            return [obj[right], j];
        }
        obj[target-right] = j;
        ++i;
        --j;
    }
    };

方法8与方法7一样,只是将map修改成了数组。执行效率一样,但内存占用多了些。

    //方法9:
//执行用时: 72 ms
//内存消耗: 41.5 MB
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  let obj = new Map();
  let i = 0;
  let j = nums.length-1; 
  while(i<=j) {
      let left = nums[i];
      let right = nums[j];
      if(obj.has(left)) {
          return [i, obj.get(left)];
      }
      obj.set(target-left, i);
      if(obj.has(right)) {
          return [obj.get(right), j];
      }
      obj.set(target-right, j);
      ++i;
      --j;
  }
  };

方法9与方法7一样,只是将数据的提取使用数组的方式,而非shift,pop的方式, 即使这一简单改变,也导致 测试执行效率降低了些。

  //方法10
//执行用时: 52 ms
//内存消耗: 41.7 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = {};
    let i = 0;
    let j = nums.length-1; 
    while(i<=j) {
        let left = nums.shift();
        let right = nums.pop();
        if(obj[left] != undefined) {
            return [i, obj[left]];
        }
        obj[target-left]= i;
        if(obj[right] != undefined) {
            return [obj[right], j];
        }
        obj[target-right]= j;
        ++i;
        --j;
    }
    };

方式10和方法9一样,只是使用了object和数组的shift,pop的方法,

除了负优化的方法2之外,总体而言,减少循环可以优化效率。使用object和map的方式会比较节约些内存。 受到数据样本情况,上面的测试结果也不是非常准确的,比如,如果数据样本大都集中在前面几个就有 答案的话,方法1可能比较快的,反之,方法1则是比较慢的;如果数据样本的答案集中在两侧的话,循环减半 的方式则相对比较快,(对于前一种情况,循环减半效果也比较好)。即使是同一个算法,每次提交 执行用时也不相同,不如方法10,我这边在测试的时候,最快在52ms, 而排在前面的小于40ms的,我将 算法提交上去后,却在64ms,方法7有时候也会执行用时也会在56ms左右。

其实无论何种方式,何种优化,关键是从何种角度去思考问题,解决问题。

javascript的Function函数

我在使用element-ui的table的控件的时候,对其中的template很好奇,使用slot-scope字段可以透传 当前row的相关信息,在react.js做列表控件的时候,就想着也实现一个类似的。

我没有仔细研究element-ui的代码是如何实现的,不过 javascript有一个Function函数,正好也在 做这个相关的功能,所以就用这个函数弄了一个类似的。

function rend_scope(t, key, slot) {
    if(!t.props['slot-scope']) {
        return (<Fragment key={key}>{React.createElement(
            t.type,
            t.props,
        )}</Fragment>);
    }
    let scope = t.props['slot-scope'];
    if(!scope.startsWith('{')) {
        let newprops = {...t.props};
        newprops[scope] = slot;
        return (<Fragment key={index2}>{React.createElement(
            t.type,
            newprops,
        )}</Fragment>);
    } else {
        let result = null;
        let looseJsonParse = (obj)=>{
            return Function('"use strict";return (' + obj + ')')()(slot);
        };
        let s = `function(${scope}=slot){return ${scope};}`;
        result = looseJsonParse(s);
        return (<Fragment key={index2}>{React.createElement(
            t.type,
            {...t.props, ...result},
        )}</Fragment>);
    }
};

上面的代码首先判断是否存在slot-scope, 不存在的话,则动态创建。

如果存在的话,则判断是否是'{‘开头的,如果不是,则是将slot的作为props指定字段的值插入,然后动态创建。

如果是'{‘开头的话,表示要提取slot中的某部分字段的内容,这些字段作为props中指定的字段和值插入,然后动态创建。 这里使用了Function的功能,首先将slot-scope中的字符串拼接为一个简单的字段提取的函数, 上面变量s中字符串 函数的功能就是提取object中指定字段的功能,虽然通过分割slot-scope中的字符串,然后对object通过 数组方式读取也可以,我还是喜欢通过Function的功能,这样如果某个字段填写有问题的话,执行到会直接报错的,报错后 位置也好找。looseJsonParse里面的功能就是生成新的js的函数并且执行,因为动态生成函数后,执行的参数是slot, 而之前s的函数就变成了是返回slot中某些字段的内容并组成新的object了,最后将这部分和老的props合并并动态生成。

第2部分的判断如果用第3部分来做的话,则是将slot的整体与老的props合并,与我想要的结果并不同。

golang vs rust vs javascript

最近研究了几天rust,并不是特别的深入,主要用来做wasm。 之前的部分关键程序使用golang写的,但不是wasm,也简单封装了接口, 试了下wasm。

就体验而言,rust的学习曲线实在太抖,相比之前的golang学习,golang用个个把小时看下语法,结构,项目组织方式,然后就可以做 项目了。rust看了几天,貌似还在门外徘徊,不过没关系,经过几天的摸索,我终于学会了使用rust来编写wasm,并且和elementui的 框架的配合调整为我想要的了。而这个只是灾难的开始。

我想试着用rust,一个主要原因是golang生成的wasm实在太大,什么都不做的2MB+了。另外一个最重要的原因是golang生成的wasm, 在解析一个很大的json字符串的时候,竟然需要13+秒的时间,如果使用第三方的json解析的话,需要1+秒的时间,虽然可以使用tinygo 来减少wasm的容量,但tinygo无法使用第三方的json解析。而在查看wasm相关信息的时候,rust据说不错。所以就决定写个试试了。

下面的坑都是根据回忆记录的。不一定是对的,所有执行的目标都是wasm

  1. rust的json解析,如果不使用js_sys的json解析的话,需要很长的时间,没记错的话,需要很久,至少10s+是有的。这个json数据大约在几十MB之间。 如果使用js_sys的话,需要0.8秒左右的时间。之所以提出来是因为太明显了。最后逼迫我必须使用js_sys的解析。缺点就是描写的各种累心
  2. 因为1解析出来的json数据会被各个不同的部分使用,因此这个变量要被全局存储。全局存储大概搞了我好多天,因为没有最理想的。 刚开始准备一个最简单的Option的方案,不支持指针类的; 升级为lazy_static方案, 不支持js_sys::JsValue,JsonValue太慢 放弃; thread_local方案,这个需要使用with,这个是我目前可以找到的可以支持js_sys::JsValue的了,也只能这样用了,使用with, 丑就丑吧
  3. 因为2使用了with方案,而代码中的数据在处理的时候,会从其他地方得到的数据进行整合,比如我又从其他的公共变量获取数据,而 with的处理结果,除非使用clone的方式,否则由于变量生命期的关系,各种编译报错,我真的恨死rust的这种反人类的设计了。 明明我只是为了读取内部的一段内存的数据,结果非的逼我clone出一段内存来,感情现在内存真的都是白菜价啊
  4. 很无奈,其他部分(我只测试了一个,以后的以后想办法对付吧),我使用了lazy_static方案,我试了下Mutex,结果在浏览器里 一跑,发现浏览器cpu 100%卡住了,等好久才有反映,这下好了,整个数据不用处理了。既然Mutex不行,就试试RwLock,反正我 数据仍到HashMap中,更新少,查询多,但要返回相应的数据,还得clone,于是又回到3了。
  5. 我无数次准备掐死自己的心都有了,让你犯贱选rust,你看使用golang多少,一周的rust研究成功,golang两个小时就达到相同效果了。 但golang真的慢啊
  6. rust的设计初衷或许非常完美,但智能指针的设计模式或者所有权的设计,真的是非人类。即使是学院的erlang,也没有rust这么变态, 我感觉我好久都没关注过erlang了,真有点怀念熟悉了之后处理代码的那种感觉。

vue中使用umd

今天在整理代码的时候,将@json-editor/json-editor升级了,升级到最新的2.2.1版本了,然后发现 很悲剧了,程序怎么也找不到JSONEditor了。我以为是我程序哪块有问题了,所以各种调查。

幸好线上版本没有做更改,结果在线上版本中发现一句window.JSONEditor=…, 然后立刻在本地查找, 发现本地更新完之后已经没有这句话了。所以可以肯定不是我程序的事情啦。

最后发现线上版本在4月7号提交了一个 Unify the format of the module to umd.的补丁, 大致从 2.1.0的版本都不支持了。

做了下测试,发现2.0.0的版本都还正常,虽然可以强制版本为2.0.0,但既然发现了,总想试着解决下。 从网上搜索”vue 无法 import umd”, 发现原来是webpack的坑。

坑找到了,就开始填坑。首先安装 @babel/plugin-transform-modules-umd:

npm i –save-dev @babel/plugin-transform-modules-umd

然后在babel.config.js的配置的plugins中添加这个插件, 类似这样:

module.exports = {
presets: [
‘@vue/app’
],
plugins:[‘@babel/plugin-transform-modules-umd’]
}

javascript的Object.assign复制

我对javascript研究不深,基本处于可用,能用而已。一直意味Object.assign就是将原来的在内存中完整复制一份镜像, 没想到Object.assign仅复制第一层,而其它层还保持内存链接的形式。另外一个坑就是复制的类型要一致, 比如数组的复制,Object.assign([],array),而不是Object.assign({},array);后者将会将数组转化为Object。

所以,要想完全复制,还是老老实实的JSON.parse(JSON.stringify());吧。但我没研究对于Object带函数的是否通用。