04 - 迴圈

  • 迴圈與迭代
  • 產生連續的數組當成索引-range()
  • 直接對串列做迭代
  • 串列的增刪
  • 避免迴圈內原地修改
  • 補充知識-zip()、enumerate()

迴圈與迭代

迭代的概念是「一個接著一個處理」,
需要有供給可迭代的物件的提供者,讓接收方一個個處理。

序列可以用索引值標示元素的概念,就是很好者提供者,
而處理這些迭代物件的接收方,在Python裡最常用的就是for迴圈!

for 命名物件 in 可迭代者:  
    重複要做的事
  • 字串、串列 都是可迭代者
  • 命名物件指向每次從可迭代者拿出的元素
  • 重複要做的事是在迴圈裡面,所以要記得冒號、縮排
  • 迴圈會取出所有存在可迭代者中的元素:字串的每個字母、串列的每個元素
for i in [1,4,7,9]:
    print (i)

等同於

i=1
print(i)
i=4
print(i)
i=7
print(i)
i=9
print(i)

for最方便的地方就是會自動拿取可迭代者下一個元素,直到無法拿取為止。
原理:會將可迭代者轉換成迭代器,然後用next方法,呼叫下一個元素。

想像我有一個紀錄員工年資的串列,
薪水是依照年資*100+22000 的公式去計算

