摘要:在上一篇如何給列表降維函數(shù)的妙用中,我們介紹了這個用法,還對函數(shù)做了擴展的學(xué)習(xí)。是的,函數(shù)做列表降維有奇效,但它性能堪憂,并不是最好的選擇。這正是函數(shù)出于一致性考慮,而舍棄掉的實現(xiàn)方案。
本文原創(chuàng)并首發(fā)于公眾號【Python貓】,未經(jīng)授權(quán),請勿轉(zhuǎn)載。
原文地址:https://mp.weixin.qq.com/s/mK1nav2vKykZaKw_TY-rtw
Python 的內(nèi)置函數(shù) sum() 可以接收兩個參數(shù),當(dāng)?shù)谝粋€參數(shù)是二維列表,第二個參數(shù)是一維列表的時候,它可以實現(xiàn)列表降維的效果。
在上一篇《如何給列表降維?sum()函數(shù)的妙用》中,我們介紹了這個用法,還對 sum() 函數(shù)做了擴展的學(xué)習(xí)。
那篇文章發(fā)布后,貓哥收到了一些很有價值的反饋,不僅在知識面上獲得了擴充,在思維能力上也得到了一些啟發(fā),因此,我決定再寫一篇文章,繼續(xù)跟大家聊聊 sum() 函數(shù)以及列表降維。若你讀后有所啟發(fā),歡迎留言與我交流。
有些同學(xué)表示,沒想到 sum() 函數(shù)竟然可以這么用,漲見識了!貓哥最初在交流群里看到這種用法時,也有同樣的想法。整理成文章后,能得到別人的認可,我非常開心。
學(xué)到新東西,進行分享,最后令讀者也有所獲,這鼓舞了我——應(yīng)該每日精進,并把所學(xué)分享出去。
也有的同學(xué)早已知道 sum() 的這個用法,還指出它的性能并不好,不建議使用。這是我不曾考慮到的問題,但又不得不認真對待。
是的,sum() 函數(shù)做列表降維有奇效,但它性能堪憂,并不是最好的選擇。
因此,本文想繼續(xù)探討的話題是:(1)sum() 函數(shù)的性能到底差多少,為什么會差?(2)既然 sum() 不是最好的列表降維方法,那是否有什么替代方案呢?
在 stackoverflow 網(wǎng)站上,有人問了個“How to make a flat list out of list of lists”問題,正是我們在上篇文章中提出的問題。在回答中,有人分析了 7 種方法的時間性能。
先看看測試代碼:
import functools import itertools import numpy import operator import perfplot def forfor(a): return [item for sublist in a for item in sublist] def sum_brackets(a): return sum(a, []) def functools_reduce(a): return functools.reduce(operator.concat, a) def functools_reduce_iconcat(a): return functools.reduce(operator.iconcat, a, []) def itertools_chain(a): return list(itertools.chain.from_iterable(a)) def numpy_flat(a): return list(numpy.array(a).flat) def numpy_concatenate(a): return list(numpy.concatenate(a)) perfplot.show( setup=lambda n: [list(range(10))] * n, kernels=[ forfor, sum_brackets, functools_reduce, functools_reduce_iconcat, itertools_chain, numpy_flat, numpy_concatenate ], n_range=[2**k for k in range(16)], logx=True, logy=True, xlabel="num lists" )
代碼囊括了最具代表性的 7 種解法,使用了 perfplot (注:這是該測試者本人開發(fā)的庫)作可視化,結(jié)果很直觀地展示出,隨著數(shù)據(jù)量的增加,這幾種方法的效率變化。
從測試圖中可看出,當(dāng)數(shù)據(jù)量小于 10 的時候,sum() 函數(shù)的效率很高,但是,隨著數(shù)據(jù)量增長,它所花的時間就出現(xiàn)劇增,遠遠超過了其它方法的損耗。
值得注意的是,functools_reduce 方法的性能曲線幾乎與 sum_brackets 重合。
在另一個回答中,有人也做了 7 種方法的性能測試(巧合的是,所用的可視化庫也是測試者自己開發(fā)的),在這幾種方法中,functools.reduce 結(jié)合 lambda 函數(shù),雖然寫法不同,它的時間效率與 sum() 函數(shù)也基本重合:
from itertools import chain from functools import reduce from collections import Iterable # or from collections.abc import Iterable import operator from iteration_utilities import deepflatten def nested_list_comprehension(lsts): return [item for sublist in lsts for item in sublist] def itertools_chain_from_iterable(lsts): return list(chain.from_iterable(lsts)) def pythons_sum(lsts): return sum(lsts, []) def reduce_add(lsts): return reduce(lambda x, y: x + y, lsts) def pylangs_flatten(lsts): return list(flatten(lsts)) def flatten(items): """Yield items from any nested iterable; see REF.""" for x in items: if isinstance(x, Iterable) and not isinstance(x, (str, bytes)): yield from flatten(x) else: yield x def reduce_concat(lsts): return reduce(operator.concat, lsts) def iteration_utilities_deepflatten(lsts): return list(deepflatten(lsts, depth=1)) from simple_benchmark import benchmark b = benchmark( [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add, pylangs_flatten, reduce_concat, iteration_utilities_deepflatten], arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)}, argument_name="number of inner lists" ) b.plot()
這就證實了兩點:sum() 函數(shù)確實性能堪憂;它的執(zhí)行效果實際是每個子列表逐一相加(concat)。
那么,問題來了,拖慢 sum() 函數(shù)性能的原因是啥呢?
在它的實現(xiàn)源碼中,我找到了一段注釋:
/* It"s tempting to use PyNumber_InPlaceAdd instead of PyNumber_Add here, to avoid quadratic running time when doing "sum(list_of_lists, [])". However, this would produce a change in behaviour: a snippet like empty = [] sum([[x] for x in range(10)], empty) would change the value of empty. */
為了不改變 sum() 函數(shù)的第二個參數(shù)值,CPython 沒有采用就地相加的方法(PyNumber_InPlaceAdd),而是采用了較耗性能的普通相加的方法(PyNumber_Add)。這種方法所耗費的時間是二次方程式的(quadratic running time)。
為什么在這里要犧牲性能呢?我猜想(只是淺薄猜測),可能有兩種考慮,一是為了第二個參數(shù)(start)的一致性,因為它通常是一個數(shù)值,是不可變對象,所以當(dāng)它是可變對象類型時,最好也不對它做修改;其次,為了確保 sum() 函數(shù)是個 純函數(shù) ,為了多次執(zhí)行時能返回同樣的結(jié)果。
那么,我要繼續(xù)問:哪種方法是最優(yōu)的呢?
綜合來看,當(dāng)子列表個數(shù)小于 10 時,sum() 函數(shù)幾乎是最優(yōu)的,與某幾種方法相差不大,但是,當(dāng)子列表數(shù)目增加時,最優(yōu)的選擇是 functools.reduce(operator.iconcat, a, []),其次是 list(itertools.chain.from_iterable(a)) 。
事實上,最優(yōu)方案中的 iconcat(a, b) 等同于 a += b,它是一種就地修改的方法。
operator.iconcat(a, b) operator.__iconcat__(a, b) a = iconcat(a, b) is equivalent to a += b for a and b sequences.
這正是 sum() 函數(shù)出于一致性考慮,而舍棄掉的實現(xiàn)方案。
至此,前文提出的問題都找到了答案。
我最后總結(jié)一下吧:sum() 函數(shù)采用的是非就地修改的相加方式,用作列表降維時,隨著數(shù)據(jù)量增大,其性能將是二次方程式的劇增,所以說是性能堪憂;而 reduce 結(jié)合 iconcat 的方法,才是大數(shù)據(jù)量時的最佳方案。
這個結(jié)果是否與你所想的一致呢?希望本文的分享,能給你帶來新的收獲。
相關(guān)鏈接:
如何給列表降維?sum()函數(shù)的妙用 :https://mp.weixin.qq.com/s/cr_noDx6s1sZ6Xt6PDpDVQ
stackoverflow 問題:https://stackoverflow.com/questions/952914/how-to-make-a-flat-list-out-of-list-of-lists
公眾號【Python貓】, 本號連載優(yōu)質(zhì)的系列文章,有喵星哲學(xué)貓系列、Python進階系列、好書推薦系列、技術(shù)寫作、優(yōu)質(zhì)英文推薦與翻譯等等,歡迎關(guān)注哦。后臺回復(fù)“愛學(xué)習(xí)”,免費獲得一份學(xué)習(xí)大禮包。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/43668.html
摘要:上個月,學(xué)習(xí)群里的同學(xué)問了個題目,大意可理解為列表降維,例子如下想得到結(jié)果原始數(shù)據(jù)是一個二維列表,目的是獲取該列表中所有元素的具體值。不經(jīng)意間,函數(shù)的注意事項,竟把其它的進階內(nèi)容都聯(lián)系起來了。小小的函數(shù),竟成為學(xué)習(xí)之路上的一個樞紐。 上個月,學(xué)習(xí)群里的 S 同學(xué)問了個題目,大意可理解為列表降維 ,例子如下: oldlist = [[1, 2, 3], [4, 5]] # 想得到結(jié)果:...
摘要:尾聲除了以上特性,函數(shù)式編程中還有,等比較難以理解的概念,本文暫時不牽扯那么深,留待有興趣的人自行調(diào)查。 本文簡單介紹了一下函數(shù)式編程的各種基本特性,希望能夠?qū)τ跍?zhǔn)備使用函數(shù)式編程的人起到一定入門作用。 showImg(/img/bVyUGu); 函數(shù)式編程,一個一直以來都酷,很酷,非常酷的名詞。雖然誕生很早也炒了很多年但是一直都沒有造成很大的水花,不過近幾年來隨著多核,分布式,大數(shù)據(jù)...
閱讀 1194·2021-11-22 13:54
閱讀 2443·2021-09-22 15:36
閱讀 2748·2019-08-30 15:54
閱讀 818·2019-08-30 15:53
閱讀 3182·2019-08-30 15:53
閱讀 525·2019-08-29 15:21
閱讀 2878·2019-08-28 18:28
閱讀 3029·2019-08-26 13:37