演算法金 | 推導式、生成器、向量化、map、filter、reduce、itertools,再見 for 迴圈

算法金「全网同名」發表於2024-07-07


大俠幸會,在下全網同名「演算法金」 0 基礎轉 AI 上岸,多個演算法賽 Top 「日更萬日,讓更多人享受智慧樂趣」

不要輕易使用 For 迴圈

For 迴圈,老鐵們在程式設計中經常用到的一個基本結構,特別是在處理列表、字典這類資料結構時。但是,這東西真的是個雙刃劍。雖然看起來挺直白,一用就上手,但是,有時候用多了,問題也跟著來了。

效能問題

首先得說說效能問題。鐵子們可能都有感覺,當你的資料量一大起來,用 For 迴圈去跑,這速度簡直能讓人急死。因為 For 迴圈處理大資料集時,每次迭代都要進行函式呼叫,這中間的開銷可不小。尤其是在 Python 這樣的解釋型語言裡,每一次迴圈的效率都非常關鍵。

可讀性問題

再來看看可讀性問題。當一個 For 迴圈巢狀多層,程式碼就開始變得難以理解。尤其是對於一些初學者或者維護別人程式碼的鐵子們,一大堆的迴圈層層疊疊,看著就頭大。

複雜度問題

最後是複雜度問題。很多時候,複雜的 For 迴圈邏輯可以透過更簡單的方式實現。比如說列表推導式、map() 或者 filter() 這些函式,它們不僅程式碼更簡潔,執行效率也往往比 For 迴圈高。

所以,老鐵們,別看 For 迴圈簡單易用,有時候在處理複雜或者大規模資料時,還是要斟酌一下,看看有沒有更合適的工具。接下來,我們將介紹一些這樣的替代工具,讓你的程式碼不僅跑得快,而且更加清晰易懂。

1. 列表推導式

說到替代 For 迴圈的利器,怎能不提列表推導式呢?這貨不僅寫法簡潔,而且執行效率高,是處理列表資料時的一大神器。

基本用法

列表推導式的基本形式是 [表示式 for 變數 in 可迭代物件]。比如說,我們要獲取一個列表中所有元素的平方,如果用 For 迴圈可能要寫幾行,用列表推導式,一行程式碼就搞定了:

squares = [x**2 for x in range(10)]

適用場景

列表推導式特別適用於從一個列表生成另一個列表的場景。只要是能透過一行表示式解決的問題,都可以考慮用列表推導式。它不僅能簡化程式碼,還能減少編寫錯誤的機會。

示例程式碼

來個更實際的例子,假設我們要從一組數字中篩選出所有偶數,並計算它們的三次方。用 For 迴圈也能做,但用列表推導式,既快又直觀:

cubes_of_evens = [x**3 for x in range(20) if x % 2 == 0]

老鐵,看到沒?這種方式不僅程式碼量少,而且一眼就能看懂做了啥,是不是比那些巢狀的 For 迴圈清爽多了?下面,我們來看看更高階一點的工具,也就是生成器表示式,這也是處理資料時的一把利器。

2. 生成器表示式

當談到處理大資料集或者想要記憶體使用更加高效時,生成器表示式就跳出來說:“鐵子們,看我的!”

基本用法

生成器表示式在形式上與列表推導式很相似,但它是用圓括號包裹起來的,不是方括號。生成器表示式不會一次性生成所有元素,而是生成一個生成器物件,每次迭代時才計算下一個值。這樣做的好處是,記憶體利用率高,特別適合處理大規模資料集。

squares = (x**2 for x in range(1000000))

這行程式碼建立了一個生成器,可以逐個產生一百萬個數的平方,但這些平方並不會同時存在記憶體中。

優勢與劣勢

生成器表示式的優勢顯而易見:記憶體效率高,懶載入,適合大資料處理。但劣勢也很明顯,它不如列表推導式直觀,而且只能迭代一次,用完就沒了,需要重新生成。

示例程式碼

假設我們需要計算大資料集中所有偶數的平方和,用生成器表示式來實現這一功能既節省記憶體又有效率:

sum_of_squares = sum(x**2 for x in range(1000000) if x % 2 == 0)

是不是感覺生成器表示式像是那種默默無聞的英雄,不佔記憶體的 spotlight,但能大顯身手呢?

3. map() 函式

接下來聊聊 map() 函式,這個函式在 Python 裡面算是老江湖了,特別擅長批次處理資料。

基本用法

map() 函式的基本思路是將一個函式應用到一個序列的所有元素上。這聽起來有點像 For 迴圈,但實際上 map() 更高效、更直接。基本語法是 map(function, iterable),它返回一個迭代器。

def square(x):
    return x * x

# 使用 map() 應用函式
squares = map(square, range(10))

適用場景

map() 函式非常適合那些需要對資料集中的每個元素都執行同一個操作的場景。它比手寫迴圈快,因為 map() 通常是用 C 語言實現的,執行效率更高。

示例程式碼

來個實際點的,比如我們要把一系列字串轉換成它們的長度:

