python - 啃书 第四章 组合数据

第4章 组合数据 4.7 典型案例 4.7.1 查找

查找

换了材料算是复习过一遍后,对iterable有了深一些的认识,再看书上的例子,很模拟~

题目:查找列表中是否包含某元素,这里以数字为例

a=[1,2,3,4,5]
fa=int(input("查找列表是否包含数字:"))
print("包含" if fa in a else "不包含")

这里犯过两次错误:

fa=input("查找列表是否包含数字:") # 这样获取的结果是字符串
fa=[int(input("查找列表是否包含数字:"))] # 这样获取的是列表,但:
列表的查找方法:
list1.conut(value) # 138 ns 如果返回值>0,则包含
list1.index(value) # 76.4 ns 如果不包含,会报错
value in list1 # 39.7 ns 包含,返回True,反之False 适用于iterable

最效率最安全的,也是为之而设计的就是in,方法1、会遍历所有元素,方法2、会记录位置,如果不存在,还会报错。
方法3,会因列表的长度,与方法2的差距缩小甚至反转

以上就是这波的内容,再来看看书中的例子,我不喜欢这本书,例子不够艺术。也有多处错误,算法也很一般!

list1=[3,6,1,9,5,8,7,4]
# key=int(input("请输入要查找的关键字:"))
key=3
num=-1
for i in range(len(list1)):
    if list1[i]==key:
        num=i
        break
if num!=-1:
    print("要查找的关键字%d索引为%d."%(key,num))
else:
    print("关键字%d查找失败"%key)

如果把上面的代码写成下面的

%%timeit
list1=[3,6,1,9,5,8,7,4]
# key=int(input("请输入要查找的关键字:"))
key=3
num=-1
if key in list1:
    print("要查找的关键字%d索引为%d."%(key,list1.index(key)))
else:
    print("关键字%d查找失败"%key)

发生了很偶然的事情,两者的运算时间都是102 µs。
但我记得在那里,有过一个用法,可以在一个流程中同时获取两个数据!

list1=[3,6,1,9,5,8,7,4]
# key=int(input("请输入要查找的关键字:"))
key=3
num=-1
for (i,k) in list(enumerate(list1)):
    if k==key:
        print("要查找的关键字%d索引为%d."%(key,i))
        break
    else:
        print("关键字%d查找失败"%key)

效率仍是魔鬼般的102 µs

看来不是书上的算法的问题,这确实是个麻烦的问题!

%%timeit
list1=[3,6,1,9,5,8,7,4]
# key=int(input("请输入要查找的关键字:"))
key=3
try:
    i=list1.index(key)
    print("要查找的关键字%d索引为%d"%(key,i))
except:
    print("关键字%d查找失败"%key)

99.8 µs

str中有个find,让我们试一下