In [1]:
l = [5.5, 9, 10]
print("員工1的薪水",l[0]*100+22000)
print("員工2的薪水",l[1]*100+22000)
print("員工3的薪水",l[2]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000

3個員工,我就要寫3行告訴我各個員工的薪水,那如果有100個員工...
如果有一個紀錄串列各個索引值0~n的資料型別,就會很方便

產生連續的數組當成索引-range()

range(start,end,stride)

  • range也是一種序列型別,會回傳一系列能迭代的物件,是可迭代者
  • start 是 inclusive,預設是0開始
  • end 是 exclusive,必填值
  • stride 是跳幾次切割,預設是1
    • stride 為負值則由尾端向前端取,需讓start>end

注意!

  • start > end ,則等同於空
  • stride 不得為0
In [2]:
print('range(5)     ',list(range(5)))     # 只有一個代表end
print('range(2,5)   ',list(range(2,5)))
print('range(2,5,2) ',list(range(2,5,2)))
print('range(5,0)   ',list(range(5,0)))    
print('range(5,0,-1)',list(range(5,0,-1))) # 5-1有<0嗎?
print('range(0,5,-1)',list(range(0,5,-1))) # 0-1有<5嗎?
range(5)      [0, 1, 2, 3, 4]
range(2,5)    [2, 3, 4]
range(2,5,2)  [2, 4]
range(5,0)    []
range(5,0,-1) [5, 4, 3, 2, 1]
range(0,5,-1) []
In [3]:
print(list(range(3)))
for i in range(3):
    print(i)
[0, 1, 2]
0
1
2

配合迴圈,這樣可分別拿到0~2的數值,可以當成串列索引

In [4]:
# 土法煉鋼,一個個員工慢慢列
l = [5.5, 9, 10]
print("員工1的薪水", l[0]*100+22000)
print("員工2的薪水", l[1]*100+22000)
print("員工3的薪水", l[2]*100+22000)
# ...
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [5]:
# 使用迴圈
l = [5.5, 9, 10]
for index in range(3):
    print(index, "員工{}的薪水".format(index+1), l[index]*100+22000)
0 員工1的薪水 22550.0
1 員工2的薪水 22900
2 員工3的薪水 23000

range()更彈性的寫法

range(數值)中,數值最好保持彈性,
如果寫死,一但串列增加元素,就得記得改值,很是不方便!

In [6]:
l = [5.5, 9, 10]
for index in range(3):
    print("員工{}的薪水".format(index+1), l[index]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [7]:
l = [5.5, 9, 10, 2.3]
for index in range(4):
    print("員工{}的薪水".format(index+1), l[index]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
員工4的薪水 22230.0
In [8]:
l = [5.5, 9, 10, 2.3]
for index in range(len(l)): # 串列有多長就用多長
    print("員工{}的薪水".format(index+1), l[index]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
員工4的薪水 22230.0

[練習] 調分公式:開根號(0.5次方),再乘10

有一列學生的成績l = [32,56,58,62,79,82,98]
我希望將學生調分:

學生1 56.568542494923804
學生2 74.83314773547883
學生3 76.15773105863909
學生4 78.74007874011811
學生5 88.88194417315589
學生6 90.55385138137417
學生7 98.99494936611666

  • 使用range()
    l = [32,56,58,62,79,82,98]
    for index in range(__):
      print("學生{}".format(__),___**0.5*10)
    

直接對串列做迭代

串列也是一個可迭代者,
如果要取所有串列的元素,除了用索引拿取 range(0,len(串列))
也可以直接用for迴圈處理:

In [9]:
l = [5.5, 9, 10]
for year in l:
    print(year)
5.5
9
10
In [10]:
l = [5.5, 9, 10]
for year in l:
    print(year*100+22000)
22550.0
22900
23000
In [11]:
# 對比 range() 寫法
l = [5.5, 9, 10]
for index in range(3):
    print(l[index]*100+22000)
22550.0
22900
23000

想要印出第幾位員工,
因為直接用串列當成可迭代者,需要紀錄現在是第幾個索引

In [12]:
l = [5.5, 9, 10]
count=0 # 紀錄現在的索引
for year in l:
    print("員工{}的薪水".format(count+1), year*100+22000)
    count = count+1 # count+=1 
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [13]:
# 對比 range() 寫法
l = [5.5, 9, 10]
for index in range(3):
    print("員工{}的薪水".format(index+1), l[index]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000

[練習] 調分公式:開根號(0.5次方),再乘10

有一列學生的成績l = [32,56,58,62,79,82,98]
我希望將學生調分:

學生1 56.568542494923804
學生2 74.83314773547883
學生3 76.15773105863909
學生4 78.74007874011811
學生5 88.88194417315589
學生6 90.55385138137417
學生7 98.99494936611666

  • 使用串列,配合紀錄現在索引位置的物件
    l = [32,56,58,62,79,82,98]
    count = 0
    for score in l:
      print("學生{}".format(__),___**0.5*10)
      count = _____
    

更多練習

[進階練習]

延續上題,
我想計算調分之後,班上的總平均:
調分後的加總/學生人數,但要保留原始的成績。

Hint:

  • 學生總數不變
  • 因為原學生成績串列必須保留,所以無法直接用 sum
    => 跟剛才紀錄索引位置的物件一樣,先設定初始值,
    然後每次迴圈的時候就加上調分後的成績。
  • 使用range()
    l = [32,56,58,62,79,82,98]
    total_score = 0
    for index in range(__):
      print("學生{}".format(__),___**0.5*10)
      total_score += _____
    print("總平均",total_score/____)
    
  • 使用串列,配合紀錄現在索引位置的物件
    l = [32,56,58,62,79,82,98]
    total_score = 0
    count = 0
    for score in l:
      print("學生{}".format(__),___**0.5*10)
      count = _____
      total_score += _____
    print("總平均",total_score/____)
    

[進階練習]

回文是個正念反念都一樣的詞句,試試看要怎麼判斷字串是否為回文,
以下這些都是回文:

  • race car
  • amor, roma
  • 上海自來水來自海上

Hint:

  • 會用到迴圈,把字串從尾跑到頭相加起來(由後到前,只能用range)
  • 會用到判斷式
  • 想想看有什麼字串方法可以把空格、標點符號都取代掉
s1 = "上海自來水來自海上"
s1 = s1.replace(___,___) # 可能不只一個
s2 = ""
for index in range(len(s1)__,__,-1):
    s2+=s1[index]
__ s1==s2:
    print("是回文")
____:
    print("不是回文")

還記得之前算文章某單字出現幾次嗎?
配合迴圈就可以把每個單字拿出來分別算:

In [14]:
article = """Bubble tea represents the "QQ" food texture that Taiwanese love. 
The phrase refers to something that is especially chewy, like the tapioca balls that form the 'bubbles' in bubble tea. 
It's said this unusual drink was invented out of boredom. 
"""
word_list = article.split(" ")
print(word_list[0],word_list.count(word_list[0]))
print(word_list[1],word_list.count(word_list[1]))
print(word_list[2],word_list.count(word_list[2]))
# .....
Bubble 1
tea 1
represents 1

=>

for index in range(len(word_list)):
    print(word_list[index],word_list.count(word_list[index]))

for word in word_list:
    print(word,word_list.count(word))

串列的增刪

  • 可變序列皆可通用
  • 會直接改變串列

增加:把序列的元素加入串列的末端

  • 串列+=序列
  • 串列.extend(物件)
In [15]:
l = [1,2,3,4,5]
l+=[6,7]
print(l)
l+="Hi"
print(l)
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 'H', 'i']

增加:把物件加入串列的末端

  • 串列.append(物件)
In [16]:
l = [1,2,3,4,5]
l.append([6,7])
print(l)
l.append("Hi")
print(l)
[1, 2, 3, 4, 5, [6, 7]]
[1, 2, 3, 4, 5, [6, 7], 'Hi']

[練習]

資料夾 files 有一連串檔案,想整理一個待刪除清單,
列出 檔名有「大阪」關鍵字的檔案。

files = ["大阪城.jpg","大阪市夜景.jpg","京都塔水舞.mp4","京都清水寺01.jpg"]

Hint:

  • 建立一個待刪除清單(串列),預設為空
  • 迴圈把 資料夾檔案清單中files 裡面的「每一個檔案 filename」挑出來
  • 判斷檔案名稱中是否有「大阪」的關鍵字(是檔案名稱,不是整個清單)
  • 如果檔名中有此關鍵字,則增加該檔案到 待刪除清單(串列)中
    • 串列的增加:串列.append(元素)
files = ["大阪城.jpg","大阪市夜景.jpg","京都塔水舞.mp4","京都清水寺01.jpg"]
del_list = __ # 建立一個待刪除清單(串列),預設為空
for filename in ____:
    if "大阪" in ___:
        del_list._____(filename)
print(del_list)

拿出索引的元素,並刪除

  • 串列.pop(索引):索引預設為 len(串列)-1
In [17]:
l = [1,2,[3,4],2,3,4]
print(l.pop())
print(l.pop(2))
print(l)
4
[3, 4]
[1, 2, 2, 3]

注意!

  • 索引不得超出範圍
In [18]:
l = [1,2,3,4,5]
a = l.pop(6)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-18-6cc12e822f3e> in <module>()
      1 l = [1,2,3,4,5]
----> 2 a = l.pop(6)

IndexError: pop index out of range

刪除第一個與物件相符的元素

  • 串列.remove(物件)

注意!

  • 若找不到相符元素會出錯
In [19]:
l = [1,2,[3,4],2,3,4]
l.remove(2)
print(l)
l.remove([3,4])
print(l)
l.remove(5)
[1, [3, 4], 2, 3, 4]
[1, 2, 3, 4]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-982bdc614fef> in <module>()
      4 l.remove([3,4])
      5 print(l)
----> 6 l.remove(5)

ValueError: list.remove(x): x not in list

想想看

利用迴圈、與內建函式或是串列的方法,留下不重複的元素:

l=[1,2,6,1,3,2,5]
會有 1,2,3,5,6 這些元素(順序可不一)

  • 可能的做法:把重複的刪除、新增到新的串列,只新增不存在的元素
    • 重複:出現次數超過一次(會用到 串列.count())
  • 把重複的刪除:從最前面刪除

    l=[1,2,6,1,3,2,5]
    for ele in l:
      if l.count(ele)>1:
          l.remove(ele)
    print(l)
    
  • 把重複的刪除:從最後面刪除

    l=[1,2,6,1,3,2,5]
    for index in range(len(l)-1,-1,-1):
      if l.count(l[index])>1:
          l.pop(index)
    print(l)
    
  • 新增到新的串列,只新增不存在的元素

    l=[1,2,6,1,3,2,5]
    l1=[]
    for ele in l:
      if ele not in l1:
          l1.append(ele)
    print(l1)
    

避免迴圈內原地修改

因為迭代器仍會取用原先串列的資訊,
但中途卻更動了原先的串列,小則結果不正確,大則程式出錯中斷

In [20]:
l=[1,2,2,2,6,1,3,5]
for ele in l:
    print(ele,l)
    if l.count(ele)>1:
        l.remove(ele)
    print(" ",l)
print(l)
1 [1, 2, 2, 2, 6, 1, 3, 5]
  [2, 2, 2, 6, 1, 3, 5]
2 [2, 2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
6 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
1 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
3 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
5 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
[2, 2, 6, 1, 3, 5]
In [21]:
l=[1,2,6,1,3,2,5]
for index in range(len(l)):
    print("l[{}]={}  ".format(index,l[index]),l)
    if l.count(l[index])>1:
        l.pop(index)
    print("\t",l)
print(l)
l[0]=1   [1, 2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[1]=6   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[2]=1   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[3]=3   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[4]=2   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 5]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-21-2905970df12a> in <module>()
      1 l=[1,2,6,1,3,2,5]
      2 for index in range(len(l)):
----> 3     print("l[{}]={}  ".format(index,l[index]),l)
      4     if l.count(l[index])>1:
      5         l.pop(index)

IndexError: list index out of range
In [22]:
## 其實只會增加5次 (一開始的串列長度=5)
l = [1,2,3,4,5]
for index in range(len(l)):
    l.append(6)
print(l)
[1, 2, 3, 4, 5, 6, 6, 6, 6, 6]
# 造成無限迴圈,因為可以一直next()
l = [1,2,3,4,5]
for ele in l:
    l.append(6)
print(l)

補充知識-zip()、enumerate()

  • 回傳的型別皆為迭代器

zip(序列1,序列2)

  • 回傳將兩序列配對成tuple的串列
  • 若兩序列長度不一,則以最短的串列為主
In [23]:
l1 = (1,2,3,4,5)
l2 = ['a','b','c','d','e']
s = zip(l1,l2)
print(list(s))
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')]
In [24]:
l1 = [1,2,3,4,5]
l2 = ['a','b','c','d']
s = zip(l1,l2)
print(list(s))
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

應用情境-學生姓名與成績

In [25]:
l1 = ['a','b','c','d','e']
l2 = [1,2,3,4,5]
for index in range(len(l1)):
    print(l1[index],"的成績為",l2[index])
a 的成績為 1
b 的成績為 2
c 的成績為 3
d 的成績為 4
e 的成績為 5
In [26]:
l1  = ['a','b','c','d','e']
l2 = [1,2,3,4,5]
for name,score in zip(l1,l2):
    print(name,"的成績為",score)
a 的成績為 1
b 的成績為 2
c 的成績為 3
d 的成績為 4
e 的成績為 5

enumerate(序列,起始值)

  • 常用於同時取得索引與元素
  • 起始值預設為0
In [27]:
l = ['a','b','c']
for index,value in enumerate(l):
    print(index,value)
0 a
1 b
2 c
In [28]:
l = ['a','b','c']
for index,value in enumerate(l,1):
    print(index,value)
1 a
2 b
3 c
In [29]:
l = [5.5, 9, 10]
for index in range(3):
    print("員工{}的薪水".format(index+1), l[index]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [30]:
l = [5.5, 9, 10]
for index,value in enumerate(l,1):
    print("員工{}的薪水".format(index), value*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000