lengths = list(map(len, ["apple", "banana", "cherry"]))

看,老鐵,只需要簡單一行程式碼,沒有複雜的迴圈,邏輯清晰,效率也棒棒的

4. filter() 函式

緊接著 map(),我們來談談 filter() 函式。這個函式就像它的名字那樣,專門用來篩選東西,特別適合從一堆資料中過濾出我們需要的那部分。

基本用法

filter() 函式的作用是從一個序列中過濾出符合條件的元素,形成一個新的迭代器。它的基本語法是 filter(function, iterable),其中 function 是一個返回布林值的函式,用來測試每個元素是否應該包含在新的迭代器中。

def is_even(x):
    return x % 2 == 0

# 使用 filter() 篩選偶數
evens = filter(is_even, range(10))

適用場景

filter() 函式最適合的場景是需要根據某些條件從列表或其他可迭代物件中選擇元素的情況。它可以幫助清潔資料,或者準備資料分析階段。

示例程式碼

比如說,我們有一列表,想要過濾出裡面所有正數的元素:

positives = list(filter(lambda x: x > 0, [-5, 3, -1, 9, 0, -2]))

老鐵們,是不是感覺 filter() 就像是資料處理的一把銳利的劍,能精準地割掉那些不需要的部分?

5. reduce() 函式

接下來講講 reduce() 函式,這個函式可能不像 map() 或 filter() 那樣常用,但在需要對列表中的所有元素進行一些累積操作時,reduce() 就能大顯身手了。

基本用法

reduce() 函式位於 functools 模組中,它的作用是將一個接受兩個引數的函式累積地應用到序列的元素上,從而將序列減少為單一的值。基本語法是 reduce(function, iterable[, initializer])。

from functools import reduce

def add(x, y):
    return x + y

# 使用 reduce() 計算元素總和
total = reduce(add, [1, 2, 3, 4, 5])

適用場景

reduce() 特別適合於需要對一系列資料執行累積操作的情況,比如求和、求乘積或者其他需要從一系列值得到單一結果的操作。

示例程式碼

來個更具體的例子,比如我們要找出一組數中的最大值,可以使用 reduce():

max_value = reduce(lambda x, y: x if x > y else y, [7, 22, 5, 13, 27])

函式——reduce(),雖然它不是 Python 標準庫的一部分,但使用起來效果槓槓的,尤其在進行資料累積處理時。

6. itertools 模組

itertools 模組中包含了多種用於構建迭代器的工具,這些工具可以幫助我們高效地處理資料,特別是在需要組合資料、過濾資料或累積資料時。

itertools.starmap

starmap 函式類似於 map(),但它允許函式接受多個引數。所以,當你有一個引數列表的列表(或其他可迭代的序列)時,starmap() 可以非常方便地應用一個多引數函式。

from itertools import starmap

def add(x, y):
    return x + y

# 使用 starmap 來計算多個數對的和
result = list(starmap(add, [(1, 2), (3, 4), (5, 6)]))

這段程式碼透過 starmap 直接將 add 函式應用於每一對元組,使得程式碼更加簡潔。

itertools.accumulate

accumulate 函式用來計算累積的中間結果,可以非常直觀地看到從第一個元素到當前元素的累積結果。預設情況下,它提供累加的功能,但你也可以指定其他的二元函式。

from itertools import accumulate
import operator

# 累加
accumulated_sums = list(accumulate([1, 2, 3, 4, 5]))
# 累乘
accumulated_products = list(accumulate([1, 2, 3, 4, 5], operator.mul))

這兩個示例展示瞭如何使用 accumulate 來進行累加和累乘,它們提供了一種非常直觀的方式來處理序列的累積計算。

itertools 模組的這些工具在處理複雜的迭代任務時非常有用,它們可以幫助我們寫出更高效、更簡潔的程式碼。利用這些工具,你可以最佳化你的資料處理流程,提高程式碼的執行效率。

7. NumPy 向量化操作

跳進資料科學的大門,怎能不提 NumPy 的向量化操作?在處理數值資料時,這技能簡直是利器。

基本概念

向量化操作指的是直接對陣列進行操作,而不是逐個元素進行。這種方法利用了 NumPy 的內部最佳化,能顯著提升計算速度。用 NumPy 來說,就是把那些通常需要在迴圈中逐個處理的任務,轉換為整體操作,讓整個陣列一次性處理。

import numpy as np

# 建立一個陣列
arr = np.array([1, 2, 3, 4, 5])

# 計算每個元素的平方
squares = arr ** 2

效能優勢

NumPy 的向量化操作由底層的 C 語言支援,執行速度遠快於 Python 的迴圈。這不僅減少了執行時間,還能在處理大型資料集時節省大量資源。

示例程式碼

比如說,我們需要計算兩個陣列的點積,直接用 NumPy 的向量化方式就可以簡潔高效地完成:

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 計算點積
dot_product = np.dot(a, b)

看到沒有老鐵?這操作簡直快如閃電。而且程式碼整潔到不行,看著都是一種享受。

8. Pandas 向量化操作

