天天看點

【Python100天學習筆記】Day7 字元串和常用資料結構

字元串和常用資料結構

使用字元串

第二次世界大戰促使了現代電子計算機的誕生,最初計算機被應用于飛彈彈道的計算,而在計算機誕生後的很多年時間裡,計算機處理的資訊基本上都是數值型的資訊。世界上的第一台電子計算機叫ENIAC(電子數值積分計算機),誕生于美國的賓夕法尼亞大學,每秒鐘能夠完成約5000次浮點運算。随着時間的推移,雖然數值運算仍然是計算機日常工作中最為重要的事情之一,但是今天的計算機處理得更多的資料可能都是以文本的方式存在的,如果我們希望通過Python程式操作本這些文本資訊,就必須要先了解字元串類型以及與它相關的知識。

所謂字元串,就是由零個或多個字元組成的有限序列,一般記為。在Python程式中,如果我們把單個或多個字元用單引号或者雙引号包圍起來,就可以表示一個字元串。

s1 = 'hello, world!'
s2 = "hello, world!"
# 以三個雙引号或單引号開頭的字元串可以折行
s3 = """
hello, 
world!
"""
print(s1, s2, s3, end='')           

複制

可以在字元串中使用

\

(反斜杠)來表示轉義,也就是說

\

後面的字元不再是它原來的意義,例如:

\n

不是代表反斜杠和字元n,而是表示換行;而

\t

也不是代表反斜杠和字元t,而是表示制表符。是以如果想在字元串中表示

'

要寫成

\'

,同理想表示

\

要寫成

\\

。可以運作下面的代碼看看會輸出什麼。

s1 = '\'hello, world!\''
s2 = '\n\\hello, world!\\\n'
print(s1, s2, end='')           

複制

\

後面還可以跟一個八進制或者十六進制數來表示字元,例如

\141

\x61

都代表小寫字母

a

,前者是八進制的表示法,後者是十六進制的表示法。也可以在

\

後面跟Unicode字元編碼來表示字元,例如

\u9a86\u660a

代表的是中文“駱昊”。運作下面的代碼,看看輸出了什麼。

s1 = '\141\142\143\x61\x62\x63'
s2 = '\u9a86\u660a'
print(s1, s2)           

複制

如果不希望字元串中的

\

表示轉義,我們可以通過在字元串的最前面加上字母

r

來加以說明,再看看下面的代碼又會輸出什麼。

s1 = r'\'hello, world!\''
s2 = r'\n\\hello, world!\\\n'
print(s1, s2, end='')           

複制

Python為字元串類型提供了非常豐富的運算符,我們可以使用

+

運算符來實作字元串的拼接,可以使用

*

運算符來重複一個字元串的内容,可以使用

in

not in

來判斷一個字元串是否包含另外一個字元串(成員運算),我們也可以用

[]

[:]

運算符從字元串取出某個字元或某些字元(切片運算),代碼如下所示。

s1 = 'hello ' * 3
print(s1) # hello hello hello 
s2 = 'world'
s1 += s2
print(s1) # hello hello hello world
print('ll' in s1) # True
print('good' in s1) # False
str2 = 'abc123456'
# 從字元串中取出指定位置的字元(下标運算)
print(str2[2]) # c
# 字元串切片(從指定的開始索引到指定的結束索引)
print(str2[2:5]) # c12
print(str2[2:]) # c123456
print(str2[2::2]) # c246
print(str2[::2]) # ac246
print(str2[::-1]) # 654321cba
print(str2[-3:-1]) # 45           

複制

在Python中,我們還可以通過一系列的方法來完成對字元串的處理,代碼如下所示。

