loethen's blog

养猫写码 meow and code


  • Home

  • Archives

fisher-yates shuffle洗牌算法

Posted on 2018-10-16

当需要随机打乱一个数组的顺序,你可能想要学习这个算法。具体的历史参见维基百科

这里简单翻译一下维基的介绍

fisher-yates是一种算法,用来在有限的序列中产生一个随机的排列,通俗一点说就是给定一个数组,这个算法可以随机打乱它的顺序。这个算法的命名是取自Ronald Fisher和Frank Yates。

下面是js版本, 复杂度是O(N):

function shuffle(array) {
  var m = array.length, t, i;
  while (m) {
    i = Math.floor(Math.random() * m--);
    console.log(m)
    t = array[m];
    array[m] = array[i];
    array[i] = t;
  }

  return array;
}

我们来说明一下这个函数的执行步骤:比如有一个数组 arr = [‘a’,’b’,’c’,’d’,’e’], 调用shuffle(arr):

shuffle(['a','b','c','d','e'])
随机数i的值 0 m的值为 4 数组值为 (5) ["e", "b", "c", "d", "a"]
随机数i的值 0 m的值为 3 数组值为 (5) ["d", "b", "c", "e", "a"]
随机数i的值 0 m的值为 2 数组值为 (5) ["c", "b", "d", "e", "a"]
随机数i的值 1 m的值为 1 数组值为 (5) ["c", "b", "d", "e", "a"]
随机数i的值 0 m的值为 0 数组值为 (5) ["c", "b", "d", "e", "a"]

注意这里 m 的值, 每次把数组前部任意一个位置和数组最后一个值交换位置。 这里的数组指的是每次交换完不包括最后一个值的数组。可能描述的不清楚,请看上面执行的例子。

python自带随机数组函数

from random import shuffle, sample

list = [1,2,3,4,5,6]

shuffle(list)  #将改变原list数组
sample(list, k=len(list))   #生成新的数组

flask decorators

Posted on 2018-08-13

今天我们不讲三国,我们好好讲一讲python的decorator(装饰器)
刚开始接触这个概念其实很容易迷惑,我也是从半懂不懂就开始使用,因为flask的例子中总会提到这个route装饰器

@app.route('/')
def index():
    return render_template('index.html')

那么,到底什么是decorator呢? 其实decorator就是一个函数,这个函数接受另外一个函数作为参数,@decorate这种写法这是python提供的语法糖,看下面的例子

@decorate
def target():
    print('running target')

这段代码的意思跟下面这段完全一样

def target():
    print('running target')

target = decorate(target)

但是请记住,这两段代码最后结果中的target已经不是原来的target函数的引用了,而是decorate(target)的引用。可能比较拗口,但是一定要理解。

这里有一个问题,非常重要,就是python什么时候执行decorators

来,下面我们通过代码片段abc.py来看

registry = []
def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func

@register
def f1():
    print('running f1')

@register
def f2():
    print('running f2')

def main():
    print('running main')
    print('registry ->', resigtry)
    f1()
    f2()

if __name__ == '__main__'
    main()

保存以上代码,并运行。

python3 abc.py

你可以看到结果是

running register f1
running register f2
running main
registry -> [f1,f2]
running f1
running f2

你可能已经猜到这里面的关键了。

在模块被载入的时候,装饰器就已经执行了。but! 注意这里的but! 被装饰的函数,比如f1,f2是要等到显式的调用才会执行。这里是装饰器的关键。
写到这里,是不是可以大概知道flask中装饰器是怎么起作用的?

@app.route('/')
def index():
    return render_template('index.html')

我们来看一下flask中route装饰器的源码

def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

我们来分析一下这个函数。

因为app.route需要接受参数,路由规则是必须的。所以

def route(self, rule, **option)

其实是一个函数工厂,真正的decorator从内部的decorator()函数开始

decorator(f)函数内部有一个函数 add_url_rule

这段代码

@app.route('/')
def index():
    pass

和下面这段代码表达的意思是一样的。

def index():
    pass
app.add_url_rule('/', 'index', index)

也就是decorator(f)内部做的事情,在函数内部调用add_url_rule()

装饰器真的是一个很方便的功能。

End

flask1.0.2 factory pattern with celery4.2

Posted on 2018-08-10

celery之前的版本和flask工厂模式一起使用很麻烦。因为celery没有提供init_app()这样的方法,celery4.2之前的版本用法参照这个博客Celery and the Flask Application Factory Pattern写的可以说是很详细了。
celery 4.2 有个很大的变化,就是实例化的时候可以不提供broker参数。

celery = Celery(__name__)

先这样就行了。虽然这个时候创建的实例还是有个默认的broker.但是配置里面可以重新覆盖…。
然后在create_app的方法里面配置:

import celeryconfig

def create_app(config_name):
    celery.config_from_object(celeryconfig)

