Category Archives: Hello World

Hello World

闪展腾挪,只为避开apply

apply是pandas一个强大的工具函数,可以逐行对数据进行操作。pandas的优点很多,比如代码简洁、相对直接遍历效率更高;但它的“效率高”是相对的,相对于merge之类的操作,apply的执行时间可能会高出两个数量级。比如下面这行:

1
2
  #为订单数据集增加以后一列:微信支付的金额
  df['pay_wx'] = df.apply(lambda x: (x.pay if x.chan == 'wx' else 0), axis=1)

从上面的代码看,逻辑非常简洁,但是实际测试,50万的数据量,要20秒。下面尝试改造一下

1
2
3
4
  df_wx = df[df['chan'] == 'wx']
  df_wx.rename(columns={'pay': 'pay_wx'}, inplace=True)
  df['pay_wx'] = df_wx['pay_wx']
  df.fillna({'pay_wx': 0}, inplace=True)

代码从一行变成了四行,不过执行效率可以高很多:50万数据,时间大约0.7秒
当然,方法不止一种,对于更见的判断条件,可以直接用merge操作:

1
2
3
4
5
6
7
8
9
10
  #合并某项分类依据
  df['color'] = df.apply(lambda x: ('红' if x.color == '粉红' else x.color), axis=1)
  #使用merge优化
  df_h = df[df['color'] == '粉红'][['id', 'color']]
  df_h['color'] = '红'
  df_nh = df[df['color'] != '粉红'][['id', 'color']]
  df_hs = pd.concat([df_h, df_nh])
  df_hs.rename(columns={'color':'color2'}, inplace=True)
  df = pd.merge(df, df_h, on='id', how='left')
  df['color'] = df['color2']

通过上面这种方法改造,执行时间和第一个方法接近,50万条数据大约0.8秒

———–
转载请注明出处:http://www.jiangkl.com/2022/12/no_apply

Hello World

Arduino软串口的使用

Arduino Uno自带一个串口,也就是管脚1、2,“硬串口”,IDE upload代码、使用串口监视器调试程序,用到的都是这个串口,那么,如果你还需要用串口连接其他设备时,应该怎么办呢?
为了不应现调试程序,这里需要用到“软串口”,也就是将其他数字管脚当成串口使用~~の,难道要按TTL串口协议解析串口数据不成?
到是没有这么复杂,Arduino已经自带SoftwareSerial库,完成了对串口逻辑的封装。以下代码是串口读取VAMLED电压模块的逻辑,咱们以此为例来看看软串口是怎么用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <SoftwareSerial.h>
 
SoftwareSerial EVSerial(2, 3); //定义软串口管脚,分别是rx tx 
 
void setup (){
  EVSerial.begin(9600); //波特率
  Serial.begin(9600);
}
 
void loop() {
  if(EVSerial.available() > 0){
    Serial.print(">> ");
    Serial.println(EVSerial.readString());//在串口监视器,打印电压模块传递的电压值
    delay(1000); //每读取一次,延时
    EVSerial.print(0); //向VAMLED模块发送“上报电压”指令
  }
}

以上逻辑,即包含了SoftwareSerial读取和写入的操作,不过实际使用有两个问题:
1. 每模拟一个软串口都要消耗单片机的资源,内存和flash、堆栈等。软串口和硬串口对于发送数据是一样的,但在接收数据时,性能就有差别了,硬串口是支持中断接收数据的,不会影响程序运行的实时性,但是软串口不支持中断,接收串口数据你得去不断轮询,收到的数据还有可能会丢掉
2. 具体到上面这个例子,实际收到的电压值包含乱码,不好解析,具体原因正在研究,等有结果了再更新o(* ̄︶ ̄*)o
———-
转载请注明出处:http://www.jiangkl.com/2022/10/arduino_serial/

Hello World

浅析schedule与update的关系

schedule是cocos自带的定时器,这里简单试试这玩意的执行情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    onLoad () {
        this.schedule(()=>{
            let now = new Date().getTime();
            console.log('schedule-test, schedule 0.001', now);
        }, 0.001);
        this.schedule(()=>{
            let now = new Date().getTime();
            console.log('schedule-test, schedule  0.01', now);
        }, 0.01);
        this.schedule(()=>{
            let now = new Date().getTime();
            console.log('schedule-test, schedule   0.1', now);
        }, 0.1);
    }
 
    update (dt) {
        let now = new Date().getTime();
        console.log('schedule-test,         update', now);
    }