str1 = 'hello, world!'
# 通過内置函數len計算字元串的長度
print(len(str1)) # 13
# 獲得字元串首字母大寫的拷貝
print(str1.capitalize()) # Hello, world!
# 獲得字元串每個單詞首字母大寫的拷貝
print(str1.title()) # Hello, World!
# 獲得字元串變大寫後的拷貝
print(str1.upper()) # HELLO, WORLD!
# 從字元串中查找子串所在位置
print(str1.find('or')) # 8
print(str1.find('shit')) # -1
# 與find類似但找不到子串時會引發異常
# print(str1.index('or'))
# print(str1.index('shit'))
# 檢查字元串是否以指定的字元串開頭
print(str1.startswith('He')) # False
print(str1.startswith('hel')) # True
# 檢查字元串是否以指定的字元串結尾
print(str1.endswith('!')) # True
# 将字元串以指定的寬度居中并在兩側填充指定的字元
print(str1.center(50, '*'))
# 将字元串以指定的寬度靠右放置左側填充指定的字元
print(str1.rjust(50, ' '))
str2 = 'abc123456'
# 檢查字元串是否由數字構成
print(str2.isdigit())  # False
# 檢查字元串是否以字母構成
print(str2.isalpha())  # False
# 檢查字元串是否以數字和字母構成
print(str2.isalnum())  # True
str3 = '  [email protected] '
print(str3)
# 獲得字元串修剪左右兩側空格之後的拷貝
print(str3.strip())           

複制

我們之前講過,可以用下面的方式來格式化輸出字元串。

a, b = 5, 10
print('%d * %d = %d' % (a, b, a * b))           

複制

當然,我們也可以用字元串提供的方法來完成字元串的格式,代碼如下所示。

a, b = 5, 10
print('{0} * {1} = {2}'.format(a, b, a * b))           

複制

Python 3.6以後,格式化字元串還有更為簡潔的書寫方式,就是在字元串前加上字母

f

,我們可以使用下面的文法糖來簡化上面的代碼。

a, b = 5, 10
print(f'{a} * {b} = {a * b}')           

複制

除了字元串,Python還内置了多種類型的資料結構,如果要在程式中儲存和操作資料,絕大多數時候可以利用現有的資料結構來實作,最常用的包括清單、元組、集合和字典。

使用清單

不知道大家是否注意到,剛才我們講到的字元串類型(

str

)和之前我們講到的數值類型(

int

float

)有一些差別。數值類型是标量類型,也就是說這種類型的對象沒有可以通路的内部結構;而字元串類型是一種結構化的、非标量類型,是以才會有一系列的屬性和方法。接下來我們要介紹的清單(

list

),也是一種結構化的、非标量類型,它是值的有序序列,每個值都可以通過索引進行辨別,定義清單可以将清單的元素放在

[]

中,多個元素用

,

進行分隔,可以使用

for

循環對清單元素進行周遊,也可以使用

[]

[:]

運算符取出清單中的一個或多個元素。

下面的代碼示範了如何定義清單、如何周遊清單以及清單的下标運算。

list1 = [1, 3, 5, 7, 100]
print(list1) # [1, 3, 5, 7, 100]
# 乘号表示清單元素的重複
list2 = ['hello'] * 3
print(list2) # ['hello', 'hello', 'hello']
# 計算清單長度(元素個數)
print(len(list1)) # 5
# 下标(索引)運算
print(list1[0]) # 1
print(list1[4]) # 100
# print(list1[5])  # IndexError: list index out of range
print(list1[-1]) # 100
print(list1[-3]) # 5
list1[2] = 300
print(list1) # [1, 3, 300, 7, 100]
# 通過循環用下标周遊清單元素
for index in range(len(list1)):
    print(list1[index])
# 通過for循環周遊清單元素
for elem in list1:
    print(elem)
# 通過enumerate函數處理清單之後再周遊可以同時獲得元素索引和值
for index, elem in enumerate(list1):
    print(index, elem)           

複制

下面的代碼示範了如何向清單中添加元素以及如何從清單中移除元素。

list1 = [1, 3, 5, 7, 100]
# 添加元素
list1.append(200)
list1.insert(1, 400)
# 合并兩個清單
# list1.extend([1000, 2000])
list1 += [1000, 2000]
print(list1) # [1, 400, 3, 5, 7, 100, 200, 1000, 2000]
print(len(list1)) # 9
# 先通過成員運算判斷元素是否在清單中,如果存在就删除該元素
if 3 in list1:
	list1.remove(3)
