python粘性拓展_Python基础之:拓展解决问题的思路
0、錘子原理
在手里拿著一把錘子的人眼中,世界就像一根釘子。
大多人試圖以一種思維模型來解決問題,而其思維往往只來自某一專業(yè)學(xué)科,
但你必須知道各種重要學(xué)科的重要理論。
一一《窮查理寶典》
在過去十年的工作中,我經(jīng)常看到一些不可思議的代碼,這些代碼有時(shí)候看起來相當(dāng)?shù)挠薮馈6掖蟛糠謺r(shí)候,這些代碼都有非常簡單而高效的替代方案。而寫出這些代碼的人,往往是因?yàn)闆]有掌握相關(guān)的基礎(chǔ)知識(shí),或者是因?yàn)榭偸怯靡粋€(gè)思路去解決問題,形成了思維慣性。
要巧妙地解決某些問題,有時(shí)候可能需要掌握非常專業(yè)和生僻的知識(shí);但大部分時(shí)候,你只需要掌握一些非常基礎(chǔ)的知識(shí),和一個(gè)拓展性的思維。
本文皆在拋磚引玉,用非常基礎(chǔ)的Python知識(shí),用不同的思路巧妙地解決相似的問題。
1、判定元素是否存在
在列表在查找一個(gè)元素,判定元素是否存在,是一個(gè)相當(dāng)常見的操作。
在貫穿本小節(jié)的所有例子中,我們都是為了查找符合某個(gè)條件的元素是否存在。如果存在,則做dealWhenFound操作;如果不存在,則做dealWhenNotFound操作。后文中用到這兩個(gè)函數(shù),我們將直接使用,不再進(jìn)行聲明。
def dealWhenFound(elem):
# 如果元素找到了,做點(diǎn)什么
print("{}is found".format(elem))
def dealWhenNotFound(elem):
# 如果元素沒有找到,做點(diǎn)什么
print("{}is not found".format(elem))
假如我們有一個(gè)名字列表,現(xiàn)在需要在其中查找某個(gè)元素是否存在。通用我們可以這么做:
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby"]
is_found = False
target = "Tom"
is_found = False
for name in names:
if name == target:
is_found = True
break
if is_found:
dealWhenFound(target)
else;
dealWhenNotFound(target)
這是一份通用可行且非常樣板式的代碼。但在Python中,我們有更加高效且解決方案。正如本小節(jié)的標(biāo)題所述的,用in關(guān)鍵字就可以了。
if name in names:
dealWhenFound(target)
else:
dealWhenNotFound(target)
in操作符是用來判定一個(gè)元素是否存在一個(gè)可迭代對象中(list、tuple、dict、set等)。對于這種查找條件比較簡單的搜索,思路就是這么簡單,甚至不值得一提。但對于稍微復(fù)雜一點(diǎn)的查找條件,in就不那么勝任了。
我們把查找條件修改為:判定是否存在以某個(gè)字母開頭的名字。這個(gè)時(shí)候,我們就沒有辦法用in操作符來直接判定了。我們發(fā)現(xiàn)反而是第1份for代碼,才能更好地解決我們的問題。
prefix = "J"
is_found = False
for name in names:
if name.startswith(prefix): # 判定name是否以J開頭
is_found = True
break
if is_found:
dealWhenFound(prefix)
else;
dealWhenNotFound(prefix)
Python考慮到了這種情況的普遍性,為我們提供了for/else結(jié)構(gòu)。
for iter in a_list:
if some_test(iter):
break
else:
# 如果循環(huán)結(jié)果,且沒有break語句被執(zhí)行,則else塊會(huì)被執(zhí)行
在for/else結(jié)構(gòu)中,如果for循環(huán)正常結(jié)束(即沒有break語句被執(zhí)行),則else下的代碼會(huì)被執(zhí)行;否則else下的代碼不會(huì)被執(zhí)行。
利用這個(gè)特性,我們可以將代碼進(jìn)行如下的優(yōu)化:
prefix = "J"
for name in names:
if name.startswith(prefix): # 判定name是否以J開頭
dealWhenFound(prefix)
break
else:
dealWhenNotFound(prefix)
如果你知道any函數(shù),那你應(yīng)該知道這份代碼還會(huì)優(yōu)化的空間。any函數(shù)接受一個(gè)可迭代對象(包括生成器)做為參數(shù),并且只要任意一個(gè)元素被判定為True,則返回True。配合map,我們的代碼可以進(jìn)一步地簡化:
prefix = "J"
if any(map(lambda name: name.startswith(prefix), names)):
dealWhenFound(prefix)
else:
dealWhenNotFound(prefix)
如果你覺得上面這份代碼不好理解,我們可以進(jìn)行拆解。
test_func = lambda name: name.startswith(prefix)
map_obj = map(test_func, names)
if any(map_obj):
dealWhenFound(prefix)
else:
dealWhenNotFound(prefix)
除了本小節(jié)用到的一些關(guān)鍵字和函數(shù)之外,Python也為我們提供了很多其它便利。在這里我們列舉一些比較常用的,但不再深入介紹用法。
in
any
all
for/else
map
reduce
filter
enumerate
zip
2、頻數(shù)統(tǒng)計(jì)
在實(shí)際開發(fā)過程,統(tǒng)計(jì)是另一個(gè)常見的需求。
還是以名字列表為例,將首字母相同的名字放在同一個(gè)分組(列表)里邊。我們很容易想到使用dict數(shù)據(jù)結(jié)構(gòu):用首字母做為key,以一個(gè)list對象做為value即可。
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = dict()
for name in names:
key = name[0]
if key in groups:
groups[key].append(name)
else:
g = [name]
groups[key] = g
使用dict的setdefault函數(shù),上面這段代碼可以進(jìn)行簡化:
for name in names:
key = name[0]
groups.setdefault(key, []).append(name)
d.setdefault(key, dft_val)的操作是,檢測key是否存在,如果存在則返回value;如果不存在,則將dft_val存儲(chǔ)到d[key],并返回dft_val。在上面的例子中, 我們在dict中存儲(chǔ)了list對象,所以我們可以通過鏈?zhǔn)秸{(diào)用,在一行代碼里完成比較復(fù)雜的操作。
到目前為止,一切都簡單到不值得一提。但如果我們把分組的需求改為,編者首字母相同的名字的個(gè)數(shù),那就是另一個(gè)情況了。這時(shí)候dict的value類型是int,我們不可以進(jìn)行簡單的鏈?zhǔn)讲僮?#xff0c;所以使用setdfault也就不存在優(yōu)勢了。一個(gè)比較直觀的實(shí)現(xiàn),還是對第一段代碼進(jìn)行簡單的改造:
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = dict()
for name in names:
key = name[0]
if key in groups:
groups[key] += 1
else:
groups[key] = 1
或者使用get函數(shù)來簡化代碼:
for name in names:
key = name[0]
val = groups.get(key, 0)
groups[key] = val + 1
對于集合類型的操作,Python提供了一個(gè)更加高效便捷的庫collections。利用collections,我們可以對代碼進(jìn)行進(jìn)一步的簡化:
import collections
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = collections.defaultdict(int)
for name in names:
groups[name[0]] += 1
collections.Counter類為我們提供了統(tǒng)計(jì)列表(可迭代對象)元素?cái)?shù)量的便利,配合列表解析表達(dá)式(list comprehension),我們可以用一行代碼就完成統(tǒng)計(jì)的操作。
import collections
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = collections.Counter(name[0] for name in names)
print(groups)
# 打印結(jié)果:
# Counter({'J': 3, 'T': 2, 'H': 2, 'A': 2, 'M': 1})
collections為我們提供了更容易使用的容器類型(如list、tuple、dict等)的子類及其它一些便利。本文只是拋磚引玉,并不打算深入介紹collections的用法。在閱讀本文之后,各位讀者可自行深入學(xué)習(xí)。以下兩個(gè)鏈接都來自Python官方文檔,第一個(gè)是英文鏈接,第二個(gè)是中文鏈接。8.3. collections - High-performance container datatypes - Python 2.7.18 documentation?docs.python.orghttps://docs.python.org/zh-cn/3/library/collections.html?docs.python.org
3、多次條件判定
我們經(jīng)常會(huì)遇到一種情況,在執(zhí)行特定操作之前,往往需要通過多次的條件判定。只有在所有的條件都滿足的情況下,才會(huì)進(jìn)行目標(biāo)操作。
有一個(gè)改名的需要求,只有當(dāng)名字滿足一系列的條件,才可以對名字進(jìn)行更改;否則提示改名失敗的原因。名字需要滿足的一系列條件是:
1、長度不得大于10
2、只包含26個(gè)英文字母
3、有且只有首字母大寫,其它字母都是小寫
4、最后一個(gè)字母必須是元音字母
我們先為各種判定結(jié)果定義一些常量,方便后面使用:
# Python沒有enum類型,我們可以通過class來模擬
class EAlterRet:
Succ = 0,
SizeOutOfRange = 1,
InvalidCharacter = 2,
NotCapitalized = 3,
EndWithConsonant = 4,
AlterNameErrors = (
"Succ", # 成功
"SizeOutOfRange", # 太長
"InvalidCharacter", # 非法字符
"NotCapitalized", # 非首字母大寫的
"EndWithConsonant", # 未以元音字母結(jié)尾
)
第一個(gè)實(shí)現(xiàn)方式,也就是最容易想到的實(shí)現(xiàn)方式,自然是多個(gè)if語句了。
import re
class Human:
def __init__(self, name):
self.name = name
def dealWithErrors(self, target, code):
ret = AlterNameErrors[code]
msg = "Alter name to '{}', result:{}".format(target, ret)
print(msg)
def alterName(self, target):
# 長度是否大于10
if len(target) > 10:
self.dealWithErrors(target, EAlterRet.SizeOutOfRange)
return
# 是否存在非法字符
if re.search(r'[^a-zA-z]', target):
self.dealWithErrors(target, EAlterRet.InvalidCharacter)
return
# 是否有且只有首字母大寫
if target.lower().capitalize() != target:
self.dealWithErrors(target, EAlterRet.NotCapitalized)
return
# 是否以無意結(jié)尾
if not re.search(r'[AEIOUaeiou]$', target):
self.dealWithErrors(target, EAlterRet.EndWithConsonant)
return
# 改名成功
self.name = target
第二種方式是使用類似于do/while(false)的結(jié)構(gòu)。由于Python沒有do/while(false)結(jié)構(gòu),我們可以使用一次for循環(huán)來替換。
import re
class Human:
def __init__(self, name):
self.name = name
def alterName(self, target):
error = EAlterRet.Succ
for i in range(0, 1):
if len(target) > 10:
error = EAlterRet.SizeOutOfRange
break
if re.search(r'[^a-zA-z]', target):
error = EAlterRet.InvalidCharacter
break
if target.lower().capitalize() != target:
error = EAlterRet.NotCapitalized
break
if not re.search(r'[AEIOUaeiou]$', target):
error = EAlterRet.EndWithConsonant
break
if error == EAlterRet.Succ: # 條件滿足,改名成功
self.name = target
else: # 條件不滿足,處理錯(cuò)誤
ret = AlterNameErrors[code]
msg = "Alter name to '{}', result:{}".format(target, ret)
print(msg)
使用for/break的好處是,我們可以把錯(cuò)誤放到后面統(tǒng)一處理,避免使用重復(fù)的錯(cuò)誤處理代碼。
第三種方式是得用異常。雖然我們這個(gè)例子引入異常有點(diǎn)牽強(qiáng),但舉一反三,各位讀者在以后的實(shí)際開發(fā)過程中,就可以多一個(gè)思路。
import re
class Human:
def __init__(self, name):
self.name = name
def alterName(self, target):
error = EAlterRet.Succ
try:
if len(target) > 10:
raise Exception(EAlterRet.SizeOutOfRange)
if re.search(r'[^a-zA-z]', target):
raise Exception(EAlterRet.InvalidCharacter)
if target.lower().capitalize() != target:
raise Exception(EAlterRet.NotCapitalized)
if not re.search(r'[AEIOUaeiou]$', target):
raise Exception(EAlterRet.EndWithConsonant)
expect Exception as ex:
ret = AlterNameErrors[ex.args[0]]
msg = "Alter name to '{}', result:{}".format(target, ret)
print(msg)
else:
self.name = target
finally: # 如果有需要的話,可以有finally語句
# 做點(diǎn)別的什么事情
在不考慮效率的情況下,使用異常應(yīng)該是三種方式中最簡潔的方式。使用異常還有一個(gè)好處,就是可以在finally中做點(diǎn)別的什么事件。因?yàn)闊o論try中有raise還是有return,finally的語句總是會(huì)被執(zhí)行。也就是說,無論發(fā)生什么情況,我們總是可以在finally做一些清理工作,如關(guān)閉之前打開的文件、關(guān)閉socket、或者打一些日志……
4、猜猜看
猜猜下面的這段代碼中,構(gòu)造函數(shù)做了些什么。請?jiān)谠u(píng)價(jià)區(qū)中進(jìn)行留言和討論 。
class FancyConstructor:
def __init__(self, a, b, c, d, e, f, g):
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
總結(jié)
以上是生活随笔為你收集整理的python粘性拓展_Python基础之:拓展解决问题的思路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python查找字符串出现次数_Pyth
- 下一篇: 消除左递归实验代码_「leetcode」