「for」ループの最後の要素を検出するpythonicの方法は何ですか?


188

forループの最後の要素に対して特別な処理を行うための最良の方法(よりコンパクトで "pythonic"な方法)を知りたいのですが。要素間でのみ呼び出す必要があるコードがあり、最後のコードでは抑制されています。

ここに私が現在それをしている方法があります:

for i, data in enumerate(data_list):
    code_that_is_done_for_every_element
    if i != len(data_list) - 1:
        code_that_is_done_between_elements

もっと良い方法はありますか?

注:を使用するなどのハックでそれを作りたくありませんreduce;)


彼の最初のものはどうですか?それも抑制されるべきですか?
Adam Matan

要素間で何が行われているのか教えていただけますか?
SilentGhost 2009年

2
一般的なケースの答えを得たいのですが、これが必要な具体的なケースは、stream.write( '、' .join(name_list))のように、ストリーム間にセパレータを置いて物事を書き込むことです。しかし、多くの書き込みがあるため、文字列を連結せずにforループでそれを実行します...
e.tadeu


この回答の最初の3行は私にとって非常に役立ちました。同様の課題がありました。
カルダモン

回答:


151

ほとんどの場合、最初のイテレーションを最後のイテレーションの代わりに特別なケースにする方が簡単(そして安価)です。

first = True
for data in data_list:
    if first:
        first = False
    else:
        between_items()

    item()

これは、イテラブルがない場合でも機能しますlen()

file = open('/path/to/file')
for line in file:
    process_line(line)

    # No way of telling if this is the last line!

それを除けば、あなたが何をしようとしているのかに依存するので、一般的に優れた解決策はないと思います。たとえば、リストから文字列を作成する場合は、「特別なケースで」ループを使用するstr.join()よりも、当然使用する方が適切forです。


同じ原理を使用しますが、よりコンパクトです:

for i, line in enumerate(data_list):
    if i > 0:
        between_items()
    item()

おなじみですね。:)


@ofko、およびなしのイテラブルの現在の値がlen()最後の値であるかどうかを実際に確認する必要がある他の人にとっては、前を見る必要があります。

def lookahead(iterable):
    """Pass through all values from the given iterable, augmented by the
    information if there are more values to come after the current one
    (True), or if it is the last value (False).
    """
    # Get an iterator and pull the first value.
    it = iter(iterable)
    last = next(it)
    # Run the iterator to exhaustion (starting from the second value).
    for val in it:
        # Report the *previous* value (more to come).
        yield last, True
        last = val
    # Report the last value.
    yield last, False

その後、次のように使用できます。

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False

1
確かに、この方法は私の方法よりも優れているようです。少なくともenumerateとlenを使用する必要はありません。
e.tadeu、2009年

はい。ただしif、ループが2つのループに分割された場合に回避できる別のループが追加されます。ただし、これが関係するのは、巨大なデータリストを反復する場合のみです。
Adam Matan

2つのループに分割することの問題は、DRYに違反するか、メソッドを定義する必要があることです。
e.tadeu 2009年

私はあなたの最後の例(私のコードでは問題なく動作します)を本当に理解しようとしますが、それがどのように機能するか(背後の考え方)は理解していません
Olivier Pons

1
@OlivierPons Pythonのイテレータプロトコルを理解する必要があります。オブジェクトのイテレータを取得し、最初の値をで取得しnext()ます。次に、イテレータがそれ自体で反復可能であることを利用して、for枯渇するまでループで使用し、2番目の値から最後の値まで反復します。この間、ローカルでイテレータから取得した現在の値とyield、代わりに最後の値を保持します。このように、もう1つ価値があることを知っています。forループの後、最後の値を除くすべての値を報告しました。
Ferdinand Beyer、2015

20

その質問はかなり古いですが、私はグーグル経由でここに来て、私は非常に簡単な方法を見つけました:リストのスライス。すべてのリストエントリの間に「&」を挿入するとします。

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

これは「1&2&3」を返します。