输出如下:
————
schedule-test, update 1658395521046
schedule-test, schedule 0.001 1658395521047
schedule-test, schedule 0.01 1658395521048
schedule-test, update 1658395521063
schedule-test, schedule 0.001 1658395521064
schedule-test, schedule 0.01 1658395521065
schedule-test, update 1658395521080
schedule-test, schedule 0.001 1658395521081
schedule-test, schedule 0.01 1658395521082
schedule-test, update 1658395521097
schedule-test, schedule 0.001 1658395521098
schedule-test, schedule 0.01 1658395521099
schedule-test, schedule 0.1 1658395521100
schedule-test, update 1658395521113
schedule-test, schedule 0.001 1658395521114
schedule-test, schedule 0.01 1658395521115
。。。。
————
简单总结一下,可以得到下面几个结论:
1. 在cocos的世界里,一“帧”,就是它的最小计时单位,也是schedule的最小调用周期,比帧间隔再小的时间单位,对schedule无效
2. schedule(可能)会在每一帧的update以后,调起定时逻辑
3. 即使定时间隔大于帧间隔,schedule也不能按毫秒级的时间精确调起回调函数(这一点从定时“0.1s”的回调里可以看出,不过上面没贴出出这么多)
嗯,简单来说看,可以把schedule当成低配版setInterval,既然cocos提供了自己的定时器,就尽量少用setInterval和setTimeout吧
————
转载请注明出处:http://www.jiangkl.com/2022/07/cocos_schedule_update

Hello World

使用图片序列创建cocos帧动画

    需求是制作一个简单的帧动画,图片素材来自一个gif,只有八帧,实现简单的人物动作。做demo时,没找到ccoos的切片工具,便直接用8个图片文件来实现了动画,效果还不错。但是正式项目时,八张图就是八个http请求,会拖慢页面加载速度,于是便回头找cocos做图片切片的方法
    这里要吐槽一个cocos,css有background-position,unity更是自带强大的切片工具,但找了很久,也没找到cocos对应的解决方案~~
    从前天一直拿“cocos 切片”类似的关键字搜索,一直也没找到解决方案~~到是在sprite的sliced浪费了不少时间。。。最后才发现被误导了,这玩意根本不是做图片切片用的,而是做背景图无变形放大用的。。。
    终于,昨天无意间看到一篇文章,提到的cocos的mask组件。。。嗯,就是它了,遮罩
    具体做法也很简单:父节点增加mask组件,然后将图片精灵放在子节点,调整子节点的position属性,就可以实现类似background-position的效果,详细设置见下图

———-
转载请注明出处:http://www.jiangkl.com/2022/06/sequence_animation
————————-
    三天后补充:
    就在我自己拿ps合并了项目里的二十几张小图后,然后呼哧呼哧一顿替换,将项目小图通过前面提得到的mask形式全都全换成了合并后大图,然后继续项目调优。。。然后就看到了cocos高手的调优经验:慎用mask,因为会带来过多的Drawcall。。。调整后的项目,Drawcall是36。。。咕~~(╯﹏╰)b
    同时也在这篇文章里看到了解决小图问题的方案:cocos createor自带“合并图集”的功能。。。
    这就是不好好看教材,直接靠搜代码方案做项目的结果,白费两天时间。。。
    还好上次折腾前,备份了项目。于是直接恢复项目,10分钟不到,就是用“合并图集”完成了减少http请求数的目标——然后打包运行,项目正常!
———-
    反思一下,其实就是没搞清“切图”和“合图”的区别,被自己一知半解的unity经验误导了。。。一直没找到点上
    记得大三时,刚接触汇编语言,有一次作业,需要相似的逻辑,执行10次。虽然当时已经学了c,知道程序里有“循环”可以做这事,可是在简单翻书没找到汇编的“循环”写法以后,下意识以为汇编这种低级语言没有循环,于是手动写了10次代码逻辑。老师看了以后评价:下次作业要执行有个一万次的循环,你要不要也这么写?

Hello World

cocos碰撞检测的坑–移动太快可能不触发

    下午调试一个cocos射击游戏,未击中时,目标背后的“墙”上会显示一个弹孔。本来以为挺简单的东西,可碰到了一个无厘头的问题,搞了一个小时才解决。
    具体的问题是这样的:子弹和墙的碰撞事件,有时能触发、有时不能触发。于是按照以前的经验,逐个找了下面一个容易出问题的点:
    1. 开启碰撞检查:cc.director.getCollisionManager().enabled = true;
    2. 碰撞检测函数onCollisionEnter加入到碰撞实体的绑定函数
    3. 项目设置里的碰撞分组设置
    4. 碰撞节点出了添加collder,还要至少有一方添加rigidbody
