Leetao's Blog

Talk is cheap, show me the code

0%

前言

对于 max 和 min 可以比较数值大小这一点大家肯定都是十分熟悉,除此之外 max 和 min 同样也可以比较 list 和 str 大小.

文档说明

首先看一下 max 和 min 的说明文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
max(...)
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value

With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.

min(...)
min(iterable, *[, default=obj, key=func]) -> value
min(arg1, arg2, *args, *[, key=func]) -> value

With a single iterable argument, return its smallest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the smallest argument.

从上面的文档不难看出,max 和 min 对 iterable 对象的支持,那么对 List 是如何比较的呢?看个例子

1
2
3
4
5

list1, list2 = ['123', 'xyz', 'zara', 'abc'], [456, 700, 200]

print("Max value element : ", max(list1))
print("Max value element : ", max(list2))

输出结果如下:

1
2
Max value element :  zara
Max value element : 700

对于 List[int], max 的比较方式很容易发现, 直接比较列表中最大的那个数字, 那么对于 List[str] 的比较方式呢? c 语言中有一个 strcmp 的函数, max 的比较方式和它的方式类似, 两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符,返回值大的字符,min 比较方式也类似,只是返回值小的字符

彩蛋

之前在 Leetcode 看到一个题目,算是对 max,min 比较灵活的运用了,
最长公共前缀
,题目如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""。

示例 1:

输入: ["flower","flow","flight"]
输出: "fl"
示例 2:

输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:

所有输入只包含小写字母 a-z 。

直接上答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def longestCommonPrefix(self, strs: 'List[str]') -> 'str':
if not strs:
return ''

s1 = min(strs)
s2 = max(strs)
ans = ""

for i, v in enumerate(s1):
if v != s2[i]:
return s1[:i]

return s1

前言

最近写 Spring Boot 中 module 发现使用 Autowired 注入失败.

解决办法

在需要测试的类上添加 ContextConfiguration(classes = ClassToAutowire.class) 注解

1
2
3
4
5
6
7
8
9
10
11
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ExampleSerivceImpl.class)
public class TestExampleSerivceImpl {
@Autowired
private ExampleService exampleService;

@Test
public void TestExample() {
//
}
}

我们常用的 SpringBootTest 是用在集成测试当中的声明

前言

关于 Python 对文件的读写操作网上有很多,基本上无外乎都是基于打开文件的 mode 的解释

昨天我媳妇有这样一个需求,读入一个文件如果文件不存在则创建,然后写入,如果文件存在,则读取上次的文件内容,然后添加上这次的内容,是不是很简单,直接用**a或者a+**就可以了. 别着急,重点来了,文件内容是json数据,追加之后也应该是一个json数据

说的可能比较抽象,那就举个例子

例子

两次写入文件内容,分别为 {‘1’:’a’}{‘2’:’b’},那么第一次写入,文件不存在,创建并写入,最终结果应该如下:

1
2
3
{
'1':'a'
}

第二次,文件存在,读取数据,合并数据重写写入,结果如下:

1
2
3
4
{
'1':'a',
'2':'b'
}

很显然简单的使用 a 或者 a+ 模式是做不到上述要求的,有兴趣的可以自己尝试一下,它们只是简单的追加内容到上次文件末尾

实践

针对上述例子的要求,最容易想到的方案就是:
1.先以 r 的模式,打开文件读取文件数据,得到 dictA
2.然后以 w 的模式,打开文件,将本次数据 dictB 与 dictA 合并后写入 dictA

首先肯定这种方案没有任何问题,能够解决问题,不过这显然不是最佳方案,因为多次打开文件,增加了 IO 的开销,那有没有只打开一次文件完成上述要求呢?显然是有的,再说解决方案之前,我们先说一下预备知识:为什么 a 模式下可以追加内容到文件末尾? 因为在 a 模式下,打开文件后,文件指针默认指向文件末尾.ok了,新的方案就这么出来了.

新的方案基于文件指针,我们以 a 或者 a+ 模式打开文件,将文件指针移动到文件开头,读取文件内容,然后合并数据,然后清除文件内容,重新写入合并后的数据.最后看一下代码