if 1234 in list1:
    list1.remove(1234)
print(list1) # [1, 400, 5, 7, 100, 200, 1000, 2000]
# 從指定的位置删除元素
list1.pop(0)
list1.pop(len(list1) - 1)
print(list1) # [400, 5, 7, 100, 200, 1000]
# 清空清單元素
list1.clear()
print(list1) # []           

複制

和字元串一樣,清單也可以做切片操作,通過切片操作我們可以實作對清單的複制或者将清單中的一部分取出來建立出新的清單,代碼如下所示。

fruits = ['grape', 'apple', 'strawberry', 'waxberry']
fruits += ['pitaya', 'pear', 'mango']
# 清單切片
fruits2 = fruits[1:4]
print(fruits2) # apple strawberry waxberry
# 可以通過完整切片操作來複制清單
fruits3 = fruits[:]
print(fruits3) # ['grape', 'apple', 'strawberry', 'waxberry', 'pitaya', 'pear', 'mango']
fruits4 = fruits[-3:-1]
print(fruits4) # ['pitaya', 'pear']
# 可以通過反向切片操作來獲得倒轉後的清單的拷貝
fruits5 = fruits[::-1]
print(fruits5) # ['mango', 'pear', 'pitaya', 'waxberry', 'strawberry', 'apple', 'grape']           

複制

下面的代碼實作了對清單的排序操作。

list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry']
list2 = sorted(list1)
# sorted函數傳回清單排序後的拷貝不會修改傳入的清單
# 函數的設計就應該像sorted函數一樣盡可能不産生副作用
list3 = sorted(list1, reverse=True)
# 通過key關鍵字參數指定根據字元串長度進行排序而不是預設的字母表順序
list4 = sorted(list1, key=len)
print(list1)
print(list2)
print(list3)
print(list4)
# 給清單對象發出排序消息直接在清單對象上進行排序
list1.sort(reverse=True)
print(list1)           

複制

生成式和生成器

我們還可以使用清單的生成式文法來建立清單,代碼如下所示。

f = [x for x in range(1, 10)]
print(f)
f = [x + y for x in 'ABCDE' for y in '1234567']
print(f)
# 用清單的生成表達式文法建立清單容器
# 用這種文法建立清單之後元素已經準備就緒是以需要耗費較多的記憶體空間
f = [x ** 2 for x in range(1, 1000)]
print(sys.getsizeof(f))  # 檢視對象占用記憶體的位元組數
print(f)
# 請注意下面的代碼建立的不是一個清單而是一個生成器對象
# 通過生成器可以擷取到資料但它不占用額外的空間存儲資料
# 每次需要資料的時候就通過内部的運算得到資料(需要花費額外的時間)
f = (x ** 2 for x in range(1, 1000))
print(sys.getsizeof(f))  # 相比生成式生成器不占用存儲資料的空間
print(f)
for val in f:
    print(val)           

複制

除了上面提到的生成器文法,Python中還有另外一種定義生成器的方式,就是通過

yield

關鍵字将一個普通函數改造成生成器函數。下面的代碼示範了如何實作一個生成斐波拉切數列的生成器。所謂斐波拉切數列可以通過下面遞歸的方法來進行定義:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-FnPt1WnS-1638277737723)(C:/Users/VAXTIA~1/AppData/Local/Temp/BNZ.61a62257204e716/res/fibonacci-blocks.png)]