———-
    这几个点都确认了一遍,还是没解决。。。只能瞎搞了
    N种尝试以后,降低子弹速度了,问题不在重现。。。
    于是看出了端倪:游戏过程是每帧间隔的,子弹速度太快,撞击体太小,刚好撞击的过程发生在了两帧之间
    尝试增大撞击体“墙”的“厚度”,问题解决
——————
转载请注明出处:http://www.jiangkl.com/2022/03/cocos_oncollisionenter

Hello World

cocos加载网络图片

    其实就是个简单的img-src需求,显示指定url地址的图片,刚开始用的cc.loader.load,后来换成cc.assetManager.loadAny,都不好用,最后换成cc.assetManager.loadRemote,问题解决

1
2
3
4
  let imgUrl = 'xxx';//图片地址
  cc.assetManager.loadRemote(imgUrl, {ext: '.png'}, (err, tex: cc.Texture2D) => {
     this.imgNode.getComponent(cc.Sprite).spriteFrame = new cc.SpriteFrame(tex);
  });

——-
转载请注明出处:http://www.jiangkl.com/2022/01/cocos_net_img

Hello World

桑基图的绘制

    首先明确一点,这里说的桑基图,是参照百度echarts的样式的这种桑基图。这种图,既可以标示数量多寡,又可以标示相关之间的关联关系,比单纯的饼图和折线图可以承载更多的信息

    刚开始准备用python来绘制,但搜了很多资料都有些不着边际,好不容易找一个看起来靠谱的里做了一版,绘制出来了,样式却和百度这个差别很大
    于是继续找资料,找到了一个能实现需求的包,装上以后,运行。。。生成的居然是一段html,然后打开看,实际还是调的echarts。。。
    既然逃不过echarts索性改改思路吧:python处理数据,生成数据json串,保存到服务器
    然后用php做一个承接页,读取这个json串
    将承接页的链接加到统计邮件里,点链接打开统计图,还带了交互功能,体验更好,嘿嘿
    示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//nodes: 所有会出现的节点
//links: 所有的关系线,source是左边节点,target是右边节点
var dataJson = {
  "nodes":[{"name": '玉米'}, {"name": '大豆'}, {"name": '蛋白质'}, {"name": '淀粉'}, {"name": '脂肪'}, {"name": '水'}, {"name": '能量'}],
  "links": [
      {"source": '玉米', "target": '蛋白质', "value": 10},
      {"source": '玉米', "target": '淀粉', "value": 78},
      {"source": '玉米', "target": '水', "value": 2},
      {"source": '玉米', "target": '脂肪', "value": 10},
      {"source": '大豆', "target": '蛋白质', "value": 30},
      {"source": '大豆', "target": '淀粉', "value": 10},
      {"source": '大豆', "target": '脂肪', "value": 57},
      {"source": '大豆', "target": '水', "value": 3},
      {"source": '蛋白质', "target": '能量', "value": 40},
      {"source": '蛋白质', "target": '水', "value": 60},
      {"source": '淀粉', "target": '能量', "value": 60},
      {"source": '淀粉', "target": '水', "value": 40},
      {"source": '脂肪', "target": '能量', "value": 50},
      {"source": '脂肪', "target": '水', "value": 50}
   ]
}
echarts.init(document.getElementById("myChart")).setOption(
    (option = {
      title: {
        text: 'xxxx'
      },
      tooltip: {
        trigger: 'item',
        triggerOn: 'mousemove'
      },
      series: [
        {
          type: 'sankey',
          data: dataJson.nodes,
          links: dataJson.links,
          emphasis: {
            focus: 'adjacency'
          },
          lineStyle: {
            color: 'gradient',
            curveness: 0.5
          }
        }
      ]
    })
  );

交互体验可以直接去echart官网试试:https://echarts.apache.org/examples/zh/editor.html?c=sankey-simple
———-
转载请注明出处:http://www.jiangkl.com/2022/01/sankey

Hello World

h5多图上传功能实现

    这几年,前端技术日新月异,浏览器/h5获得的权限越来越多。这里使用h5页面做一个相册的上传功能,读取手机相册、上传到服务器,要求能够一次选择多张、上传前能看到预览、并能显示各个照片的上传进度。看到这里,有朋友肯定说:这有何难,input-file标签早就已经有multiple属性,加了它就能选多图了。呵呵,看起来简单,实际做起来问题还挺多。这里按顺序列一几个卡主的地方和解决方法:
