用Python写算法 | 蓄水池算法实现随机抽样

现在有一组数,不知道这组数的总量有多少,请描述一种算法能够在这组数据中随机抽取 k 个数,使得每个数被取出来的概率相等。 如果这组数有 n 个,那么每个数字取到的概率就是 k/n,但是这个问题的难点在于不知道这组数的总数,也就是不知道 n,那么该怎么计算每个数取到的概率呢? 蓄水池算法 游泳池(蓄水池)大家都不陌生,有些游泳池中的水是活的,有入水管也有出水管,那么和泳池体积相当的水流过之后,是不是泳池中所有的水都会被替换呢?当然不是,有的水在泳池中可能会存留很久,有的可能刚进去就流走了。仿照这种现象,蓄水池抽样算法诞生了,蓄水池算法的关键在于保证流入蓄水池的水和已经在池中的水以相同的概率留存在蓄水池中。并且蓄水池算法可以在不预先知道总量的情况下,在时间复杂度 O(N)的情况下,来解决这类采样问题。 核心原理 这一部分涉及公式,为了保证效果直接贴了图过来。 Python 实现 接下来尝试用 Python 实现一下蓄水池算法,由于蓄水池算法是在事先不知道总量的情况下抽样的,所以定义一个方法来接收单个元素,并且把这个方法放在类中,以持有采样后的数据。 import random class ReservoirSample(object): def __init__(self, size): self._size = size self._counter = 0 self._sample = [] def feed(self, item): self._counter += 1 # 第i个元素(i <= k),直接进入池中 if len(self._sample) < self._size: self._sample.append(item) return self._sample # 第i个元素(i > k),以k / i的概率进入池中 rand_int = random.randint(1, self._counter) if rand_int <= self._size: self._sample[rand_int - 1] = item return self._sample 测试代码 接下来实现一个测试用例验证实现的算法是否正确,既然是随机抽样,无法通过单词测试来验证是否正确,所以通过多次执行的方式来验证,比如从 1-10 里随机取样 3 个数,然后执行 10000 次取样,如果算法正确,最后结果中 1-10 被取样的次数应该是相同的,都是 3000 上下。 ...

八月 5, 2018 · 1 分钟 · Zhiya

使用gofmt格式化代码

对于一门编程语言来说,代码格式化是最容易引起争议的一个问题,不同的开发者可能会有不同的编码风格和习惯,但是如果所有开发者都能使用同一种格式来编写代码,开发者就可以将宝贵的时间专注在语言要解决的问题上。 gofmt 介绍 Golang 的开发团队制定了统一的官方代码风格,并且推出了 gofmt 工具(gofmt 或 go fmt)来帮助开发者格式化他们的代码到统一的风格。gofmt 是一个 cli 程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有.go 文件,如果不传参数,会格式化当前目录下的所有.go 文件。 gofmt 默认不对代码进行简化,使用-s 参数可以开启简化代码功能,具体来说会进行如下的转换: 去除数组、切片、Map 初始化时不必要的类型声明: 如下形式的切片表达式: []T{T{}, T{}} 将被简化为: []T{{}, {}} 去除数组切片操作时不必要的索引指定 如下形式的切片表达式: s[a:len(s)] 将被简化为: s[a:] 去除迭代时非必要的变量赋值 如下形式的迭代: for x, _ = range v {...} 将被简化为: for x = range v {...} 如下形式的迭代: for _ = range v {...} 将被简化为: for range v {...} gofmt 命令参数列表如下: usage: gofmt [flags] [path ...] -cpuprofile string write cpu profile to this file -d display diffs instead of rewriting files -e report all errors (not just the first 10 on different lines) -l list files whose formatting differs from gofmt's -r string rewrite rule (e.g., 'a[b:len(a)] -> a[b:]') -s simplify code -w write result to (source) file instead of stdout 可以看到,gofmt 命令还支持自定义的重写规则,使用-r 参数,按照 pattern -> replacement 的格式传入规则。 ...

七月 17, 2018 · 2 分钟 · Zhiya

搭建Kubernetes集群时DNS无法解析问题的处理过程