def fib(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
        yield a


def main():
    for val in fib(20):
        print(val)


if __name__ == '__main__':
    main()           

複制

使用元組

Python中的元組與清單類似也是一種容器資料類型,可以用一個變量(對象)來存儲多個資料,不同之處在于元組的元素不能修改,在前面的代碼中我們已經不止一次使用過元組了。顧名思義,我們把多個元素組合到一起就形成了一個元組,是以它和清單一樣可以儲存多條資料。下面的代碼示範了如何定義和使用元組。

# 定義元組
t = ('駱昊', 38, True, '四川成都')
print(t)
# 擷取元組中的元素
print(t[0])
print(t[3])
# 周遊元組中的值
for member in t:
    print(member)
# 重新給元組指派
# t[0] = '王大錘'  # TypeError
# 變量t重新引用了新的元組原來的元組将被垃圾回收
t = ('王大錘', 20, True, '雲南昆明')
print(t)
# 将元組轉換成清單
person = list(t)
print(person)
# 清單是可以修改它的元素的
person[0] = '李小龍'
person[1] = 25
print(person)
# 将清單轉換成元組
fruits_list = ['apple', 'banana', 'orange']
fruits_tuple = tuple(fruits_list)
print(fruits_tuple)           

複制

這裡有一個非常值得探讨的問題,我們已經有了清單這種資料結構,為什麼還需要元組這樣的類型呢?

  1. 元組中的元素是無法修改的,事實上我們在項目中尤其是多線程環境(後面會講到)中可能更喜歡使用的是那些不變對象(一方面因為對象狀态不能修改,是以可以避免由此引起的不必要的程式錯誤,簡單的說就是一個不變的對象要比可變的對象更加容易維護;另一方面因為沒有任何一個線程能夠修改不變對象的内部狀态,一個不變對象自動就是線程安全的,這樣就可以省掉處理同步化的開銷。一個不變對象可以友善的被共享通路)。是以結論就是:如果不需要對元素進行添加、删除、修改的時候,可以考慮使用元組,當然如果一個方法要傳回多個值,使用元組也是不錯的選擇。
  2. 元組在建立時間和占用的空間上面都優于清單。我們可以使用sys子產品的getsizeof函數來檢查存儲同樣的元素的元組和清單各自占用了多少記憶體空間,這個很容易做到。我們也可以在ipython中使用魔法指令%timeit來分析建立同樣内容的元組和清單所花費的時間,下圖是我的macOS系統上測試的結果。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-BtQXl75S-1638277737725)(C:/Users/VAXTIA~1/AppData/Local/Temp/BNZ.61a62257204e716/res/ipython-timeit.png)]

使用集合

Python中的集合跟數學上的集合是一緻的,不允許有重複元素,而且可以進行交集、并集、差集等運算。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-R1B4KFHj-1638277737726)(C:/Users/VAXTIA~1/AppData/Local/Temp/BNZ.61a62257204e716/res/python-set.png)]

可以按照下面代碼所示的方式來建立和使用集合。

# 建立集合的字面量文法
set1 = {1, 2, 3, 3, 3, 2}
print(set1)
print('Length =', len(set1))
# 建立集合的構造器文法(面向對象部分會進行詳細講解)
set2 = set(range(1, 10))
set3 = set((1, 2, 3, 3, 2, 1))
print(set2, set3)
# 建立集合的推導式文法(推導式也可以用于推導集合)
set4 = {num for num in range(1, 100) if num % 3 == 0 or num % 5 == 0}
print(set4)           

複制

向集合添加元素和從集合删除元素。

set1.add(4)
set1.add(5)
set2.update([11, 12])
set2.discard(5)
if 4 in set2:
    set2.remove(4)
print(set1, set2)
print(set3.pop())
print(set3)           

複制

集合的成員、交集、并集、差集等運算。

# 集合的交集、并集、差集、對稱差運算
print(set1 & set2)
# print(set1.intersection(set2))
print(set1 | set2)
# print(set1.union(set2))
print(set1 - set2)
# print(set1.difference(set2))
print(set1 ^ set2)
# print(set1.symmetric_difference(set2))
# 判斷子集和超集
print(set2 <= set1)
# print(set2.issubset(set1))
print(set3 <= set1)
# print(set3.issubset(set1))
print(set1 >= set2)
# print(set1.issuperset(set2))
print(set1 >= set3)
# print(set1.issuperset(set3))           

複制

說明: Python中允許通過一些特殊的方法來為某種類型或資料結構自定義運算符(後面的章節中會講到),上面的代碼中我們對集合進行運算的時候可以調用集合對象的方法,也可以直接使用對應的運算符,例如

&

