1. 程式人生 > 實用技巧 >更優雅地在列表, 字典, 集合中篩選資料

更優雅地在列表, 字典, 集合中篩選資料

通常我們在列表、字典或集合等容器型別中進行條件篩選時,都是使用遍歷+判斷的方式來實現。這種實現方式的實現邏輯非常簡單,但實現的效率卻比較低,程式碼寫起來也比較麻煩。我們來看下面這個例子,篩選出列表d中小於0的數:

d = [-1, 10, -2, 3, 4, 7, -9]
result = []
for num in d:
if num < 0:
result.append(num)

print(result)
# 結果:[-1, -2, -9]

這裡,我們首先用了一個for迴圈,然後又用了一個if判斷對每一個數進行判斷,最終才得到符合條件的結果。Python一向追求程式碼簡潔、優美、高效,那有沒有更加簡單的方法可以實現這個需求呢?答案其實有的,我們可以利用更加Pythonic的方式來解決這個問題。

在Python中,我們有一個叫列表推導式的東西,相信很多瞭解Python的人應該都知道。除了列表推導式,當然還有字典推導式、元組推導式、集合推導式等。推導式的表示法非常簡潔,並且Python在其內部已經對演算法進行了大量的優化,所以推導式執行起來比單純的迴圈要快很多,並且語法也更加優雅。那麼,接下來我們可以嘗試使用列表推導式來求實現上面的那個效果。程式碼如下:

d = [-1, 10, -2, 3, 4, 7, -9]
result = [x for x in d if x < 0]
print(result)
# 結果:[-1, -2, -9]

核心的程式碼縮短到了只有一行就搞定了,效率非常高。至於執行效率,為了看到效果,我們可以使用一個稍微大一點的列表做個試驗,比如利用random生成1000000個-10到10之間的隨機數,程式碼如下:

import random
import time


d = [random.randint(-10, 10) for _ in range(1000000)]
result = []
start = time.time()
for num in d:
if num < 0:
result.append(num)
print('執行時間為:', time.time()-start)

# 執行時間約為0.15秒左右

如果用列表推導式來實現,程式碼如下:

import random
import time


d = [random.randint(-10, 10) for _ in range(1000000)]

start = time.time()
result = [x for x in d if x < 0]
print('執行時間為:', time.time()-start)

# 執行時間約為0.05秒左右

可見,列表推導式無論從編寫程式碼的效率還是執行效率上,都更勝一籌。

除了列表推導式,還有字典推導式也是同樣的道理。我們也來看看如何通過字典推導式來實現字典中特定元素的過濾。假如我們現在有一個學生成績的字典,裡面有20個學生的成績資訊,要求用字典推導式篩選出所有分數在90分以上的學生,我們的程式碼就可以這樣寫:

import random

d = {'student{}'.format(i): random.randint(60, 100) for i in range(1, 21)}
result = {k: v for k, v in d.items() if v > 90}
print(result)

除了推導式之外,Python內建的filter函式也是專門用來篩選容器型別的資料結構裡的元素的。通常我們在使用filter函式的時候,需要傳兩個引數,一個用於篩選元素的函式指標,一般我們會定義一個lambda匿名函式。另一個引數是被篩選的容器物件,比如一個字典或一個列表。另外還要注意一點就是,在Python3中,filter函式返回的是一個生成器物件,如果想要得到一個列表或者是字典,必須做一次強制轉換。

接下來,我們以列表為例,用filter來實現第一個列表推導式篩選元素的例子,程式碼就應該這樣來寫:

d = [random.randint(-10, 10) for _ in range(20)]
result = list(filter(lambda x: x < 0, d))
print(result)

上面的程式碼執行結果跟前面使用列表推導式是一模一樣的,但filter的執行效率沒有列表推導式高。另外,再次強調一下,filter中提供的第一個引數是一個函式指標,我們一般會直接定義一個lambda表示式。對於這個lambda表示式有兩點必須注意,一是它必須返回一個布林值作為結果,凡是結果為True的,就是要留下的,結果為False的就是要被篩掉的。二是這裡的lambda表示式只接受一個引數(lambda本身是可以接收多個引數的,但這裡只能接收一個)。那麼這個引數是怎麼來的呢,是誰提供的呢?答案是這個引數將由filter函式的第二個引數,也就是那個容器物件自動提供,所以每次比較的值就是容器物件內的一個元素,以實現依次比較的目的。

明白了這些問題之後,要實現利用filter函式來實現字典元素的篩選,也是很容易的一件事了,程式碼如下:

import random

d = {'student{}'.format(i): random.randint(60, 100) for i in range(1, 21)}
result = dict(filter(lambda item: item[1] > 90, d.items()))
print(result)

上面的程式碼中,lambda裡面的item為什麼要取item[1]呢?因為前面我們說過,lambda只接收一個引數,所以字典中的元素的鍵和值是以一個整體的形式來傳入的。而我們要比較的是值,所以就要通過item[1]取出字典元素的值來進行運算。

綜上,在進行列表、字典或集合等容器型別資料結構的簡單元素篩選操作時,我們完全不需要呆板地利用迴圈遍歷+判斷的方式來寫程式碼實現,畢竟那一點都不Pythonic。Python一向是優雅、高效、簡潔的代名詞,所以多瞭解一些更簡潔的Pythonic式的用法是很有必要的。寫Python是不擔心脫髮問題的,但前提得是你的程式碼要足夠優雅和簡潔,否則,老是想著用Java的方式來解決Python的問題,那神仙都幫不了你了。

歡迎關注我的公眾號,學習更多優質內容!