4

私はPythonについて読んでいて、リスト内包表記の問題を解決したいと思っています。問題は簡単です:

n の前にある 3 と 5 の倍数の和を与えるプログラムを書きなさい

n = 1000 を取る (オイラー計画、第 1 の問題)

私はこのようなことをしたい:

[mysum = mysum + i for i in range(2,1000) if i%3==0 or i%5==0]

1行だけで...しかし、それは機能しません。

  1. これはリスト内包表記で実現できますか? どのように??
  2. また、リスト内包表記を使用するのが良いのはいつですか?
4

3 に答える 3

10

リスト内包表記のポイントは、結果値のリストを、ソース値ごとに 1 つ (句がある場合は、一致するソース値ごとに 1 つ) 生成することです。if

言い換えれば、 (複数の句がある場合はand呼び出しmapのチェーン)と同じですが、新しい値をそれぞれ古い値の観点から式として記述できる点が異なります。機能。mapfilter

mysum = mysum + iステートメント ( など) を内包表記に入れることはできません。式のみです。そして、たとえあなたが望む副作用を持つ式を考え出すことができたとしても、それは依然として混乱を招く理解の誤用です. 結果値のリストが必要ない場合は、リスト内包表記を使用しないでください。

ループで計算を実行しようとしているだけの場合は、明示的なforループを記述します。


本当に1行にする必要がある場合は、いつでもこれを行うことができます:

for i in [i for i in range(2, 10) if i%2==0 or i%5==0]: mysum += i

内包でループするもののリストを作成します。forループで副作用 y の計算を行います。

(もちろん、これはmysum、たとえば を使用して、追加する値を既に持っていることを前提としていますmysum = 0。)

そして、一般に、一度ループするためだけに内包表記が必要な場合はいつでも、必要な種類の内包表記はリスト内包表記ではなく、ジェネレーター式です。したがって、これらの角括弧を括弧に変えると、次のようになります。

for i in (i for i in range(2, 10) if i%2==0 or i%5==0): mysum += i

ただし、いずれにしても、次の 2 行の方が読みやすく、pythonic です。

for i in (i for i in range(2, 10) if i%2==0 or i%5==0):
    mysum += i

…または 3 つ:

not2or5 = (i for i in range(2, 10) if i%2==0 or i%5==0) 
for i in not2or5:
    mysum += i

reduceループよりも/foldをより直感的にする言語から来ている場合、Pythonにはreduce機能があります。forただし、ループを排除してブロックステートメントをワンライナーにするためだけに使用することは、一般的に Pythonic とは見なされません。

より一般的に言えば、物事を 1 行に詰め込もうとすると、通常、Python では読みにくくなります。多くの場合、頭の中で入力する文字と処理するトークンが増えることになり、利益が相殺されます。ラインを節約します。


もちろん、この特定のケースでは、本当にやりたいことはリストの値を合計することだけです。そして、それはまさにそれsumです。そして、それを理解するのは簡単です。そう:

mysum += sum(i for i in range(2, 10) if i%2==0 or i%5==0)

mysum(繰り返しますが、これは、既に追加したいものが にあることを前提としています。そうでない場合は、 を に変更し+==ください。以降のすべての例についても同じことが当てはまるので、説明をやめます。)


そうは言っても、私はおそらくこれを明示的なネストされたブロックとして書くでしょう:

for i in range(2, 10):
    if i%2==0 or i%5==0:
        mysum += i

…またはイテレータ変換のシーケンスとして (この場合、実際には 1 つの変換にすぎません):

not2or5 = (i for i in range(2, 10) if i%2==0 or i%5==0)
mysum += sum(not2to5)

(リスト内包表記の代わりにジェネレーター式を使用する限り) この方法で物事を分割するコストは実際にはなく、通常はコードの意図がより明確になります。


ジェネレーター式に関する詳細な説明:

ジェネレータ式は、リストの代わりにイテレータを作成することを除いて、リスト内包表記と同じです。イテレータは、一度しか使用できないことを除いて、一部の関数型言語の「レイジー リスト」に似ています。(通常、これは問題ではありません。上記のすべての例で、sum関数に渡すか、forループで使用することだけが目的であり、それを再度参照することはありません。)各値はオンデマンドで構築され、次の値に到達する前に解放されます。

これは、スペースの複雑さが線形ではなく一定であることを意味します。一度にメモリ内に取得できる値は 1 つだけですが、リストを使用すると明らかにすべての値を取得できます。それはしばしば大きな勝利です。

ただし、時間の複雑さは変わりません。リスト内包表記はすべての作業を前もって行うため、構築する時間は直線的で、その後は自由に使用できます。ジェネレーター式は、反復処理を行うときに機能するため、自由に構築でき、線形に使用できます。いずれにせよ、同じ時間。(ジェネレータ式は、実際にはキャッシュ/メモリの局所性、パイプラインなどにより、実際には大幅に高速になる可能性があり、メモリの移動と割り当てのコストをすべて回避することは言うまでもありません。一方、少なくともリストの迅速な特殊ケースではなく、完全なイテレータ プロトコルを使用する必要があるためです。)

(ここでは、各ステップの作業は一定であると仮定しています。明らかに[sum(range(i)) for i in range(n)]、n については 2 次であり、線形ではありません…)

于 2013-09-30T22:12:05.937 に答える
4

あなたはもうすぐそこにいます!これを試して:

mysum = sum([i for i in range(2,10) if i%2==0 or i%5==0])

これにより、「ループ」からリストが作成され、このリストがsum関数に渡されます。

のようなリスト内包mylist = [*some expression using i* for i in iterable]表記は、

mylist = []
for i in iterable:
    mylist.append(*some expression using i*)

のようなリスト内包mylist = [*some expression using i* for i in iterable if *boolean with i*]表記は、

mylist = []
for i in iterable:
    if *boolean with i*:
        mylist.append(*some expression using i*)

何らかの式を使用して新しいリストを作成する必要があるときはいつでも、これらを使用できます。リスト内包表記は、解釈された python ではなく 内部forでコードを実行するため、通常、同等のループよりも効率的です。C

于 2013-09-30T22:12:26.780 に答える