问题描述 在搭建 Kubernetes 集群过程中,安装了 kube-dns 插件后,运行一个 ubuntu 容器,发现容器内无法解析集群外域名,一开始可以解析集群内域名,一段时间后也无法解析集群内域名。 $ nslookup kubernetes.default Server: 10.99.0.2 Address 1: 10.99.0.2 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'kubernetes.default' 排查过程 在排查问题前,先思考一下 Kubernetes 集群中的 DNS 解析过程,在安装好 kube-dns 的集群中,普通 Pod 的 dnsPolicy 属性是默认值 ClusterFirst,也就是会指向集群内部的 DNS 服务器,kube-dns 负责解析集群内部的域名,kube-dns Pod 的 dnsPolicy 值是 Default,意思是从所在 Node 继承 DNS 服务器,对于无法解析的外部域名,kube-dns 会继续向集群外部的 dns 进行查询,过程如图。 Ubuntu 容器是一个普通的 Pod,在 Linux 系统中,/etc/resolv.conf 是存储 DNS 服务器的文件,普通 Pod 的/etc/resolv.conf 文件应该存储的是 kube-dns 的 Service IP。 nameserver 10.99.0.2 # 这里存储的是kube-dns的Service IP search default.svc.cluster.local. svc.cluster.local. cluster.local. options ndots:5 查看后发现/etc/resolv.conf 文件中存储的是 kube-dns 的 Service IP,证明这一步没有问题,接下来查看一下 kube-dns 的 Pod,先进入 kube-dns 的 Pod 中检查一下/etc/resolv.conf 文件,这里存储的应该是集群外部的 DNS 服务器地址,查看后发现,这里存储的地址是 127.0.0.53,进一步查看 kube-dns Pod 的 log,发现出现了非常多的 i/o timeout 错误。 ...

七月 15, 2018 · 3 分钟 · Zhiya

Golang环境安装和依赖管理

2015 年,Go 1.5 加入了一个试验性的 vendor 机制(到 2016 年的 Go 1.6 版变为默认开启),vendor 机制就是在项目中加入了 vendor 文件夹,用于存放依赖,这样就可以将不同项目的依赖隔离开。 Golang 一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Golang 提供了方便的安装包,支持 Windows、Linux、Mac 系统。 下载安装包 Golang 的官网是https://golang.org/,如果官网打不开,可以访问https://golang.google.cn/这个域名。在官网点击 Download Go 会进入下载页,可以看到这里提供了针对各个系统的安装包,也提供了源码,可以下载源码编译安装。 下载运行安装包后,在 terminal 中执行 go env 命令,如果出现下面的输出说明已经安装成功。 GOROOT 与 GOPATH 仔细看上面的输出,会发现其中有一个 GOPATH,又有一个 GOROOT,那么到底哪个才是 Golang 的运行环境呢。 首先访问一下 GOROOT 这个路径,会发现其中包含 bin、lib 等文件夹。GOROOT 就是 Golang 的安装路径,其中包含 Golang 编译、工具、标准库等,在安装后就会存在。 和 GOROOT 不同,GOPATH 是工作空间路径,从 go 1.8 开始,如果 GOPATH 没有被设置,会有一个默认值,在 Unix 上为$HOME/go,在 Windows 上为%USERPROFILE%/go,当调用 go build 时,它会在 GOPATH 中寻找源码。访问一下 GOPATH 这个路径,会发现其中只有 pkg、bin、src 三个文件夹,并且里面基本是空的,这是一个约定的目录结构,src 文件夹用来存放源码、pkg 存放编译后生成的文件,bin 存放编译后生成的可执行文件。项目代码需要在 GOPATH/src 路径下。 ...

七月 10, 2018 · 1 分钟 · Zhiya

你所不知道的Python | 函数参数的演进之路

函数参数处理机制是 Python 中一个非常重要的知识点,随着 Python 的演进,参数处理机制的灵活性和丰富性也在不断增加,使得我们不仅可以写出简化的代码,也能处理复杂的调用。 关键字参数 调用时指定参数的名称,且与函数声明时的参数名称一致。 关键字参数是 Python 函数中最基础也最常见的,我们写一个记账的函数,参数是需要记录的时间和金额。 def add_record(date, amount): print('date:', date, 'amount:', amount) 这里的 amount 参数就是一个关键字参数,关键字参数支持两种调用方式: 位置调用 关键字调用 位置调用,就是按参数的位置进行调用,例如传入两个参数,第一个是字符串 2018-07-06,第二个是整数 10,那么这两个参数会被分别赋予 date 和 amount 变量,如果顺序反过来,则这两个参数分别赋予 amount 和 date 变量。 add_record('2018-07-06', 10) # 输出date: 2018-07-06 amount: 10 add_record(10, '2018-07-06') # 输出date: 10 amount: 2018-07-06 关键字调用,可以忽略参数顺序,直接指定参数。 add_record(amount=10, date='2018-07-06') # 虽然参数顺序反了,但是使用了关键字调用,所以依然输出date: 2018-07-06 amount: 10 仅限关键字参数 我们定义一个 Person 类,并实现它的__init__方法 class Person(object): def __init__(self, name, age, gender, height, weight): self._name = name self._age = age self._gender = gender self._height = height self._weight = weight 当初始化这个类的时候,我们可以使用关键字调用,也可以使用位置调用。 ...

七月 10, 2018 · 2 分钟 · Zhiya