1
2
3
4
5
6
7
8
9
10
11
import json
import os
new_dict = {'c':'d'}
old_dict = {}
with open('test.json','a+') as f:
if os.path.getsize('test.json'):
f.seek(0) # 重点,移动指针
old_dict = json.load(f)
merge_dict = {**old_dict,**new_dict}
f.truncate(0) # 清空之前内容
json.dump(merge_dict,f)

前言

Docker for Windows/Mac 上的 Kubernetes 启动时需要下载镜像,部分镜像国内无法访问,会出现 Kubernetes 一直 starting 的情况

解决办法

Github 上有一个 k8s-for-docker-desktop 的项目,可以解决 18.09/18.06 版本(包含 Kubernetes 1.10.3),18.03 以及 Docker for Mac/Windows 18.09.1 (包含 Kubernetes 1.13.0) 启动问题,不过最新版本的 Docker for Mac/Windows(v2.0.0.2),如下图:

同样没有办法启动,针对这个我提交了一个 PR, 也可以参考我 Fork 之后的新建的分支:v2.0.0.2 按照说明安装启动 Kubernetes

一个爱折腾的90后,喜欢写有趣的代码,做有趣的事

喜欢的编程语言

Python🐍、Golang🐭 、Rust🦀️

兴趣爱好:

跑步、游泳和读书

最近在做的事情:

学习日语

最近在追的动漫:

咒术回战、关于我转生成为史莱姆的那件事、关于我转生变成史莱姆这档事 第二季、世界最强暗杀者转生成异世界贵族

关注我

友链

前言

前端时间部署自己的博客,使用到了 Nginx ,涉及到一些静态文件, 简单对比一下 nginx 中 alias 和 root 的区别.

alias

先看一下 alias 的语法:

1
2
3
Syntax:	alias path;
Default: —
Context: location

alias 的功能很简单,替换指定位置,举个简单的例子:

1
2
3
location /i/ {
alias /data/w3/images/;
}

当请求 /i/test.jpg 的时候,实际请求地址是 /data/w3/images/test.jpg, 注意: alias 最后面的 “/“ 不能省略.

root

看一下 root 的语法

1
2
3
4
Syntax:	root path;
Default:
root html;
Context: http, server, location, if in location

root 则是用来设置根目录的,同样看一个简单的例子:

1
2
3
location /i/ {
root /data/w3;
}

当请求 /i/test.jpg 的时候,实际请求地址是 /data/w3/i/test.jpg,文字可能看起来比较抽象,我们看一张图片,直观的感受一下:

参考链接

Nignx-alias

Nignx-root

Nginx虚拟目录alias和root目录

数据类型

在Rust中每个值都有自己确定的类型,接下来我们会介绍两类数据类型子集:标量(scalar)复合 (compound)

Rust是静态类型语言,也就意味着在编译期它必须知道所有变量的类型,正常情况下编译器可以根据变量的值推断出变量的类型,也存在没有办法推断的情况,这个时候我们就必须声明这个变量的类型。看一个简单的例子:

1
2
3
4
#![allow(unused_variables)]
fn main() {
let guess: u32 = "42".parse().expect("Not a number!");
}

这里我们将字符串42转换成数字的时候必须声明转化后的类型,否则的话编译的时候会出错。我们尝试把 u32 去掉编译,结果如下:

1
2
3
4
5
6
7
8
error[E0282]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^
| |
| cannot infer type for `_`
| consider giving `guess` a type

最上面我们说道,当编译器无法推断出变量的类型的时候就需要我们自己声明变量类型,这就说明 parse 这个函数返回类型不确定,那么 parse 返回的类型有哪些呢?看一下官方文档

parse can parse any type that implements the FromStr trait.

那么有哪些类型实现了 FromStr, 简单截部分结果:

说完了这些,让我们时候看一下两类数据类型子集了

标量类型

Rust 有四个基本的标量类型: 整型(integer),浮点型(floating-point),布尔型(Boolean),以及字符类型(character)

整型

对于整型,学过其他语言的肯定不会模型,也就是整数类型,说明其是没有小数部分的数字。那么 Rust 中的整型有哪些呢?

SignedUnsigned 分别代表着有符号无符号,即数字是否能为负值. 一个有符号的变体可以存储 -2^(n-1) 到 2^(n-1) - 1, n 代表字节长度,比如 i8的取值范围在 -2^7 到 2^7 - 1. 而无符号的变体可以储存从 0 到 2^n - 1 的数字,则 u8 的取值范围在 0 到 2^8 - 1.

