2009-07-09 55 views
3

我一直在家裏編寫小型Python程序來了解更多關於該語言的知識。我試圖理解的最新功能是List Comprehensions。我創建了一個小腳本,根據我過去多少次更換機油,估計我的汽車何時需要換油。在下面的代碼片段中,oil_changes是我更換機油的里程列表。幫助需要使用列表理解改進Python代碼

# Compute a list of the mileage differences between each oil change. 
diffs = [j - i for i, j in zip(oil_changes[:-1], oil_changes[1:])] 

# Use the average difference between oil changes to estimate the next change. 
next_oil = oil_changes[-1] + sum(diffs)/len(diffs) 

的代碼產生正確的答案(沒有用手數學檢查),但它並不感到很Python的呢。我是否在第一行做了大量不必要的原始列表複製?我覺得有一個更好的方法來做到這一點,但我不知道它是什麼。

回答

9

至於其他的答案中指出,你並不需要擔心,除非你的oil_changes名單非常長。然而,隨着「基於數據流的」計算的粉絲,我認爲這是有趣地指出,itertools提供所有你需要計算在O(1)空間(當然O(N)時間你next_oil價值的工具! - )不管N有多大,就是len(next_oil),得到。

izip本身是不夠的,因爲它不僅降低了一點乘法不變,但離開你的空間需求爲O(N)。關鍵的思想,使這些需求降到O(1)配對iziptee - 和避免列表理解,這將是在太空O(N)反正,有利於良好的簡單的老式循環! - )。這裏說到:

it = iter(oil_changes) 
    a, b = itertools.tee(it) 
    b.next() 
    thesum = 0 
    for thelen, (i, j) in enumerate(itertools.izip(a, b)): 
    thesum += j - i 
    last_one = j 
    next_oil = last_one + thesum/(thelen + 1) 

而不是從列表中取切片,我們取一個迭代器就可以了,它三通(製造其兩個獨立的可推進克隆),並提前,有一次,一個克隆,btee需要空間O(x)其中x是各種克隆進程之間的最大絕對差值;在這裏,兩個克隆的進步最多隻相差1,所以空間需求顯然是O(1)。

izip對兩個略微歪斜的克隆迭代器進行一次一個「壓縮」,我們將它打扮成enumerate,以便我們可以跟蹤我們經過該循環的次數,即長度我們正在迭代迭代(我們需要最終表達式中的+1,因爲enumerate從0開始!)。我們用一個簡單的+=來計算總和,這對數字來說很好(sum甚至更​​好,但它不會跟蹤長度!)。

這是誘人的後循環使用last_one = a.next(),但是這是行不通的,因爲a實際上是用盡 - izip推進其參數iterables左到右,所以它擁有先進的a最後一次實現b結束前! - )。這是確定的,因爲Python循環變量不在範圍循環本身的限制 - 在循環後,j仍然有最後被推進b提取izip放棄了(就像thelen仍然通過返回的最後一個計數值之前的值enumerate)。我仍然在命名last_one,而不是直接在最終表達式中使用j,因爲我認爲它更清晰,更具可讀性。

所以有它 - 我希望這是有益的 - - !) - 儘管你提出了這個當時的具體問題的解決方案,幾乎可以肯定的是矯枉過正。我們意大利人有一句古老的諺語 - 「Impara l'Arte,e mettila da parte!」... ...「學習藝術,然後放在一邊」 - 我認爲這很適用於此:學習是件好事先進的和複雜的方式來解決非常棘手的問題,以防萬一遇到它們,但是爲了簡單和直接,您需要去解決簡單,普通問題的大量常見問題 - 不適用最有可能贏得的高級解決方案「T需要 - !)

2

看起來很好,真的。並非一切都很簡單(無論您如何設計,您都可以通過簡單的計算獲得幾個步驟)。有些選項可以減少副本,比如使用itertools.islice和itertools.izip,但是除了izip之外,代碼中的額外步驟只會使其複雜化。並不是所有的事情都需要列表理解,但有時候這是一種判斷力的呼喚。什麼看起來更清潔?下一個閱讀它的人會理解什麼最好?當你在三個月內回來修復這個錯誤時,你會明白什麼?

3

itertools包提供了額外的生成器樣式函數。例如,您可以使用izip代替zip以保存在某些內存中。

你也可以或許寫一個average功能,這樣你就可以把diffs變成發電機,而不是一個列表理解:

from itertools import izip 

def average(items): 
    sum, count = 0, 0 

    for item in items: 
     sum += item 
     count += 1 

    return sum/count 

diffs = (j - i for i, j in izip(oil_changes[:-1], oil_changes[1:]) 
next_oil = oil_changes[-1] + average(diffs) 

或者,你可以在你的diffs定義修改爲:

diffs = [oil_changes[i] - oil_changes[i-1] for i in xrange(1, len(oil_changes))] 

我不知道,這不是一個巨大的改進。你的代碼是相當不錯的。

+0

有趣的是,這裏的答案(除了當然的約翰·馬金的答案)中最快的運行時間的diff結果您的備選定義。 – 2009-07-10 01:56:44

+0

如果len(條目)> 0,那麼平均值可能只是sum(items)/ len(items)? – Martlark 2011-07-31 02:52:41

9

試試這個:

assert len(oil_changes) >= 2 
sum_of_diffs = oil_changes[-1] - oil_changes[0] 
number_of_diffs = len(oil_changes) - 1 
average_diff = sum_of_diffs/float(number_of_diffs) 
+0

這顯然是得到我的答案的最佳方式,但後來我不會學習有關列表理解的任何內容。無論如何。 :-) – 2009-07-10 01:51:44

2

我做了很多原單的不必要的複製 的第一 行?

技術上,是的。實際上,不。除非你幾百萬次改變了你的油,否則速度懲罰不會很大。您可以將zip更改爲izip,但它似乎不值得(在python 3.0中,zip有效izip)。

在此插入old quote by Knuth

(你也可以只用oil_changes取代oil_changes[:-1],因爲zip()截斷到最短輸入序列的長度反正)