運算符跟intersection方法的作用就是一樣的,但是使用運算符讓代碼更加直覺。

使用字典

字典是另一種可變容器模型,Python中的字典跟我們生活中使用的字典是一樣一樣的,它可以存儲任意類型對象,與清單、集合不同的是,字典的每個元素都是由一個鍵和一個值組成的“鍵值對”,鍵和值通過冒号分開。下面的代碼示範了如何定義和使用字典。

# 建立字典的字面量文法
scores = {'駱昊': 95, '白元芳': 78, '狄仁傑': 82}
print(scores)
# 建立字典的構造器文法
items1 = dict(one=1, two=2, three=3, four=4)
# 通過zip函數将兩個序列壓成字典
items2 = dict(zip(['a', 'b', 'c'], '123'))
# 建立字典的推導式文法
items3 = {num: num ** 2 for num in range(1, 10)}
print(items1, items2, items3)
# 通過鍵可以擷取字典中對應的值
print(scores['駱昊'])
print(scores['狄仁傑'])
# 對字典中所有鍵值對進行周遊
for key in scores:
    print(f'{key}: {scores[key]}')
# 更新字典中的元素
scores['白元芳'] = 65
scores['諸葛王朗'] = 71
scores.update(冷面=67, 方啟鶴=85)
print(scores)
if '武則天' in scores:
    print(scores['武則天'])
print(scores.get('武則天'))
# get方法也是通過鍵擷取對應的值但是可以設定預設值
print(scores.get('武則天', 60))
# 删除字典中的元素
print(scores.popitem())
print(scores.popitem())
print(scores.pop('駱昊', 100))
# 清空字典
scores.clear()
print(scores)           

複制

練習

練習1:在螢幕上顯示跑馬燈文字。

參考答案:

import os
import time


def main():
    content = '北京歡迎你為你開天辟地…………'
    while True:
        # 清理螢幕上的輸出
        os.system('cls')  # os.system('clear')
        print(content)
        # 休眠200毫秒
        time.sleep(0.2)
        content = content[1:] + content[0]


if __name__ == '__main__':
    main()           

複制

練習2:設計一個函數産生指定長度的驗證碼,驗證碼由大小寫字母和數字構成。

參考答案:

import random


def generate_code(code_len=4):
    """
    生成指定長度的驗證碼

    :param code_len: 驗證碼的長度(預設4個字元)

    :return: 由大小寫英文字母和數字構成的随機驗證碼
    """
    all_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    last_pos = len(all_chars) - 1
    code = ''
    for _ in range(code_len):
        index = random.randint(0, last_pos)
        code += all_chars[index]
    return code           

複制

練習3:設計一個函數傳回給定檔案名的字尾名。

參考答案:

def get_suffix(filename, has_dot=False):
    """
    擷取檔案名的字尾名

    :param filename: 檔案名
    :param has_dot: 傳回的字尾名是否需要帶點
    :return: 檔案的字尾名
    """
    pos = filename.rfind('.')
    if 0 < pos < len(filename) - 1:
        index = pos if has_dot else pos + 1
        return filename[index:]
    else:
        return ''           

複制

練習4:設計一個函數傳回傳入的清單中最大和第二大的元素的值。

參考答案:

def max2(x):
    m1, m2 = (x[0], x[1]) if x[0] > x[1] else (x[1], x[0])
    for index in range(2, len(x)):
        if x[index] > m1:
            m2 = m1
            m1 = x[index]
        elif x[index] > m2:
            m2 = x[index]
    return m1, m2           

複制

練習5:計算指定的年月日是這一年的第幾天。

參考答案:

def is_leap_year(year):
    """
    判斷指定的年份是不是閏年

    :param year: 年份
    :return: 閏年傳回True平年傳回False
    """
    return year % 4 == 0 and year % 100 != 0 or year % 400 == 0


def which_day(year, month, date):
    """
    計算傳入的日期是這一年的第幾天

    :param year: 年
    :param month: 月
    :param date: 日
    :return: 第幾天
    """
    days_of_month = [
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    ][is_leap_year(year)]
    total = 0
    for index in range(month - 1):
        total += days_of_month[index]
    return total + date