s="".join(chr(i) for i in range(40000,40500))
#'鱀鱁鱂鱃鱄鱅鱆鱇鱈鱉鱊鱋鱌鱍鱎鱏鱐鱑鱒鱓鱔鱕鱖鱗鱘鱙鱚鱛鱜鱝鱞鱟鱠鱡鱢鱣鱤鱥鱦鱧鱨鱩鱪鱫鱬鱭鱮鱯鱰鱱鱲鱳鱴鱵鱶鱷鱸鱹鱺鱻鱼鱽鱾鱿鲀鲁鲂鲃鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳚鳛鳜鳝鳞鳟鳠鳡鳢鳣鳤鳥鳦鳧鳨鳩鳪鳫鳬鳭鳮鳯鳰鳱鳲鳳鳴鳵鳶鳷鳸鳹鳺鳻鳼鳽鳾鳿鴀鴁鴂鴃鴄鴅鴆鴇鴈鴉鴊鴋鴌鴍鴎鴏鴐鴑鴒鴓鴔鴕鴖鴗鴘鴙鴚鴛鴜鴝鴞鴟鴠鴡鴢鴣鴤鴥鴦鴧鴨鴩鴪鴫鴬鴭鴮鴯鴰鴱鴲鴳鴴鴵鴶鴷鴸鴹鴺鴻鴼鴽鴾鴿鵀鵁鵂鵃鵄鵅鵆鵇鵈鵉鵊鵋鵌鵍鵎鵏鵐鵑鵒鵓鵔鵕鵖鵗鵘鵙鵚鵛鵜鵝鵞鵟鵠鵡鵢鵣鵤鵥鵦鵧鵨鵩鵪鵫鵬鵭鵮鵯鵰鵱鵲鵳鵴鵵鵶鵷鵸鵹鵺鵻鵼鵽鵾鵿鶀鶁鶂鶃鶄鶅鶆鶇鶈鶉鶊鶋鶌鶍鶎鶏鶐鶑鶒鶓鶔鶕鶖鶗鶘鶙鶚鶛鶜鶝鶞鶟鶠鶡鶢鶣鶤鶥鶦鶧鶨鶩鶪鶫鶬鶭鶮鶯鶰鶱鶲鶳鶴鶵鶶鶷鶸鶹鶺鶻鶼鶽鶾鶿鷀鷁鷂鷃鷄鷅鷆鷇鷈鷉鷊鷋鷌鷍鷎鷏鷐鷑鷒鷓鷔鷕鷖鷗鷘鷙鷚鷛鷜鷝鷞鷟鷠鷡鷢鷣鷤鷥鷦鷧鷨鷩鷪鷫鷬鷭鷮鷯鷰鷱鷲鷳鷴鷵鷶鷷鷸鷹鷺鷻鷼鷽鷾鷿鸀鸁鸂鸃鸄鸅鸆鸇鸈鸉鸊鸋鸌鸍鸎鸏鸐鸑鸒鸓鸔鸕鸖鸗鸘鸙鸚鸛鸜鸝鸞鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳'
%%timeit
s.index('鸳')
276 ns ± 6.62 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%%timeit
s.find('鸳')
264 ns ± 5.49 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%%timeit
s.count('鸳')
503 ns ± 6.55 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

没意思,给自己出道题吧!

%%timeit
i=s.count('鸳')
c=[]
a=0
while i:
    c.append(a:=s.find('鸳',a))
    i-=1
    a+=1
c # [106, 249, 355, 500]
2.15 µs ± 70.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

纳尼?

%%timeit
i=ss.count('鸳')
c=[]
a=0
while i:
    c.append(a:=ss.index('鸳',a))
    i-=1
    a+=1
19.1 µs ± 422 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%%timeit
ss.index('鸳')
1.96 µs ± 14.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
s.index('鸳')
188 ns ± 1.6 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

列表的索引操作相对字符串,慢

但:
%%timeit
ss=list(s)
22.7 µs ± 197 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

二分查找