celery4.2推荐所有配置项小写。所以celery的配置文件单独开来,不跟flask的配置放一起,新建一个文件叫celeryconfig.py

## Broker settings.
broker_url = 'redis://localhost:6379'

一般celery的任务都要用到flask运行时环境。所以create_app()里面还要加以下代码

class ContextTask(celery.Task):
    def __call__(self, *args, **kwargs):
        with app.app_context():
            return self.run(*args, **kwargs)
celery.Task = ContextTask

这段代码的主要目的是通过重写celery.Task给celery加上flask app_context()
最后在app.py文件里面import celery

import os
from app import create_app, db, celery

app = create_app(os.getenv('CONFIG') or 'default')

通过以下命令就可以启动celery任务啦

celery -A app.celery worker -l info

javascript数组中的各种循环和返回值

Posted on 2016-03-31

给自己总结一下,循环比较多容易弄混了。

forEach

Array.forEach(callback)

callback参数(value,index,array)

没有返回值,对数组的所有元素执行一遍callback

for of

for (variable of iterable) {
  statement
}

没有返回值,对可循环的对象(包括Array,Map,Set,String,arguments)执行statement

MDN

map

数组的map函数返回一个新的数组

arr.map(callback[, thisArg])

callback有三个参数 (value, index ,array)

every

数组的every函数测试是否所有的元素都能通过provided function的测试

arr.every(callback[, thisArg])

callback参数 (value index array)

thisArg

Optional. Value to use as this when executing callback.

function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough);   // false
[12, 54, 18, 130, 44].every(isBigEnough); // true

返回值是boolean

数组推导(array comprehension)

var a1 = [1, 2, 3, 4];
var a2 = [for (i of a1) i * 2];

a2 // [2, 4, 6, 8]

允许直接通过现有数组生成新数组。

left-pad和按位操作符

Posted on 2016-03-25

这两天Npm leftpad的新闻真是挺火的,详情点这里了解,来龙去脉就不讲了,跟本文也没有太大的关系。这篇文章主要想说一说我们很少用到的位操作符,然而位操作符跟这件事情有什么关系呢?

这要从leftpad的用处说起。leftpad这个模块起一个很简单的作用,从左边补位。看示例代码

leftpad('foo', 5)
// => "  foo" 

leftpad('foobar', 6)
// => "foobar" 

leftpad(1, 2, 0)
// => "01" 

参数说明

第一个参数是字符串,
第二个参数是字符串的长度,
第三个参数是补位的字符串默认是空字符串。

实现

function leftpad(str,len,ch){
    if(!ch && ch!==0){
        ch = ' '
    }

    var count = len - str.length;

    if(count<0) return str;

    var rpt = '';

    for(;;){
        //count为奇数的时候执行,其实只要count为1执行就可以了,这样写我认为是为了速度更快
        if(count & 1) rpt += ch ;

        //无符号右移。代码的core所在
        count >>>= 1;

        if(count == 0) break;
        ch += ch
    }

    return rpt + str;
}

注意for循环里面的代码。因为位操作符是比较少用,正好复习一下。
for循环里并不关心count的值是几,而是每次循环按位向右移动,只要判断值是否等于0,也就是右移到边界(二进制里面所有的1都移走了)。
按位操作符为什么比较快,因为位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

tips

按位操作可以参考MDN上的文档)

Javascript deep copy and shallow copy

Posted on 2016-03-15

js中深复制和浅复制

今天忽然想到了这个问题,总结一下js中有哪些复制对象的方法。先得明白什么是深复制,什么是浅复制,举个栗子:

var arr = [[1,2],3,4];
var brr = arr.slice();

//这时候brr是arr的浅复制
console.log(brr);

为啥呢?再试试这样:

brr[0][1] = 5;
console.log(arr)  // result is [[1,5],3,4];

因为arr[0]是一个数组,而brr[0]只是引用了这个数组;
所以深复制也就很好理解了,当改变任何brr的值都不会影响arr,那么该怎么实现呢?实现的方法有好几种,下面一一列举。

1.最熟悉的方法可能就是jQuery中的方法 $.extend(),看看jQuery官方的例子:

//浅复制
var object1 = {
  apple: 0,
  banana: { weight: 52, price: 100 },
  cherry: 97
};
var object2 = {
  banana: { price: 200 },
  durian: 100
};

// Merge object2 into object1
$.extend( object1, object2 );
//result is {"apple":0,"banana":{"price":200},"cherry":97,"durian":100}


//深复制
$.extend( true, object1, object2 );
//result is {"apple":0,"banana":{"weight":52,"price":200},"cherry":97,"durian":100}

深复制会递归的复制


2.原生的js实现