7
結合関数を再実装しました: `"& ".join([str(x)for x in l])
Bryan Oakley

文字列の連結はやや非効率的です。場合はlen(l)=1000000、この例では、プログラムがしばらくの間実行されます。appendafaikがお勧めです。l=[1,2,3]; l.append(4);
plhn 2017

18

「間のコード」は、Head-Tailパターンの例です。

あなたはアイテムを持っており、その後に(between、item)ペアのシーケンスが続きます。これは、一連の(アイテム間)ペアとそれに続くアイテムとして表示することもできます。一般的に、最初の要素を特別で他のすべての要素を「標準」の場合とする方が簡単です。

さらに、コードの繰り返しを避けるために、繰り返したくないコードを含む関数または他のオブジェクトを提供する必要があります。ifステートメントをループに埋め込むのは、1回を除いて常にfalseであるのはばかげています。

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = head_tail_iter.next()
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

これは、証明が少し簡単なので、信頼性が高くなります。余分なデータ構造(つまり、リストのコピー)を作成せず、1回を除いて常にfalse であるif条件の多くの無駄な実行を必要としません。


4
関数呼び出しはifステートメントよりもはるかに遅いため、「無駄な実行」の引数は成立しません。
フェルディナンドベイヤー

1
関数呼び出しとifステートメントの速度の違いが何かと関係があるかどうかはわかりません。重要なのは、この公式には常に偽であるifステートメントが(1回を除いて)ないということです
S.Lott

1
私はあなたの声明を「…そして一度だけを除いて常に偽であるif条件の多くの無駄な実行を必要としない」と「…そしてifsを節約するのでより速い」と解釈しました。明らかに、あなたは単に「コードの清潔さ」に言及しているだけですか?
フェルディナンドベイヤー

ifステートメントを使用する代わりに関数を定義することは、Pythonコミュニティによって本当によりクリーンであると考えられていますか?
Markus von Broady

17

の最後の要素を変更するdata_listだけの場合は、次の表記法を使用できます。

L[-1]

しかし、あなたはそれ以上のことをしているようです。あなたのやり方には何も悪いことはありません。テンプレートタグのDjangoコードを一目見ただけでも、基本的にはユーザーが行っていることを実行します。


1
私はそれを修正しているのではなく、何かをするためにそれを使っています
e.tadeu

4
@ e.tadeu変更するかどうかは関係ありません。ifステートメントを次のように変更し、if data != datalist[-1]:他のすべてを同じに保つことが、私の意見ではこれをコーディングする最良の方法でしょう。
spacetyper 2015

8
@spacetyperこれは、最後の値が一意でない場合に壊れます。
アークくん

14

アイテムが一意の場合:

for x in list:
    #code
    if x == list[-1]:
        #code

別のオプション:

pos = -1
for x in list:
    pos += 1
    #code
    if pos == len(list) - 1:
        #code


for x in list:
    #code
#code - e.g. print x


if len(list) > 0:
    for x in list[:-1]
        #code
    for x in list[-1]:
        #code

10

これはAnts Aasmaのアプローチに似ていますが、itertoolsモジュールを使用しません。また、イテレータストリーム内の単一の要素を先読みする遅延イテレータでもあります。

def last_iter(it):
    # Ensure it's an iterator and get the first field
    it = iter(it)
    prev = next(it)
    for item in it:
        # Lag by one item so I know I'm not at the end
        yield 0, prev
        prev = item
    # Last item
    yield 1, prev

def test(data):
    result = list(last_iter(data))
    if not result:
        return
    if len(result) > 1:
        assert set(x[0] for x in result[:-1]) == set([0]), result
    assert result[-1][0] == 1

test([])
test([1])
test([1, 2])
test(range(5))
test(xrange(4))

for is_last, item in last_iter("Hi!"):
    print is_last, item

4

入力データ上でスライディングウィンドウを使用して次の値を確認し、センチネルを使用して最後の値を検出できます。これはイテラブルで機能するため、事前に長さを知っておく必要はありません。ペアワイズ実装はitertoolsレシピからのものです

from itertools import tee, izip, chain

def pairwise(seq):
    a,b = tee(seq)
    next(b, None)
    return izip(a,b)

def annotated_last(seq):
    """Returns an iterable of pairs of input item and a boolean that show if
    the current item is the last item in the sequence."""
    MISSING = object()
    for current_item, next_item in pairwise(chain(seq, [MISSING])):
        yield current_item, next_item is MISSING:

for item, is_last_item in annotated_last(data_list):
    if is_last_item:
        # current item is the last item

3

最後の要素を除いてすべてを繰り返し、ループの外で最後の要素を処理する可能性はありませんか?結局のところ、ループは、ループするすべての要素と同様のことを行うために作成されます。1つの要素に特別なものが必要な場合は、ループに含めないでください。

(この質問もご覧ください:does-the-last-element-in-a-loop-deserve-a-separate-treatment

編集:質問は「中間」に関するものであるため、最初の要素は先行要素がないという点で特別な要素であるか、最後の要素は後続要素がないという点で特別です。


ただし、最後の要素は、リスト内の他のすべての要素と同様に扱う必要があります。問題は、要素間でのみ行うべきことです。
e.tadeu 2009年

その場合、最初のものは前任者なしの唯一のものです。それを分解して、リストの残りの一般的なコードをループします。
xtofl 2009年

3

@ ethan-tのアプローチは好きwhile Trueですが、私の観点からすると危険です。

data_list = [1, 2, 3, 2, 1]  # sample data
L = list(data_list)  # destroy L instead of data_list
while L:
    e = L.pop(0)
    if L:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')
del L

ここでdata_listは、最後の要素がリストの最初の要素と値で等しいようになっています。Lはと交換できますdata_listが、この場合はループ後に空になります。while True処理前にリストが空でないことを確認したり、チェックが必要ない場合(ouch!)にも利用できます。

data_list = [1, 2, 3, 2, 1]
if data_list:
    while True:
        e = data_list.pop(0)
        if data_list:
            print(f'process element {e}')
        else:
            print(f'process last element {e}')
            break
else:
    print('list is empty')

良い点はそれが速いということです。悪い-それは破壊可能です(data_list空になります)。

最も直感的なソリューション:

data_list = [1, 2, 3, 2, 1]  # sample data
for i, e in enumerate(data_list):
    if i != len(data_list) - 1:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')

ああ、あなたはすでにそれを提案しました!


2

100 000のループがあり、100 000の「if」ステートメントを保存したい場合を除き、方法に問題はありません。その場合、あなたはその方法で行くことができます:

iterable = [1,2,3] # Your date
iterator = iter(iterable) # get the data iterator

try :   # wrap all in a try / except
    while 1 : 
        item = iterator.next() 
        print item # put the "for loop" code here
except StopIteration, e : # make the process on the last element here
    print item

出力:

1
2
3
3

しかし、実際には、あなたの場合、それはやり過ぎだと思います。

いずれにせよ、あなたはおそらくスライスで幸運になるでしょう:

for item in iterable[:-1] :
    print item
print "last :", iterable[-1]

#outputs
1
2
last : 3

あるいは単に :

for item in iterable :
    print item
print iterable[-1]

#outputs
1
2
3
last : 3

最終的には、KISSを使用して作業を行うと、以下のようなものを含め、すべてのイテラブルで機能します__len__

item = ''
for item in iterable :
    print item
print item

出力:

1
2
3
3

私がそのようにしたいと思うなら、私には簡単に思えます。


2
しかし、注目すべきことのiterable [-1]に動作しませんすべて(ていないよう発電機などイテラブルでlen
e.tadeu

ループ後の最後の項目にアクセスするだけの場合は、を使用してitem再計算するのではなく、単に使用しlist[-1]ます。しかし、それでも、これがOPが要求していたことではないと思いますか?
フェルディナンドベイヤー

Re:iterable.__iter__() - __関数を直接呼び出さないでください。する必要がありますiter(iterable)
PaulMcG 2009年

2

スライスを使用isして、最後の要素を確認します。

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

警告のエンプター:これは、リスト内のすべての要素が実際に異なる(メモリ内の場所が異なる)場合にのみ機能します。内部では、Pythonは等しい要素を検出し、同じオブジェクトをそれらに再利用します。たとえば、同じ値と共通の整数の文字列の場合です。


2

あなたがリストを通過しているなら、私にとってもこれはうまくいきました:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

2

グーグルは私にこの古い質問をもたらしました、そして私はこの問題に別のアプローチを加えることができると思います。

ここでの答えのほとんどは、要求されたとおりのforループ制御の適切な処理を扱いますが、data_listが破壊可能な場合は、空のリストになるまでリストから項目をポップすることをお勧めします。

while True:
    element = element_list.pop(0)
    do_this_for_all_elements()
    if not element:
        do_this_only_for_last_element()
        break
    do_this_for_all_elements_but_last()

最後の要素で何もする必要がない場合は、while len(element_list)を使用することもできます。このソリューションは、next()を処理するよりもエレガントだと思います。


2

私にとって、リストの最後で特殊なケースを処理する最も簡単でPython的な方法は次のとおりです。

for data in data_list[:-1]:
    handle_element(data)
handle_special_element(data_list[-1])

もちろん、これを使用して最初の要素を特別な方法で処理することもできます。


2

カウントアップする代わりに、カウントダウンすることもできます:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()

1

ループの後まで最後のアイテムの特別な処理を遅らせます。

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

1

複数の方法が考えられます。スライスが最速になります。.index()メソッドを使用するもう1つを追加します。

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]

0

入力をイテレータと仮定して、itertoolsからteeとizipを使用する方法を次に示します。

from itertools import tee, izip
items, between = tee(input_iterator, 2)  # Input must be an iterator.
first = items.next()
do_to_every_item(first)  # All "do to every" operations done to first item go here.
for i, b in izip(items, between):
    do_between_items(b)  # All "between" operations go here.
    do_to_every_item(i)  # All "do to every" operations go here.

デモ:

>>> def do_every(x): print "E", x
...
>>> def do_between(x): print "B", x
...
>>> test_input = iter(range(5))
>>>
>>> from itertools import tee, izip
>>>
>>> items, between = tee(test_input, 2)
>>> first = items.next()
>>> do_every(first)
E 0
>>> for i,b in izip(items, between):
...     do_between(b)
...     do_every(i)
...
B 0
E 1
B 1
E 2
B 2
E 3
B 3
E 4
>>>

0

私の頭に浮かぶ最も簡単な解決策は:

for item in data_list:
    try:
        print(new)
    except NameError: pass
    new = item
print('The last item: ' + str(new))

したがって、処理を1回繰り返すことで、常に1つのアイテムを先読みします。最初の反復で何かをスキップするために、私は単にエラーをキャッチします。

もちろん、必要なNameErrorときにを上げるために、少し考える必要があります。

また、 `国会議員