最后说一说,isize 和 usize 类型,它取决于运行程序的计算机架构:64位的
则它们是64位的,32位的则是32位的,通常它们主要用来作为某些集合的索引.

除了常见的10进制,Rust 同样也支持 16进制,8进制,二进制以及 byte 类型(仅支持 u8),Rust 允许在数字后面使用类型后缀,eg: 57u8, 同样也允许使用 _ 作为分隔符,方便读数,eg: 1_000. 此外 Rust 默认的类型是 i32,通常情况下它的速度会是最快的.

关于整型,Rust 里面还有一个有趣的问题 – 整型溢出(Integer Overflow )

整型溢出 (Integer Overflow)

“整型溢出” 其实很简单,以 u8 为例,它的取值范围为 0-255,如果你把它变成了 256,(这里的变成是指通过算术运算:+,-,*,/ 等等)这就叫 “整型溢出”,对于这个 Rust 有一个有趣的规则:当在 debug 模式编译时,Rust 检查这类问题并使程序 panic(这个术语被 Rust 用来表明程序因错误而退出,在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。简而言之,256 变成 0,257 变成 1,依此类推。

让我们通过例子直观感受一下:

1
2
3
4
5
6
# 这个项目是通过 cargo 构建的
fn main() {
let x : u8 = 255;
let x: u8 = x + 1;
println!("{}",x);
}

看一下编译结果:

说完了整型,就得说一说 Rust 的浮点型了

浮点型

Rust 有两个原生的浮点类型:f32,f64,默认是 f64,因为两者速度相当,但是后者精度更高,采用 IEEE-754 标准表示。f32 是单精度浮点数,f64 是双精度浮点数。深入理解可以阅读下面的链接:

布尔型

和其他编程语言一样,Rust 的布尔类型也有两个值:true 和 false. 其中布尔类型用 bool 表示.

1
2
3
4
5
fn main() {
let t = true;

let f: bool = false;
}

字符类型

说完了数字,我们说一说 Rust 的字符, Rust 的 char 类型是语言中最原生的字母类型,唯一需要注意的是:char 由单引号指定,不同于字符串使用双引号,看一个具体的例子:

值得注意的是,Rust 的 char 类型代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容,拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。

变量

变量是所有语言中都不可或缺的重要部分,这一点同样适用 Rust. 但是与其他语言有所不同的是,在 Rust 中变量默认是不可改变的(immutable).

不可改变的就意味着,一旦一个变量赋值之后,我们就不能再次改变它的值了,如果尝试进行修改,则会产生错误.让我们通过一个列子直观的感受一下.

1
2
3
4
5
6
7
// example.rs
fn main() {
let x = 5;
println!("the value of x is {}",x);
x = 6;
println!("The value of x is: {}", x);
}

然后用 rustc example.rs 编译一下,编译结果如下:

问题来了,实际开发过程中,我们难免会需要对变量重新赋值,那么 Rust 有没有**可变的(mutable)**变量呢?自然是有的,用法很简单,在变量前添加一个关键字 mut 就可以了,让我们改造一下上面的例子感受一下

1
2
3
4
5
6
fn main() {
let mut x = 5;
println!("the value of x is {}",x);
x = 6;
println!("The value of x is: {}", x);
}

先用 rustc example.rs 会在当前目录下生成一个可执行文件,然后通过 ./example 的方式运行编译后的文件,在 windows 下则会生成一个 exe 文件,通过类似的方式运行,结果如下图:

说道这里,可能大家就会有一个困惑,默认不可变的变量和常量有什么区别呢?

默认不可变的变量和常量的区别

  1. 常量前面不能使用 mut 关键字
  2. 常量定义使用的关键字是 const,并且常量在定义的时候必须声明其类型,eg: const MAX_POINTS: u32 = 100_000
  3. 常量可以在任意地方声明,包括全局变量
  4. 常量只能是一个常数表达式,也就意味着函数调用的结果或者在运行时计算得出的值都不能设置为常量

隐藏(Shadowing)

在官方教程中 Rust 在变量中还提及到了一个概念-阴影(Shadowing),所说的内容很简单,就是你通过 let 重新声明一个和之前变量名一样的变量,你可以使用之前的变量的值,说起来很抽象,让我们看一下例子:

1
2
3
4
5
6
fn main() {
let x = 5;
println!("the value of x is {}",x);
let x = x + 6; // x 保留着上一个变量x的值 5
println!("The value of x is: {}", x);
}

然后编译运行一下结果:

最后再看一个关于 变量阴影(Variable shadowing),个人觉得其实就是变量的作用范围的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let x = 0;

{
let x = 1;
println!("Inner x: {}", x); // prints 1

}

println!("Outer x: {}", x); // prints 0

// Shadow
let x = "Rust";
println!("Outer x: {}", x); // prints 'Rust'
}

