学海无涯

慎用 Python list 乘法

误用 list 乘法

今天刷 LeetCode 碰到一个水题转置矩阵, 这不就是先生成个空的倒置矩阵再填结果嘛,没多想就用 list 乘法上手就写。

class Solution:
    def transpose(self, A):
        """
        :type A: List[List[int]]
        :rtype: List[List[int]]
        """
        x, y = len(A), len(A[0])
        
        buff =[[0] * x] * y
        for i in range(x):
            for j in range(y):
                buff[j][i] = A[i][j]
        return buff

看似结果非常正确,但是样例输出了一个很奇怪的结果。

我的输入:
	[[1,2,3],[4,5,6]]

我的答案:
	[[3,6],[3,6],[3,6]]

标准答案
	[[1,4],[2,5],[3,6]]

赶紧在第12行前加上print(buff)一看

我的输入:
	[[1,2,3],[4,5,6]]

标准输出
	[[0, 0], [0, 0], [0, 0]]
	[[1, 0], [1, 0], [1, 0]]
	[[2, 0], [2, 0], [2, 0]]
	[[3, 0], [3, 0], [3, 0]]
	[[3, 4], [3, 4], [3, 4]]
	[[3, 5], [3, 5], [3, 5]]

我的猜测与验证

发现果然列表里的每一个子元素都相等了,猜测可能是 只复制了值的引用,而不是新建了一个对象,接下来就是验证。

Python 单链表实现&基础函数

前两天面滴滴,被问到怎么判断两个链表是否相交,然后并不懂什么是单链表相交…就很尴尬。 赶紧复习一下单链表的知识。

单链表实现

class LNode:
	def __init__(self, elem, next_ = None):
		self.elem = elem
		self.next = next_

class LList:
	def __init__(self):
		self._head = None

	def is_empty(self):
		return self._head is None

	# 前端插入
	def prepend(self, elem):
		self._head = LNode(elem, self._head)

	# 删除头结点并返回
	def pop_first(self):
		if self.is_empty():
			raise LinkedListUnderFlow("in pop")
		e = self._head.elem
		self._head = self._head.next
		return e

	def append(self, elem):
		if self.is_empty():
			self._head = LNode(elem)
			return
		p = self._head
		while p.next:
			p = p.next
		p.next = LNode(elem)

	def pop(self):
		if self.is_empty():
			raise LinkedListUnderFlow("in pop")
		p = self._head
		if p.next is None:
			e = p.elem
			self._head = None
			return e

		# 用p.next.next做条件是因为把最后一个结点删除,需要找到倒数第二个结点
		while p.next.next is not None:
			p = p.next
		e = p.next.elem
		p.next = None
		return e

	def find(self, pred):
		p = self._head
		while p.next is not None:
			if pred(p.elem):
				# 构建生成器,找到了一个元素可以继续找下一个
				yield p.elem
			p = p.next


	def printall(self):
		p = self._head
		while p is not None:
			print(p.elem, end="")
			if p.next is not None:
				print(", ", end="")
			p = p.next

		# 换行,因为默认end参数为 "\n"
		# 等价 print("\n", end="")
		print("")

if __name__ == '__main__':
	my_list = LList()
	for i in range(10, 0, -1):
		my_list.prepend(i)
	my_list.printall()    # 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
	for i in range(11, 21):
		my_list.append(i)
	my_list.printall()
	print(my_list.pop())  # 20
	print(my_list.pop_first()) # 1
	my_list.printall()   # 2~19

	def find_odd(n):
		if n % 2 != 0:
			return n
	for i in my_list.find(find_odd):
		print(i)         # 1, 3, 5, 7, 9 ,...

补充一下print的参数

  • print(value, …, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False)
  • sep: 多个参数之间的分隔字符串
  • end: print结束后的字符串
  • file: 输出到已打开的文件,注意,当文件关闭后才会保存
  • flush: 所有数据打印到控制台,立即“刷新”到实际控制台并保留待处理的打印缓冲区 可用于上面的文件操作,当文件未关闭时及时输出到控制台,参考Stack Overflow

Readfree 自动签到 & crontab自动任务踩坑

自动签到 Python 脚本

这部分没什么难度,主要是这个网站的cookiesMax-Age31449600秒,大概1年的寿命,所以直接将存好的cookiesrequests发一个get请求到验证地址就行。稍微修改了博主杨英明代码如下:

import requests
import time

# 登录验证地址
check_url = 'http://readfree.me/accounts/checkin'

# 记录程序运行时的时间
fp = open('./auto_signon_readfree.log','a')
ISOTIMEFORMAT='%Y-%m-%d %X'
curtime = time.strftime(ISOTIMEFORMAT, time.localtime(time.time()))
print ('at %s'%curtime)
fp.write('at %s\n'%curtime)