条件:有序数列,升序或降序
假定升序:先查找最中间的,如果key>list1[len(list1)//2],则查找后半段中间的,依次!

def Binary_Search(start,end):
    m=start+(end-start)//2
    lm=list1[m]
    if key==lm:
        print("%d在列表的索引%d"%(key,m))
    elif key>lm:
        Binary_Search(m+1,end)
    else:Binary_Search(start,m)

list1=list(range(100))
start=0
end=len(list1)-1
key=int(input("请输入要查找的数字:"))
if key<list1[start] or key>list1[end]:
    print("key不在列表范围内")
else:Binary_Search(start,end)

以上出现了两次书写错误,input没有int,想着来着,想先写完input回头用int括起来,结果写完就顺着写下去了,忘了。
又把list1写成list了,少了1。其实我宁愿写l,例如m就是middle,我本来吧m和lm用全写写出来了,又改回去了,我不喜欢这种命名。觉得反而会让代码看着乱。在这,start、end,这么明显了,还来?
30:108 µs
和常规顺序查找效率差不多
80:也是这个数,但是常规查找是114 µs
理论上二分查找较常规查找更有优势,但是前提是顺序序列

列表的索引是从末尾开始的

list1[0]
51 ns ± 14.2 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
list1[99]
42.3 ns ± 2.14 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
list1[50]
48.5 ns ± 6.08 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
list1[50]
list1[50]
list1[50]
127 ns ± 8.74 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
100
11.9 ns ± 0.878 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
a
25.6 ns ± 3.28 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
a=100
33.2 ns ± 2.83 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
a=100
a
47.5 ns ± 2.51 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

以上是码的时候想到的事情,即,每次list[index],其实是需要经过一串步骤的,相当于x**2这类的运算式,所以如果多次用到的情况下,还是先把元素赋值给一个变量再使用这个变量

if list1[50]:
    list1[50]
    list1[50]
150 ns ± 6.26 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

v=list1[50]
if v:
    v
    v
74.1 ns ± 14.6 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

if (v:=list1[50]):
    v
    v
67 ns ± 7.01 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

之前的一段笔记中,稍微测了下海象酱的效率,那时觉得他就是颗糖,这次结合实际应用,写了下,海象酱赛高

%%timeit
f=2**3
if f>0:f
71.1 ns ± 2.07 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit
if 2**3>0:2**3
32.6 ns ± 1.56 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit
if (f:=2**3)>0:f
271 ns ± 10.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit
f=list1[50]
if f>0:f
76.1 ns ± 1.67 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit
if list1[50]>0:list1[50]
114 ns ± 1.96 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit
if (f:=list1[50])>0:f
96.3 ns ± 3.08 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

海象的有效性和需要赋值的那个数值的运算复杂性有关,也就是说当数值的运算时间>海象时间-常规时间,海象是很好的选择。但是这个很不好说的样子,但差距只在分毫,而且有的地方可能不方便添加一个常规赋值,或者海象能够减少一个变量。

冒泡法排序

list1=[chr(i) for i in range(ord('a'),ord('z')+1)]
list2=list(set(list1))
maxi=site=len(list2)-2
while site>-1:
    index=site
    while index<=maxi:
        if list2[index]>list2[index+1]:
            list2[index],list2[index+1]=list2[index+1],list2[index]
            index+=1
        else:break
    site-=1

52.7 µs
先生成一个a到z的列表,然后用set的无序性变成一个乱序的列表
第一组比较就是最后两个位置上先排序
然后依次排倒数第三个,和向后相邻的作比较,只要比之后的大就交换位置
依次做上面两步,直到索引0的也排序完成,整个排序就结束

写代码时发生过一次错误,在排序过程中并没有设置终止方法,内部的是while 1:

用海象优化下:

        if (i1:=list2[index])>(i2:=list2[index+1]):
            list2[index],list2[index+1]=i2,i1

45.6 µs
这次海象也很可爱,添加代码只需要加上海象、变量、括号。而看代码时,当看到变量时,立刻直到这个变量是干什么的,而且变量仅仅是替换了这两行的代码,如果是在函数中,就绝对不会有变量干扰的情况,但是函数每次调用都相当于要寻找一个变量的位置,所以效率稍微第一点,试一下吧!

%%timeit
def change(index):
    while index<=maxi:
        if (i1:=list2[index])>(i2:=list2[index+1]):
            list2[index],list2[index+1]=i2,i1
            index+=1
        else:return
list1=[chr(i) for i in range(ord('a'),ord('z')+1)]
list2=list(set(list1))
maxi=site=len(list2)-2
while site>-1:
    index=site
    change(index)
    site-=1

43.6 µs、42.4 µs
但如果我吧return换成break,45 µs,总之这个return很好用的样子

def f():""
f()
165 ns ± 5.78 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

""
10.5 ns ± 1.04 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

我是非def当,但是这次return真是带来了微爽的体验,毕竟break的流程是与while配套的。
break:“老大,我干完了” while:“哦。 哥们,交工了”
return:“给”
尤其是很多嵌套循环的时候,一个return搞定,否则要用很多break的样子!

打脸,而且还有个现象,额,是我忽略了,这次赋值时,我本来想什么都不用赋值的,因为它可以从主空间中读取变量的数值,但,只读。一旦改变值的内容,就需要专门的传递数值,相当于是个完全权限的副本。我忘了if后,index+=1,被系统ERROR了下才填上,那时我还在想是不是搞错了,而没看到那个赋值表达式。
因为list2[index]的方法,他调用的是主体中的list2,并没有改变他的ID,所以

我发现了个现象,当%%itmeit时,被执行的代码并没有影响主空间的列表内容变化,我之前刚好显示了个没有排序的list2,而%%itemit后,这个列表仍未顺序,所以去掉%%itmeit了下,哦,原来如此,%%itmeit就相当是一个虚拟机。

接下来看看书上的代码:

list1=[8,3,1,5,2]
N=list1.__len__()
print("排序之前:",end=' ')
for i in range(N):
    print(list1[i],end=' ')
print()
#排序
for i in range(N-1):
    for j in range(i+1,N):
        if list1[i]>list1[j]:
            list1[i],list1[j]=list1[j],list1[i]
print("排序之后:",end=' ')
for i in range(N):
    print(list1[i],end=' ')

1.13 ms ± 23.9 µs
书中使用的是for i in range(N),不是while。
那就试一下两者的区别吧!

i=0
while i<10:
    i+=1
594 ns ± 39.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

for i in range(10):pass
400 ns ± 32 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

for领先,但看看实际案例中吧!

....
for i in range(len(list2)-2,-1,-1):
    index=i
    while index<=maxi:
        if (i1:=list2[index])>(i2:=list2[index+1]):
            list2[index],list2[index+1]=i2,i1
            index+=1
        else:break

45.1 µs ± 1.9 µs per
和while不相上下,而且由于算法的原因,我是要从倒数第二个到第一个的运算,所以写了range有点复杂!

range(len(list2)-2,-1,-1)
321 ns ± 9.33 ns
range(0,len(list2)-1)
308 ns ± 10.9 ns

差距是肯定的,但是这么点可以忽略!

而第二步的话,书上代码做了没意义的事情!

list1=[chr(i) for i in range(ord('a'),ord('z')+1)]
list1=list(set(list1))
N=list1.__len__()
for i in range(N-1):
    for j in range(i+1,N):
        if list1[i]>list1[j]:
            list1[i],list1[j]=list1[j],list1[i]

57.6 µs ± 2.79 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
循环325次,交换115次
而我的方法
循环137次,交换同样是115次

emm…

这里书上和我的逻辑不一样,他的属于那种真,从最下到最上的冒泡。
我的可能就不叫冒泡了,或者叫从最上到最下的冒泡,我觉得我的思路大家更加容易理解。
但结果就是,交换次数是一样的!

书上的思路是:
我要找个全表最小的,放在第一个位置。
下一个索引,剩下的列表,最小的。

我的思路是:
我要找列表片段最大的,排出来
之后向前扩展,对前面的一个元素与后面的对比,因为后面的排列过,所以遇到无法再大的,就是这里了,再往前看了。

emm,还可以再优化!?

list1=[chr(i) for i in range(ord('a'),ord('z')+1)]
list(set(list1))
['e', 'k', 'b', 'g', 'd', 'o', 'p', 'h', 'i', 'm', 'l', 's', 'n', 'x', 'y', 't', 'j', 'r', 'z', 'a', 'u', 'v', 'w', 'q', 'c', 'f']

话说最近每次的执行结果都是这个,是偶然还是必然,总之就以此为样本,不再写生成式了!
PS:我重新开机后,就是另外一个数值,大概是跟服务有关,我重启了服务,果然如此,也就是每打开一次python,结果就不一样了!

我想了下,想尝试用二份查找法的思路去确定他插在哪里合适,但是这个二分法又与那个二分法不同!
1、他插入的位置,需要满足大于后面的,小于前面的两个条件,所以这个思路:

1 2 3 4 5 6 7 8
1^2 3^4 5^6 7^8,两个一组
就变成了只需要找:
1 3 5 7 或是 2 4 6 8
但总觉得哪里不大对,这样如果他正好是需要插在4、5之间,56都比他大,34都比他小,如果遇到一个比他小的,就,这种思路不对。

于是还是另一种更为简单但最后又复杂的方法!

1 2 3 4 5 6 7 8
如果引入二分法的思路,我习惯先检测1、8,确定他是其之间,然后他们之间的,如果他与中间位置相比,大了,那他就是中间、8,之间的,这样持续的做下去。

得出的是区间,那最后的区间怎么判定呢

1 2 3
1 start=1 end=3 middle=(3-1//2)+start
if v>list1[middle]:start=middle

最后start与end挨着的时候,每次都要判断start和end吗?就是v需要的位置,它需要在start位置上,而前面的位置左移。

这种方法真的快吗?理论上是,但是多次的运算的时间消耗和简单的累加交换的消耗,哪个更多呢!?

其实关于上面的奇偶法,可以产生一个变种,但是运算量嘛!
之前因为两组之间没有连接,那么补完这个连接呢

1-2-3-4-5-6-7-8

1-2 7-8
4-5 2-3 3-4

但总觉得做了多余的事。

list1=['e', 'k', 'b', 'g', 'd', 'o', 'p', 'h', 'i', 'm', 'l', 's', 'n', 'x', 'y', 't', 'j', 'r', 'z', 'a', 'u', 'v', 'w', 'q', 'c', 'f']
maxi=len(list1)-1
site=len(list1)-2
if (v1:=list1[site])>(v2:=list1[maxi]):list1[site],list1[maxi]=v2,v1
site-=1
while site>-1:
    start=site+1
    end=maxi
    while 1:
        if list1[site]<list1[start]:break
        elif (v1:=list1[site])>list1[end]:list1[site:end],list1[end]=list1[site+1:end+1],v1;break
        while 1:
            if end==start+1:list1[site:start],list1[start]=list1[site+1:start+1],list1[site];break
            elif list1[site]>list1[(middle:=start+(end-start)//2)]:start=middle
            else:end=middle
    site-=1

39.1 µs ± 846 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
提升是提升了,这个是那种最简单的思路,还能再优化吗?

list1=['e', 'k', 'b', 'g', 'd', 'o', 'p', 'h', 'i', 'm', 'l', 's', 'n', 'x', 'y', 't', 'j', 'r', 'z', 'a', 'u', 'v', 'w', 'q', 'c', 'f']
maxi=len(list1)-1
site=len(list1)-2
if (v1:=list1[site])>(v2:=list1[maxi]):list1[site],list1[maxi]=v2,v1
site-=1
while site>-1:
    start=site+1
    end=maxi
    if list1[site]<list1[start]:pass
    elif (v1:=list1[site])>list1[end]:list1[site:end],list1[end]=list1[site+1:end+1],v1
    else:
        while 1:
            if end==start+1:list1[site:start],list1[start]=list1[site+1:start+1],list1[site];break
            elif list1[site]>list1[(middle:=start+(end-start)//2)]:start=middle
            else:end=middle
    site-=1

36.6 µs ± 1.21 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
小逻辑上改了下,剩下的就是看看别的算法是否有显著提升!

话说上述内容其实就是把最开始我的那个,一层层的冒泡,变成一个跳跃。例如一次冒泡要上升十个位置,他就是执行了十次一对一的位置交换。
而这次是一次的位置交换,不过十次的一对一变成了一次的一对十。

至于再优化,有点不想了,我承认我懒!

选择排序法

话说我对书中的冒泡排序法可能有误解,说不定冒泡排序的常规定义还真是书中那么比较低效的,因为这边选择排序法说了另一种方式,不过仍是从小到大的一对多,一对多方面类似于上面我改的,但他仍然是从小到大。那我试一试吧!

list1=['e', 'k', 'b', 'g', 'd', 'o', 'p', 'h', 'i', 'm', 'l', 's', 'n', 'x', 'y', 't', 'j', 'r', 'z', 'a', 'u', 'v', 'w', 'q', 'c', 'f']
edge=len(list1)
while 1:
    maxi=0
    for i in range(edge):
        if list1[i]>list1[maxi]:maxi=i
#   if maxi==edge-1:pass # 这句话有没有都不会影响序列的正常运算结果
    list1[edge-1],list1[maxi:edge-1]=list1[maxi],list1[maxi+1:edge]
    if edge==2:break
    edge-=1

49.7 µs ± 4.43 µs
期间搞错过,切片到底是谁传输到谁。改对后,终结条件也变成了edge==2。
如果当前要测试的位置就是最大的数,那么其实是可以不用做任何改变的,不过正好那个赋值式的结果其实和之前是一样的,不过肯定要耗费一点时间,但如果每次都要加一个判断语句,也是会耗费时间的,试试。

    if maxi==edge-1:pass

49.3 µs ± 2.54 µs

那如果在极限条件下呢,list1的序列本身就是顺序的话
42.3 µs ± 1.84 µs
50 µs ± 3.91 µs
很明显的结果,大家都懂得把!
但是其实之前也说过,这个赋值给list1的,就是list(set)的一个随机值,而那个位置正好不需要动,却是一个很少见的状况,所以一开始时我就考虑到这个问题,并且验证之后的赋值式并不会改变序列内容,才省去了if。至今我仍然这样想,所以最好的方法还是加上注释吧!要么就当一个小测验给之后看的人一个题目。

那如果终结条件用其他的方法写,效率会怎样呢!

while edge>1
50.2 µs ± 2.86 µs

list1=['e', 'k', 'b', 'g', 'd', 'o', 'p', 'h', 'i', 'm', 'l', 's', 'n', 'x', 'y', 't', 'j', 'r', 'z', 'a', 'u', 'v', 'w', 'q', 'c', 'f']
edge=len(list1)
for e in range(edge,1,-1):
    maxi=0
    for i in range(e):
        if list1[i]>list1[maxi]:maxi=i
    list1[e-1],list1[maxi:e-1]=list1[maxi],list1[maxi+1:e]
51.7 µs ± 6.79 µs
49.6 µs ± 3.55 µs
50.1 µs ± 4.78 µs

果然,逻辑思维正常的话,还是那个脑袋出的流程最有效率!至少效率相差不大的时候,那种流程更加容易理解!

但是,我觉得这种方法就是有点弱鸡。

不过我突然想到,沉淀法可能会更加的高效,因为列表的每个位置读取速度是不一样的,那就再试试吧!

list1=['e', 'k', 'b', 'g', 'd', 'o', 'p', 'h', 'i', 'm', 'l', 's', 'n', 'x', 'y', 't', 'j', 'r', 'z', 'a', 'u', 'v', 'w', 'q', 'c', 'f']
site=0
len1=len(list1)
for site in range(0,len1-1):
    mini=site
    for i in range(site,len1):
        if list1[i]<list1[mini]:mini=i
    list1[site],list1[site+1:mini+1]=list1[mini],list1[site:mini]

46.7 µs ~ 49 µs
他们的运算级是
∑ 1 l e n ( l i s t 1 ) = 1 + 2 + . . . + l e n ( l i s t 1 ) {\textstyle \sum_{1}^{len(list1)}}=1+2+...+len(list1) 1len(list1)=1+2+...+len(list1)

果然目前几个来说,还是我的那个方法最好。

虽然我没有特意的去思考,但是就是一下子想到那个,优化后更是超出一大截!
十大经典排序算法(动图演示)

推导式

推导式提供了创建组合数据的简单途径。
推导式一般在for之后跟一个表达式,后面有零到多个for或if子句,返回结果是一个根据其后的for和if条件生成的组合数据。通过推导式可以快速创建列表、元组、字典和集合等。
列表推导式的一般格式为:
[表达式 for 变量 in 列表 [if 条件]]
其中,if条件表示对列表中元素的过滤,可选。
元组、字典、集合等推导式的创建与列表创建类似,只需要将外层[]替换成相应的{}或()。

list1=[i for i in range(100)]

list1=[i for i in range(100) if i%2==0]

list1=[i for i in range(100) if i%2!=0]

list1=[i**2 for i in range(100)]

list1=[i*j for i in [1,2,3] if i <3 for j in [4,5,6] if j >4]

list1=[i*j for i in [1,2,3] for j in [4,5,6] if i<3 and j >4]

以上得出的只能是一次次循环中的结果,例如后面两个for,并非是1vs1的关系,而是多vs多的关系。

list1=[chr(i) for i in range(ord('a'),ord('z')+1)]
list2=[i for i in range(ord('a'),ord('z')+1)]
dict1={list1[i]:list2[i] for i in range(len(list1))}

书上有个例子,那个变种就是如上!

dict1={chr(i):i for i in range(ord('a'),ord('z')+1)}

那个只是表示一一对应如何去写,例如两个列表中的内容一一对应形成一个字典,虽然上面三句可以合并为一句

set1={dict1[key] for key in dict1}

顺便写个有点意义的set,之所以说有意义,是因为书中的例子比较蛋疼。

set1={i for i in list1} # 2.07 µs 脑残不
set1={list1[i] for i in range(len(list1))} # 3.29 µs 捂脸笑
set1=set(list1) # 1.39 µs 这个才是正确用法
if 1:""
else:""
14.7 ns ± 0.0292 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

if 1:""
14.7 ns ± 0.0366 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

if 0:""
else:""
14.7 ns ± 0.0354 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

if 0:""
""
14.6 ns ± 0.0452 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

我尝试把判断奇偶质合缩写成推导式,在完整版中多处用到if,通常如果成功的话,就直接return True了,只有失败的时候才会下一步,所以这里就省去了else,实际运行效果都差不多,那么else到底该不该省略呢!

def 奇数():
    if i%2!=0:return True
    return False
def 偶数():
    if i%2==0:return True
    return False
def 质数():
    if i%2!=0:
        for j in range(2,i):
            if i%j==0:return False
        return True
i=int(input("输入一个自然数:"))
if i==0:print("偶数")
elif i==1:print("奇数")
elif i==2:print("偶质数")
elif 偶数():print("偶合数")
elif 奇数() and 质数():print("奇质数")
else:print("奇合数")
dict1={i:p for i in range(10) if i==0:p="偶数" elif i==1:p="奇数" else:p="待定"}
SyntaxError: invalid syntax

此时我才明确地认识到,这里的if就是一次流程筛选,而式子中貌似只能存在的判断式,而不能存在赋值,可以把数的分类写成函数来调用!

def 奇数(i):
    if i%2!=0:return True
    return False
def 偶数(i):
    if i%2==0:return True
    return False
def 质数(i):
    if i%2!=0:
        for j in range(2,i):
            if i%j==0:return False
        return True
def 判断(i):
    if i==0:return "偶数"
    elif i==1:return "奇数"
    elif i==2:return "偶质数"
    elif 偶数(i):return "偶合数"
    elif 奇数(i) and 质数(i):return "奇质数"
    else:return "奇合数"

dict1={i:判断(i) for i in range(10)}

嗯,这是必须要写成函数的时候,函数很万能,函数的return很万能,我爱函数.jpg 啊呸…
看来越是涉入的深,越离不开函数了,难怪大佬们都喜欢函数。
不过这个段代码和上面的不一样之处在于值传递到了局域变量,因为推导式里面的变量是一个与外界无关的小天地,而函数调用的i是全局变量的i,而推导式执行前后,全局变量i都是1,都没有被改变!所以只能将值传递给局部变量了!

list1=[chr(i) for i in range(ord('a'),ord('z')+1)]
list2=[i for i in range(ord('a'),ord('z')+1)]
dict1=dict(zip(list1,list2))

书中说:使用推导式创建元组,那么别管什么元组字典,zip也算是推导式吗?

字符串运算

列表推导式

lambda 和 map()

单行条件语句

zip()

看到一篇这文章,下一步就是过一遍!

上面忘了看了,先搞定了书上的内容,书上用一个元素为字典的列表描述三国人物,我觉得没啥意思,就从网上攻略资料手动复制,代码处理生成了,目前只是生成,暂没有要做处理,下午再说!

print('*'*40+'\n'+'-'*14+"游戏角色管理"+'-'*14+"\n1:查询角色\n2:添加角色\n3:修改角色\n4:删除角色\n5:显示所有角色\n-1:退出程序\n"+'*'*40)

txt=r"""武将 	编号 	武力 	智力 	专属造型 	名将(CG) 	可复活  	专属必杀 	特殊官职14 	特殊官职15 	特性1 	特性2 	特性3 	备注
大乔 	16 	70 	80 	是 	是 	是 	幻舞艳光(扇) 	东吴娘娘 	  	扇术精髓 	飞刀 	媚力 	 
小乔 	17 	82 	82 	是 	是 	是 	花影乱击(刀) 	东吴郡主 	  	刀术 	冷箭 	媚力 	 
太史慈 	32 	97 	69 	是 	是 	是 	断天白虹(枪) 	孝侯 	  	水面战擅长 	格挡 	枪术 	 """
txt=txt.splitlines()
list1=[]
for i in txt:
    list1.append(i.replace(" ","").split("\t"))
    
三国群英传7_武将资料=[dict(zip(list1[0],list1[i])) for i in range(1,len(list1))]

for i in 三国群英传7_武将资料:
    if i["武将"]=="刘备":
        print(i)
        break
else:print("没有找到刘备的资料")

for i in 三国群英传7_武将资料:
    for x,y in i.items():
        print(("%-8s\t%s")%(x,y))

上面处理中发生的问题就是不整齐,无论是原数据,里面有空内容(好在复制出来后,空内容是\u3000,可以很方便的保留,但是一些无意的处理还是容易丢掉这个内容,例如直接split而不加\t,这里dict(zip)仍能运行,不过已经只显示前面几个,也错位了,后面的丢失了),有个别多余空格,还是输出时不对齐,我用的是jupyter,顶多是因为前面字符串大于4造成的\t问题,而不是前辈有提到的另一个中文字符和等宽字体的问题。所以相对简单的就处理了。

上面的代码出发了csdn的审核机制,不知道是哪一段,我这里只列出了三个角色来着,实际上一下子都处理的说。

s=input('输入"-1":删除;输入对应的项目和内容编辑(项目:内容 回车进入下一个编辑);输入"重写":修改编号后面的内容')
if s=="-1":del 三国群英传7_武将资料[i]
elif s=="重写":
    for key in list(三国群英传7_武将资料[i].keys())[2:]:
        三国群英传7_武将资料[i][key]=input(key+":")
else:
    while 1:
        key,value=s.split(":")
        三国群英传7_武将资料[i][key]=value
        s=input("继续修改,输入退出,推出编辑\n")
        if s=="退出":break

这里删除列表元素有三种方法:

txt=r"""武将 	编号 	武力 	智力 	专属造型 	名将(CG) 	可复活  	专属必杀 	特殊官职14 	特殊官职15 	特性1 	特性2 	特性3 	备注
大乔 	16 	70 	80 	是 	是 	是 	幻舞艳光(扇) 	东吴娘娘 	  	扇术精髓 	飞刀 	媚力 	 
小乔 	17 	82 	82 	是 	是 	是 	花影乱击(刀) 	东吴郡主 	  	刀术 	冷箭 	媚力 	 """
list1=txt.splitlines()
						# 857 ns ± 10.3 ns
del list1[0]			# 925 ns ± 6.7 ns 	68ns
list1.pop(0)			# 985 ns ± 11.7 ns	127ns
list1.remove(list1[0])	# 992 ns ± 8.41 ns	135ns
list1.__delitem__(0)	# 1.05 µs ± 16.3 ns	148ns

貌似所有的删除都并非单纯的建立在索引的基础上,除了pop,但是pop他又要搞个输出,同样造成了内容读取的现象,而最后一项貌似是第一项的定义而已,指的是可以使用del这个方法的属性。
至于del,他… 我错了,不深究了!否则又要没完没了了

刚才查了下,还有个问题,就是remove是删除搜索到的第一个内容,并不是定位删除的。而其他的都是定位删除的。所以这里又倾向了del方法,另外remove在一片文章中还提到了一些问题,虽然我觉得问题是一串操作后的结果,但是还是可以,码一下,加深下!

Python-列表元素删除与remove()方法

list1=[1,2,1,2,1]
for i in list1:
    if i==1:list1.remove(1)
# [2, 2]
list1=[1,1,1,2,2] # [1, 2, 2]
list1=[1,1,1,2,2]
for i in range(list1.count(1)):
    list1.remove(1)
904 ns

list1=[1,1,1,2,2]
listi=[]
for i,v in enumerate(list1):
    if v==1:listi.append(i)
for i in listi[::-1]:
    del list1[i]
1.37 µs

list1=[1,1,1,2,2]
listi=[]
for i in range(len(list1)):
    if list1[i]==1:listi.append(i)
for i in listi[::-1]:
    del list1[i]
1.51 µs

虽然标记法是个思路,但至少在这个场景下并没计数法快,毕竟计数法是原生方法。

你可能感兴趣的:(python)