繼 NumPy 之後,Pandas 在資料處理界也是個大腕兒。它的向量化操作專門針對表格資料,效率和功能都一流。

基本概念

Pandas 向量化操作主要是指對 DataFrame 或 Series 物件進行的操作,這些操作不需要顯式的迴圈。就像 NumPy,Pandas 的操作也是建立在底層的 C 語言最佳化之上,所以速度很快,特別是在處理大型資料集時。

import pandas as pd

# 建立一個 DataFrame
df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6]
})

# 計算每個元素的平方
df_squared = df ** 2

效能優勢

使用 Pandas 的向量化操作,可以顯著提高資料處理速度,並減少程式碼的複雜度。當處理成千上萬條記錄時,這種優勢尤為明顯。

示例程式碼

來看一個實用的例子,比如我們要根據一列的條件快速過濾資料:

# 建立一個較大的 DataFrame
large_df = pd.DataFrame({
    'Age': [22, 45, 18, 25, 30],
    'Salary': [35000, 80000, 27000, 32000, 40000]
})

# 過濾出年齡大於 24 歲的記錄
filtered_df = large_df[large_df['Age'] > 24]

老鐵們,Pandas 的向量化操作讓資料篩選、轉換這些任務變得簡單又快速。處理表格資料時,它簡直是得力助手。

9. 並行處理

在處理大規模資料或需要高效能運算時,單純依靠向量化操作有時還不夠,這時並行處理就閃亮登場了。並行處理能讓我們把任務分散到多個處理器上,實現真正的同時執行,大幅提升效率。

基本概念

並行處理意味著同時執行多個計算任務。這通常透過多執行緒或多程序實現,每個執行緒或程序處理資料的一個部分。Python 中有多種方式來實現並行處理,包括使用 threading 和 multiprocessing 庫。

from multiprocessing import Pool

def square(number):
    return number * number

# 建立一個程序池
with Pool(4) as p:
    results = p.map(square, range(10))

使用方法

並行處理適用於計算密集型任務,或者當任務可以被自然地分解成多個獨立部分時。正確使用並行處理可以顯著減少程式的執行時間。

示例程式碼

比如,我們需要處理一個大資料集,每個資料點需要進行復雜計算,可以將資料分批處理:

import numpy as np
from multiprocessing import Pool

# 大資料集
data = np.random.rand(10000)

# 複雜計算的函式
def complex_calculation(x):
    return np.sin(x) ** 2 + np.cos(x) ** 2

# 使用程序池
with Pool(8) as p:
    results = p.map(complex_calculation, data)

透過並行處理,我們可以更高效地利用計算資源,處理那些讓單核 CPU 望而卻步的重任務。這就是現代程式設計的一種趨勢,特別是在資料科學和機器學習領域,有效地利用並行處理技術可以大大加速資料處理和模型訓練過程。

[ 抱個拳,總個結 ]

在程式設計中,替換 For 迴圈的方法有很多,每種方法都有其適用的場景。選擇合適的方法不僅能提升程式碼的執行效率,還能增強程式碼的可讀性和可維護性。

根據具體需求選擇

老鐵們,選擇替代方法的時候,首先得考慮你的具體需求。比如,如果處理的是大資料集,並且對效能要求極高,可能向量化操作或並行處理會更合適。如果是簡單的資料轉換,列表推導式或 map() 函式可能就足夠了。

考慮程式碼的可讀性

程式碼的可讀性是軟體開發中的關鍵。選擇那些能讓其他開發者一看就懂的方法,可以減少未來維護的難度。比如,列表推導式因其簡潔性通常比傳統的 For 迴圈更易讀,但如果推導式變得過於複雜,可能就得考慮回到更基本的迴圈結構,或者使用函式來提高畫質晰度。

效能最佳化的注意事項

在進行效能最佳化時,別忘了測試和驗證你的選擇是否真的提升了效能。有時候,一些看似高效的方法(如並行處理)可能因為引入的額外開銷而未必帶來預期的效能提升。使用像 Python 的 timeit 模組這樣的工具來量化不同方法的效能,可以幫助你做出更明智的選擇。

老鐵們,選對工具,事半功倍。希望這些建議能幫你們在實際工作中做出更好的技術選擇,寫出更優雅、更高效的程式碼。如果還有其他想了解的,儘管問!

- 科研為國分憂,創新與民造福 -

日更時間緊任務急,難免有疏漏之處,還請大俠海涵內容僅供學習交流之用,部分素材來自網路,侵聯刪

[ 演算法金,碎碎念 ]

應對不確定,最確定的方法是做好確定的事

確定的,比如,基礎很重要;

確定的,比如,人是會掛的;

大俠,你說還是什麼是確定的且是需要被特別關照的呢?

全網同名,日更萬日,讓更多人享受智慧樂趣

如果覺得內容有價值,煩請大俠多多 分享、在看、點贊,助力演算法金又猛又持久、很黃很 BL 的日更下去;

同時邀請大俠 關注、星標 演算法金,圍觀日更萬日,助你功力大增、笑傲江湖

相關文章