3

言語についてもっと学ぶために、自宅で小さな Python プログラムを書いています。私が理解しようとした最新の機能は、リスト内包表記です。過去にオイル交換を行った頻度に基づいて、次のオイル交換がいつ必要になるかを予測する小さなスクリプトを作成しました。以下のコード スニペットoil_changesは、オイルを交換した走行距離のリストです。

# 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)

コードは正しい答えを生成します (チェックするために手作業で計算を行いました) が、まだ完全に Pythonic とは感じられません。最初の行の元のリストを不必要にコピーしていませんか? これを行うにはもっと良い方法があるように感じますが、それが何であるかはわかりません。

4

5 に答える 5

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)
于 2009-07-09T03:07:40.527 に答える
9

他の回答が指摘したように、oil_changesリストが非常に長くない限り、実際に心配する必要はありません。しかし、「ストリームベース」コンピューティングのファンとして、 O(1) 空間 (そしてもちろん O(N) 時間!-) で値itertoolsを計算するために必要なすべてのツールを提供することを指摘するのは興味深いと思います。 next_oilN、つまり がどれだけ大きくなっても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)

リストからスライスを取得する代わりに、イテレータを取得し、それをティー (独立して進めることができるその 2 つのクローンを作成) し、クローンの 1 つを 1 回進めbます。teeスペース O(x) を取ります。x は、さまざまなクローンの前進の最大絶対差です。ここでは、2 つのクローンの進行状況の違いは最大でも 1 だけであるため、必要なスペースは明らかに O(1) です。

izipわずかに斜めになった 2 つのクローン イテレータを 1 つずつ「圧縮」し、それをドレスアップしenumerateて、ループを何回通過したか、つまり、反復している iterable の長さを追跡できるようにします。オン ( enumerate0 から始まるため、最後の式に +1 が必要です!-)。単純な で合計を計算し+=ます。これは数値には問題ありません (sumさらに優れていますが、長さを追跡しません!-)。

ループの後で を使用するのは魅力的ですが、それは実際には使い果たさlast_one = a.next()れているため機能しません--は引数 iterables を左から右に進めます。つまり、終わったことに気付く前に最後にもう 1 回進めてしまいます!-)。Python ループ変数のスコープはループ自体に限定されていないため、問題ありません。ループの後、あきらめる前に前進することによって最後に抽出された値がまだ残っています(によって返された最後のカウント値がまだ残っているのと同じように)。最終的な式で直接使用するのではなく、値に名前を付けています。これは、より明確で読みやすいと思うためです。aizipabjbizipthelenenumeratelast_onej

というわけで -- 参考になれば幸いです!-) -- ただし、今回あなたが提起した特定の問題の解決策については、やり過ぎであることはほぼ確実です。私たちイタリア人には、古代からのことわざがあります。非常に難しい問題に遭遇した場合に備えて、非常に難しい問題を解決するための高度で洗練された方法。必要ありません!-)

于 2009-07-09T05:15:57.113 に答える
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))]

わかりませんが、それは実際には大きな改善ではありません。あなたのコードはそのままでかなり良いです。

于 2009-07-09T03:01:17.633 に答える
2

大丈夫そうです、本当に。すべてが単純というわけではありません (どのように構成しても、そうでなければ単純な計算にはいくつかのステップがあります)。itertools.islice や itertools.izip を使用するなど、コピーを減らすためのオプションがありますが、(izip は別として) コード内の余分な手順により、さらに複雑になります。すべてがリスト内包表記である必要はありませんが、判断が必要な場合もあります。あなたにとってよりきれいに見えるものは何ですか?それを読んだ次の人は何を最もよく理解するでしょうか? 3 か月後にそのバグを修正するために戻ってきたときに、何を理解できますか?

于 2009-07-09T02:56:11.637 に答える
2

最初の行の元のリストを不必要にコピーしていませんか?

技術的には、はい。現実的には、いいえ。オイルを文字通り何百万回も交換していない限り、速度のペナルティはそれほど大きくないでしょう。zipに変更することもできますがizip、その価値はほとんどないようです (そして、python 3.0 では、zip実質的には です izip)。

クヌースの古い引用をここに挿入します。

(とにかく、最短の入力シーケンスの長さに切り捨てられるためoil_changes[:-1]、 justoil_changesに置き換えることもできます)zip()

于 2009-07-09T04:11:17.773 に答える