# 准备cookie
print ('准备cookie中……')
fp.write('准备cookie中……\n')
cookie_str = 'cookies here'
cookie = {}
for line in cookie_str.split(';'):
    name,value=line.strip().split('=',1)
    cookie[name]=value
print (cookie)
fp.write('%s\n'%cookie)

# 使用cookie访问网站
print ('签到中……')
fp.write('签到中……\n')
res = requests.get(check_url,cookies=cookie)
print (res)
fp.write('%s\n\n'%res)

crontab 自动任务

这一步确实是踩了不少坑,还是Linux知识太欠缺了。

几次失败

  1. 直接在crontab -e中加入指令0 1 * * * python3 ~/autoSign/autoSign_readfree/py不执行
  2. 更换python3绝对路径后依旧不执行
  3. 查看crontablog发现文件不存在
  4. .py文件头部添加#!/usr/bin/env python3报错env: python\r: No such file or directory

几次尝试

  1. 1-2 几次修改后依旧无果,在尝试2的后依旧不执行,考虑用新自动任务输出hello到log检测crontab是否出错,发现crontab能正常运行,随后考虑修复3问题

requests 代理问题

在给一个本地的Flask项目测试post接口时遇到一个问题,无论用requests的get还是post请求localhost全部都会超时。

经过仔细分析后,发现是开了系统代理的锅(毕竟写代码少不了Google),然而直接关闭系统代理仍然超时。

最后的解决方案如下:

import os
import requests

os.environ['NO_PROXY'] = '127.0.0.1'
r = requests.get('http://127.0.0.1:5000')
print(r.content)

设置不走代理的url,而不是直接把请求的proxies设置为本地代理!

参考:

stackoverflow: requests-how-to-disable-bypass-proxy

stackoverflow: python-requests-return-504-in-localhost

github: Issues with HTTP proxy and accessing localhost - does requests ignore no_proxy?

Python 元组解包的几种方法

访问下标解包

这其实都不算解包了吧。。

>>> a = (1,2,3)
>>> a[0]
1

赋值解包

>>> a = (1,2,3,)
>>> b, c, d = a

星号(*)解包

要将一个tuple中的所有值作为参数,如果直接用上面两种方法就不太 pythonic了,可以用以下方法解包

...
>>> brith = (2018, 7, 5,)
>>> datetime.date(brith)     # 当然这里直接传入元组是不行的,该函数要求传入int类型

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: an integer is required (got type tuple)

所以正确的应该是

>>> import datetime
>>> brith = (2018, 7, 5,)
>>> print(datetime.date(*brith))  # 注意*号
2018-7-5

来源: what-is-the-pythonic-way-to-unpack-tuples

Django 部署(Apache)

安装

sudo apt-get install python2.7.12     // 安装对应版本python
sudo apt-get install apache2          // 安装apache
sudo apt-get install libapache2-mod-wsgi  // 安装apache的wsgi组件
sudo apt-get install pip              // 安装python包管理

如果项目内有requrements.txt文件,进行如下操作安装项目依赖

pip install -r requrements.txt

配置 Apache

首先将django项目放入/var/www/目录下,然后修改/etc/apache2/site-enabled/000-default.conf/文件

<VirtualHost *:80>

...略...

	# 配置python环境的路径,此处已系统环境为例

	WSGIDaemonProcess Score python-path=/var/www/Score:/usr/bin/python/lib/python2.7/site-packages
	WSGIProcessGroup Score
	WSGIScriptAlias / /var/www/Score/Score/wsgi.py

	# 配置django静态文件目录

	Alias /static/ /var/www/Score/ScoreQuery/static/
	<Directory /var/www/Score/ScoreQuery/static/>
		Require all granted
	</Directory>
</VirtualHost>

收集 Django 静态文件

进入manage.py目录输入

将有序数组转换为二叉搜索树

几个简单二叉树的 Python 实现

二叉树的基本实现

 class TreeNode:
     def __init__(self, x):
         self.val = x
         self.left = None
         self.right = None

617.合并二叉树

合并二叉树

class Solution:
    def mergeTrees(self, t1, t2):
        """
        :type t1: TreeNode
        :type t2: TreeNode
        :rtype: TreeNode
        """
        if not t1:
            return t2
        if not t2:
            return t1
        t1.val += t2.val
        t1.left = self.mergeTrees(t1.left, t2.left)
        t1.right = self.mergeTrees(t1.right, t2.right)
        return t1     

104.二叉树的最大深度

二叉树的最大深度

class Solution:
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if root == None:
            return 0
        ldepth = Solution.maxDepth(self, root.left)
        rdepth = Solution.maxDepth(self, root.right)
        return max(ldepth, rdepth) + 1

108.将有序数组转换为二叉搜索树

将有序数组转换为二叉搜索树

转换规则

Python 蛋疼的编码问题

Python 的编码问题早就困扰我太久了, 但一直没有看到比较通俗易懂的专门介绍 Python 编码问题的文章。 正好今天刷知乎看到了非常不错的文章, 这里稍微抛砖引玉归纳下。