输出结果:

1
2
3
Inner x: 1
Outer x: 0
Outer x: Rust

前言

和其他语言一样 Rust 同样有 变量,函数,结构体等其他概念,因此也需要 标识符, 那么关于 Rust 的标识符命名规则和其他语言一样吗?

命名规则

  1. 和所有语言一样, Rust 同样不能使用关键字 (keyword) 不能作为标识符
  2. 当首字符是字母的时候:
    1. 余下的字符可以是字母或者下划线( _ )
  3. 当首字符是下划线 ( _ ) 时候:
    1. 标识符长度必须不能小于两字符
    2. 余下的字符可以是字母或者下划线( _ )

相较于其他语言, Rust 中并不允许数字作为标识符的一部分.除此之外, Rust 还有一个 Raw identifiers 的概念.

Raw identifier

有的时候你不得不使用 关键词(keyword),比如,当你需要调用一个 c 语言库,碰巧它有个函数名称叫做 match, 为了正常调用这个时候,我们就需要用到 raw identifier 了. 使用方法很简单,在函数的开头加上 r# 符号即可.

可以看一下官网给的例子:

1
2
3
let r#fn = "this variable is named 'fn' even though that's a keyword"; 
// call a function named 'match'
r#match();

参考链接

Identifiers

前言

不知道大家有没有注意到 python 有一个 __future__ 模块,如果大家有人看开源项目兼容 py2 和 py3 的话,它们的源代码中会经常出现这个模块,那么这个模块究竟有什么作用呢?

__future__

关于 __future__ 的作用,官方文档中也有明确提到,让我们看一下:

_future_ is a real module, and serves three purposes:

  1. To avoid confusing existing tools that analyze import statements and expect to find the modules they’re importing.
  2. To ensure that future statements run under releases prior to 2.1 at least yield runtime exceptions (the import of __future__ will fail, because there was no module of that name prior to 2.1).
  3. To document when incompatible changes were introduced, and when they will be — or were — made mandatory. This is a form of executable documentation, and can be inspected programmatically via importing __future__ and examining its contents.

翻译过来大致意思如下:

  1. 为了避免混淆现有工具,分析import语句和期望找到他们要导入的模块。
  2. 确保2.1之前的版本导入 __future__产生运行时异常,因为2.1之前没有这个模块
  3. 记录何时引入了不兼容的更改,以及何时将其强制执行。 这是一种可执行文档,可以通过导入 __future__进行可编程式检查。

看完的感觉是不是这样的?

如果是的话,就对了。别看官方文档上面写的很邪乎,其实总结起来很简单, python 在新版本会引入某些新的功能特性,但是有的时候有些改动是不兼容旧版本的,举个例子说,比如你的项目是用的是 py2.7,现在你需要迁移到 3.x 版本,直接升级到 3.x 肯定是不行的,那怎么办呢?这个时候就需要用到 _future_ 了,我们可以通过 _future_ 导入新版本某些模块,测试新版本的新功能,等测试成功后再升级到新的版本上.

例子

说完了,我们举个最典型的例子, print, 使用过 py2.7 和 py3.x 的小伙伴肯定都知道,在 3.x 的时候,print 成了一下函数,那么让我们在 py2.7 通过 __future__ 感受一下

除了 print,还有其他不少类似的,在官方文档中都有提及到,我这里就简单截个图,有兴趣的可以自己去官网研究一下

参考链接

__future__ — Future statement definitions

What is __future__ in Python used for and how/when to use it, and how it works