function clone(obj){
    //这一行很重要,判断obj是否基本类型 null,undefined,string,number,boolean,如果是直接返回,因为没法再继续循环了。
    if(obj == null || typeof obj !=='object') return obj

    if(obj instanceof Array){
        var arr = [];

        for(var i=0, len=obj.length; i<len; i++){
            //这一步就是深度复制的magic所在了
            arr[i] = clone(obj[i]);
        }

        return arr;
    }

    if(obj instanceof Object){
        //实例化obj的构造函数,得到和obj一样的prototype.
        var tem_obj = obj.constructor(); 

        for(attr in obj){
            if(obj.hasOwnProperty(attr)){
                //把obj实例的自有属性复制到temobj
                tem_obj[attr] = clone(obj[attr]);
            }
        }

        return tem_obj
    }
}

var arr = [[1,2],3,4];
var brr = clone(arr);
brr[0][1] = 5;
console.log(arr)  //[[1,2],3,4]

that’s it.


3.es6中的Object.assign,实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
详细的参考这里

Object.assign(target, source1, source2);

var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

JS算法题 生小羊

Posted on 2016-03-13

面试的时候碰到的问题:

农场里面买了1只小羊,这只小羊第二年和第四年都会生一只小羊,第五年会死掉。生下来的小羊会继续生小羊,问若干年都农场共有多少羊?

这道题的解题思路有两个。
先说第一个,使用递归,想想阶乘是怎么实现的?

function sheepNum(year){
    var num = 1;
    for(var i=1; i<=year; i++){
        if(i==2){
            num += sheepNum(year - 2);
        }else if(i==4){
            num += sheepNum(year - 4);
        }else if(i==5){
            num--;
        }
    }
    return num;
}

sheepNum(10)

说说我的解题思路,把复杂度降低。

//这是农场
var farm = [];

//构造一只羊,羊有3个方法,成长,生育,死亡
function Sheep() {
  this.age = 0;
}

Sheep.prototype.grow = function(index,years){
  for(var i=1; i<=years; i++){
    //每年都会长1岁
    this.age++;

    //小羊2岁4岁都会生一只新的小羊    
    if(this.age == 2 || this.age == 4){
      //注意这里的参数
      this.bear(new Sheep(),years-i)
    }

    if(this.age == 5){
      //5岁死亡
      this.die(index);
      break;
    }
  }
}

Sheep.prototype.bear = function(childSheep,years){
  //生羊的时候把新生的羊也放进农场。
  var childIndex = farm.push(childSheep) - 1;

  //小羊继续长大,参数childIndex:羊在农场中的编号,years:此羊离N年还能长几年
  childSheep.grow(childIndex,years)
}

Sheep.prototype.die = function(index){
  //小羊死后移出农场
  farm.splice(index,1);
}

这样调用就可以了

var sheep = new Sheep();
//羊在羊圈中的位置
var index = farm.push(sheep) - 1;
//N年以后的情况
var years = 40;
//从第一只羊自己成长就不用管它了。
sheep.grow(index,years);

//统计农场里面一共几只羊。
console.log('total:'+farm.length)

当然你也可以把农场设成number,生小羊number+1,小羊死number-1。
这样是不是比递归好理解一点呢?

Javascript中的发布订阅模式

Posted on 2016-03-12

Javascript中的Publish/Subscribe模式是一种比较经典的模式。消息的发送者叫做publisher,
publisher不关心是谁接收了信息,也不用知道是谁接收了消息,只要把信息发出去就可以了。消息的订阅者叫做subscriber,跟publisher类似,subsriber也只关心特定的频道,只接收相关频道的消息,对谁是publisher一无所知。

核心的代码如下:

//第一步创建构造函数
function PubSub() {
    this.topics = {};
}

//publish方法,可以接受两个参数,第一个参数是发布的频道,第二个是传给listener的参数
PubSub.prototype.publish = function(topic,info){
  if(!this.topics.hasOwnProperty(topic))  return;

  this.topics[topic].forEach(function(listener){
    listener(info == null ? {} : info);
  })
}

//subscribe方法接受两个参数,订阅的频道,和listener函数
PubSub.prototype.subscribe = function(topic,listener){
  if(!this.topics.hasOwnProperty(topic)) {
    this.topics[topic] = [];
  }

  if(typeof listener != 'function'){
    alert('无效的listener,必须是一个函数');
    return
  }

  var index = this.topics[topic].push(listener) - 1;

  //注意返回值是一个对象,提供了remove函数,可以用来删除订阅者
  var topics = this.topics;
  return {
    remove: function(){
      topics[topic].splice(index,1);
    }
  }
}

调用方法:

var pubsub = new PubSub();
var sub = pubsub.subscribe('haha',function(info){
  alert('订阅了haha'+info.hello)
})
pubsub.publish('haha',{hello:'world'})

//移除这个订阅者
sub.remove()
12
loethen

loethen

frontend javascript 前端 js cat

18 posts
6 tags
© 2019 loethen
Powered by Hexo
|
Theme — NexT.Muse v5.1.4