def main():
    print(which_day(1980, 11, 28))
    print(which_day(1981, 12, 31))
    print(which_day(2018, 1, 1))
    print(which_day(2016, 3, 1))


if __name__ == '__main__':
    main()           

複制

練習6:列印楊輝三角。

參考答案:

def main():
    num = int(input('Number of rows: '))
    yh = [[]] * num
    for row in range(len(yh)):
        yh[row] = [None] * (row + 1)
        for col in range(len(yh[row])):
            if col == 0 or col == row:
                yh[row][col] = 1
            else:
                yh[row][col] = yh[row - 1][col] + yh[row - 1][col - 1]
            print(yh[row][col], end='\t')
        print()


if __name__ == '__main__':
    main()           

複制

綜合案例

案例1:雙色球選号。

from random import randrange, randint, sample


def display(balls):
    """
    輸出清單中的雙色球号碼
    """
    for index, ball in enumerate(balls):
        if index == len(balls) - 1:
            print('|', end=' ')
        print('%02d' % ball, end=' ')
    print()


def random_select():
    """
    随機選擇一組号碼
    """
    red_balls = [x for x in range(1, 34)]
    selected_balls = []
    selected_balls = sample(red_balls, 6)
    selected_balls.sort()
    selected_balls.append(randint(1, 16))
    return selected_balls


def main():
    n = int(input('機選幾注: '))
    for _ in range(n):
        display(random_select())


if __name__ == '__main__':
    main()           

複制

說明: 上面使用random子產品的sample函數來實作從清單中選擇不重複的n個元素。

綜合案例2:約瑟夫環問題。

"""
《幸運的基督徒》
有15個基督徒和15個非基督徒在海上遇險,為了能讓一部分人活下來不得不将其中15個人扔到海裡面去,有個人想了個辦法就是大家圍成一個圈,由某個人開始從1報數,報到9的人就扔到海裡面,他後面的人接着從1開始報數,報到9的人繼續扔到海裡面,直到扔掉15個人。由于上帝的保佑,15個基督徒都幸免于難,問這些人最開始是怎麼站的,哪些位置是基督徒哪些位置是非基督徒。
"""


def main():
    persons = [True] * 30
    counter, index, number = 0, 0, 0
    while counter < 15:
        if persons[index]:
            number += 1
            if number == 9:
                persons[index] = False
                counter += 1
                number = 0
        index += 1
        index %= 30
    for person in persons:
        print('基' if person else '非', end='')


if __name__ == '__main__':
    main()           

複制

綜合案例3:井字棋遊戲。

import os


def print_board(board):
    print(board['TL'] + '|' + board['TM'] + '|' + board['TR'])
    print('-+-+-')
    print(board['ML'] + '|' + board['MM'] + '|' + board['MR'])
    print('-+-+-')
    print(board['BL'] + '|' + board['BM'] + '|' + board['BR'])


def main():
    init_board = {
        'TL': ' ', 'TM': ' ', 'TR': ' ',
        'ML': ' ', 'MM': ' ', 'MR': ' ',
        'BL': ' ', 'BM': ' ', 'BR': ' '
    }
    begin = True
    while begin:
        curr_board = init_board.copy()
        begin = False
        turn = 'x'
        counter = 0
        os.system('clear')
        print_board(curr_board)
        while counter < 9:
            move = input('輪到%s走棋, 請輸入位置: ' % turn)
            if curr_board[move] == ' ':
                counter += 1
                curr_board[move] = turn
                if turn == 'x':
                    turn = 'o'
                else:
                    turn = 'x'
            os.system('clear')
            print_board(curr_board)
        choice = input('再玩一局?(yes|no)')
        begin = choice == 'yes'


if __name__ == '__main__':
    main()           

複制

說明: 最後這個案例來自《Python程式設計快速上手:讓繁瑣工作自動化》一書(這本書對有程式設計基礎想迅速使用Python将日常工作自動化的人來說還是不錯的選擇),對代碼做了一點點的調整。