try:
    new
except NameError: pass
else:
    # continue here if no error was raised

これは、newという名前が以前に定義されていなかったことに依存しています。あなたが偏執的であるならば、あなたはそれnewが存在しないことを確実にすることができます:

try:
    del new
except NameError:
    pass

あるいは、もちろんifステートメント(if notfirst: print(new) else: notfirst = True)を使用することもできます。しかし、私が知る限り、オーバーヘッドはより大きくなります。


Using `timeit` yields:

    ...: try: new = 'test' 
    ...: except NameError: pass
    ...: 
100000000 loops, best of 3: 16.2 ns per loop

だからオーバーヘッドは選べなくなると思う。


0

一度アイテムを数え、残りのアイテムの数に追いつきます:

remaining = len(data_list)
for data in data_list:
    code_that_is_done_for_every_element

    remaining -= 1
    if remaining:
        code_that_is_done_between_elements

この方法では、リストの長さを1回だけ評価します。このページのソリューションの多くは、事前に長さが不明であることを前提としているようですが、それは質問の一部ではありません。長さがあればそれを使ってください。


0

頭に浮かぶ簡単な解決策は次のとおりです。

for i in MyList:
    # Check if 'i' is the last element in the list
    if i == MyList[-1]:
        # Do something different for the last
    else:
        # Do something for all other elements

カウンターを使用することで、2番目に同様に単純なソリューションを実現できます。

# Count the no. of elements in the list
ListLength = len(MyList)
# Initialize a counter
count = 0

for i in MyList:
    # increment counter
    count += 1
    # Check if 'i' is the last element in the list
    # by using the counter
    if count == ListLength:
        # Do something different for the last
    else:
        # Do something for all other elements
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.