1. 多图上传
    看到这里,肯定有人说了,不是有multiple属性吗?
    确实有,可实际用起来,不一样的
    iphone好办,全都支持一次选择多张图片
    Android手机有点麻烦,部分手机、部分浏览器不支持一次选多图;而且即便支持选多图,交互方式也不一样。比如我手头这台华为/荣耀,微信内置浏览器只能一次选一张图,改用系统浏览器,虽然可以选多张,但是需要长按,对于不熟悉的用户,可能发现不了这个功能,这就需要在应用里加指引;旁边同事的锤子就还好,微信内置浏览器就能选多图,和iphone的体验类似
2. 上传前的预览
    这个功能说白了,就是怎么拿到本机图片文件、放到img标签的src里。前端浏览器的权限限制一直很严格,对于图片上传当然也是如此,肯定不会把文件路径给你。试了几个方案,最终用的是FileReader读取、转base64显示。第一次用前端的FileReader功能,还算好用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var imgB64Arr = [];
//fileInput是input标签
fileInput.change=function(){
  var files = this.files;
  for(var j = 0; j < files.length; j++){
    var reader = new FileReader();
    reader.readAsDataURL(files[j]);
    reader.onload = function(){
      var v64 = this.result;
      imgB64Arr.push(b64);
      //...预览显示逻辑...
    };
  }
};

    这个方案将图片的base64编码放在了imgB64Arr数组里,以方便后续上传处理。不过考虑到现代手机拍照的大小,和多选功能,如果一次选的图很多,会占用大量的内存,所以这里流畅度待验证,可能需要优化
3. 逐个上传
    分开上传的目的有二:
    1). 递归上传,降低有网络问题带来的上传失败的概率
    2). 分开显示上传进度,这样前端体验会更好
    这里我偷了个懒,没用递归,而是定时轮训imgB64Arr数组,有内容就上传,上传完删掉,呵呵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  var uploadingImg = false; //标示当前是否有正在上传的东西
  setInterval(function(){
    if(!uploadingImg && imgB64Arr.length > 0){
      var nowUp = imgB64Arr[0];
      uploadingImg = true;
      var imgName = 'xxx';//文件命名策略
      subimtInageFile(imgName, imgBa64s[nowUp.img], function(retu, err){
        if(err){
          //TODO: 错误处理
          return;
        }
        //上传完成相关数据维护
        imgB64Arr = imgB64Arr.length > 0 ? imgB64Arr.slice(1) : [];
        uploadingImg = false;
        //TODO: 显示更新逻辑
      });
    }
  }, 300);
  function subimtInageFile(imgName, ba64, callback){
    var formData = new FormData();
    formData.append('up_photo', convertBase64UrlToBlon(ba64));
    //这里服务端要留意:没找到怎么把名字写到上传文件里,所以将文件名直接作为单独一个post参数提交
    formData.append('file_name', imgName); 
    $.ajax({
      url: '...',
      type: 'POST',
      data: formData,
      dataType: 'json',
      processData: false, //否则jquery会报错
      contentType: false,
      success: function(sd){
        callback && callback(sd);
      },
      error: function(xhr, err){
        callback && callback(false, err);
      }
    });
  }
  //图片处理,base64欢迎成图片blob
  function convertBase64UrlToBlon(base64Str){
    var bs = window.atob(base64Str.split(',')[1]);
    var ab = new ArrayBuffer(bs.length);
    var ia = new Uint8Array(ab);
    for(var i = 0; i < bs.length; i++) ia[i] = bs.charCodeAt(i);
    var bb = new Blob([ab], {type: 'image/png'});
    return bb;
  }

———-
    写到这里,大概有朋友要说了:这么麻烦干嘛,微信有wx.chooseImage()不香吗?没错,要是能调微信api的需求,当然就不用上面这么麻烦了O(∩_∩)O~
over
转载请注明出处:http://www.jiangkl.com/2022/01/h5_file_multiple

Hello World

pandas降低内存占用小技巧