Unicode 和 UTF-8

知识储备:

  • ASCII 占1个字节,只支持英文
  • GBK GB2312的升级版,支持21000+汉字
  • Shift-JIS 日本字符
  • ks_c_5601-1987 韩国编码
  • TIS-620 泰国编码

由于每个国家都有自己的字符,所以其对应关系也涵盖了自己国家的字符,但是以上编码都存在局限性,即:仅涵盖本国字符,无其他国家字符的对应关系。 应运而生出现了万国码(Unicode),他涵盖了全球所有的文字和二进制的对应关系。

Unicode解决了字符和二进制的对应关系,但是使用unicode表示一个字符,太浪费空间。例如:利用unicode表示“Python”需要12个字节才能表示,比原来ASCII表示增加了1倍。

由于计算机的内存比较大,并且字符串在内容中表示时也不会特别大,所以内容可以使用unicode来处理,但是存储和网络传输时一般数据都会非常多,那么增加1倍将是无法容忍的!!!

为了解决存储和网络传输的问题,出现了Unicode Transformation Format,学术名UTF,即:对unicode中的进行转换,以便于在存储和网络传输时可以节省空间!

  • UTF-8: 使用1、2、3、4个字节表示所有字符;优先使用1个字符、无法满足则使增加一个字节,最多4个字节。英文占1个字节、欧洲语系占2个、东亚占3个,其它及特殊字符占4个
  • UTF-16: 使用2、4个字节表示所有字符;优先使用2个字节,否则使用4个字节表示。
  • UTF-32: 使用4个字节表示所有字符;

总结: UTF 是为unicode编码设计的一种在存储和传输时节省空间的编码方案。

编码的转换

对于历史遗留的以 GBK 编码编写的程序,我们可以不将其重新编码成 UTF-8 让其在没有安装 GBK 编码的终端上不乱码。 因为 Unicode 包含它与所有国家编码的映射关系, 所有只需要把数据从硬盘读到内存里, 转成 Unicode 来显示就可以了。 由于所有的系统、编程语言都默认支持unicode,那你的gbk软件放到美国电脑 上,加载到内存里,变成了unicode,中文就可以正常展示啦。

Python3 的执行过程

在看实际代码的例子前,我们来聊聊,python3 执行代码的过程

  1. 解释器找到代码文件,把代码字符串按文件头定义的编码加载到内存,转成unicode
  2. 把代码字符串按照语法规则进行解释,
  3. 所有的变量字符都会以unicode编码声明

编码转换过程

在 py2 和 py3 下分别运行下面这段程序

# coding: utf-8
s = '你好'
print(s)

Python3:

Python 查漏补缺之字符串

count 方法

str.count(sub, start= 0,end=len(string))

参数

  • sub – 搜索的子字符串
  • start – 字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为0。
  • end – 字符串中结束搜索的位置。字符中第一个字符的索引为 0。默认为字符串的最后一个位置。

返回值

该方法返回子字符串在字符串中出现的次数。

LeetCode 相关题目 461. 汉明距离

最优解法

class Solution:
    def hammingDistance(self, x, y):

        return(bin(x^y).count('1'))

python中进制互换

十进制转二进制

bin() 返回一个整数 int 或者长整数 long int 的二进制表示。

>>>bin(10)
'0b1010'
>>> bin(20)
'0b10100'

二进制转十进制

>>>int('0b1010', 2)
'10'
>>>int('0b10100', 2)
'20'

LeetCode 相关题目 476. 数字的补数

给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。

注意:

  1. 给定的整数保证在32位带符号整数的范围内。
  2. 你可以假定二进制数不包含前导零位。

示例1:

输入: 5 输出: 2 解释: 5的二进制表示为101(没有前导零位),其补数为010。所以你需要输出2。

示例2:

输入: 1 输出: 0 解释: 1的二进制表示为1(没有前导零位),其补数为0。所以你需要输出0。

我的解法(83.7%):

暴力解法,把数字转换成二进制字符串去掉0b, 用遍历的方法取字符串补码, 再转回十进制数。

class Solution:
    def findComplement(self, num):
        """
        :type num: int
        :rtype: int
        """
        str_num = str(bin(num))[2:]
        anti_num = []
        for e, i in enumerate(str_num):
            if i == '0':
                anti_num.append("1")
            else:
                anti_num.append("0")
        anti_num.insert(0, "0b")
        anti_num = "".join(anti_num)
        return int(anti_num, 2)       

最优解:

class Solution:
    def findComplement(self, num): 
        """
        :type num: int
        :rtype: int
        """
        n = len(bin(num))-2
        return num^(2**n-1)

思路是,取出去除0b的二进制数‘长度’ n, 通过n求出num‘位数’全置1的十进制数, 在与num亦或即可得到补数。(补数的性质啊喂。。。这都没想出来)