这些年个人电脑的配置逐渐提高,服务器的配置反而停滞不前,自己手头的笔记本早就16G内存了,可公司用的阿里云的服务器,8G内存已经算“较高配置”了。。。
作为php、mysql之类的服务器,8G确实够用了,可做pandas数据分析,就不一定了,这不现在要做一个月度数据分析,要加载一个月的订单量,大概五千万量级,在本地虽然慢点,但结果都能跑出来,可到了服务器上,虽然linux开了虚拟内存,可不但比本地慢很多,还经常跑到半截被“killed”掉了。。。
“你被终结了,蠢货”,每到这个时候,就想起了星际里雷神的这句台词 O(∩_∩)O哈哈~
本地跑的时候,大概会占用12G内存,精简了不必要的字段以后,降低到了9G,还是不行,于是开始招其他能够降低内存占用的方法,比较有效的是修改数据类型,不过试了几次,int64改int16、int32,都没有效果,只有下面这个,让内存占从9G降低到了7、8G:
df[‘hour’] = df[‘hour’].astype(‘category’)
hour字段原本是字符串类型,直接.info(),显示是object,变成category以后,确实能节约不少空间
—————–先写到这里,后续验证有效的能减少内存占用的方法会持续更新(2021年12月21日17:07:05)

Hello World

Pandas双轴折线图生产与中文显示

双轴折线图,适合显示有两组不同类型,或者幅度差异较大的数据,这里展示的是一个对pandas生产双轴折线图的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
# figsize 输出尺寸,默认1000×300
# savePath 保存路径
# title 折线图上显示的标题
# rightColumns 放在右侧的字段
# leftLabel  rightLabel  左右标签
def buildChartLine(dataDf, savePath, xInd='日期', figsize=(10, 3), title='', rightColumns=[], leftLabel='', rightLabel='', colors=[]):
    if isLocalServ(): # macos本地的中文配置
        plt.rcParams['font.sans-serif'] = ['SimHei']
        plt.rcParams['axes.unicode_minus'] = False
    else: # Linux的中文配置
        tfont = FontProperties(fname=r"/usr/share/fonts/simhei.ttf", size=14)
    dataDf.sort_values(xInd, inplace=True) # 确保顺序
    dataDf = dataDf.copy()
    dataDf.set_index(xInd, inplace=True) # 确保索引x轴字段名
    if len(colors) == 0:
        #TODO: 自定义颜色策略
        print('no color')
    plt.figure()
    if isLocalServ(): # macos本地的中文配置,和下面else里的,主要还是中文配置的差异,其他相同
        if len(rightColumns) > 0:
            ax = dataDf.plot(secondary_y=rightColumns, figsize=figsize, color=colors, kind='line')
            ax.right_ax.set_ylabel(rightLabel)
        else:
            ax = dataDf.plot(figsize=figsize, color=colors, kind='line')
        ax.set_ylabel(leftLabel)
        if len(title) > 0: ax.set(title=title)
        ax.legend(loc=2)
        plt.legend(loc=1)
    else:
        if len(rightColumns) > 0:
            ax = dataDf.plot(secondary_y=rightColumns, figsize=figsize, color=colors, kind='line')
            ax.right_ax.set_ylabel(rightLabel, fontproperties=tfont)
        else:
            ax = dataDf.plot(figsize=figsize, color=colors, kind='line')
        ax.set_ylabel(leftLabel, fontproperties=tfont)
        if len(title) > 0: plt.title(title, fontproperties=tfont)
        ax.legend(loc=2, prop=tfont)
        plt.legend(loc=1, prop=tfont)
    # plt.show() #直接显示折线图
    plt.savefig(savePath)

使用示例

1
2
3
4
5
6
7
dataDf = pd.DataFrame({
	'日期': ['2021-11-01', '2021-11-02', '2021-11-04',  '2021-11-06',  '2021-11-03',  '2021-11-05'],
	'风力': [3, 4, 5, 7, 9,	1],
	'最高温度': [13, 24, 25, 27, 19, 21],
	'最低温度': [1, 4, 5, 7, 9, 2]},
	columns=['日期', '风力', '最高温度', '最低温度'])
buildChartLine(dataDf, 'dayInfo', xInd='日期', figsize=(10, 3), title='气象记录', rightColumns=['风力'], leftLabel='温度(℃)', rightLabel='风力(级)', colors=['#fb0320', '#e50ce2', '#770ce5'])

实际效果:

小结:搞定这个双轴折线图过程中,主要碰到了两个问题:
1. pandas plot网上资料很多,但可能是因为版本的问题,大部分直接拿过来用,是用不了的,最终拼凑出上面的方法
2. linux里中文的解决。本来想让运维协助配置linux本地字库,无奈运维搞了半天也没效果,最终只能写死字库位置这个方案;另外一个比较坑的地方,同样是字体的定义,label/title那里参数名叫fontproperties,到了legend又叫prop。。。
—–
over
转载请注明出处:http://www.jiangkl.com/2021/12/pandas_iine_font/