web-dev-qa-db-ja.com

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

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

現在私がどのようにやっているのかを次に示します。

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を使用するなどのハックで作成したくありません。 ;)

158
e.tadeu

ほとんどの場合、最後の代わりにfirstの繰り返しを特殊なケースにする方が簡単(かつ安価)です:

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!

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


同じ原則を使用してよりコンパクトに:

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

おなじみですね。 :)


@ofko、およびlen()なしのiterableの現在の値が最後の値であるかどうかを本当に知る必要がある他の人にとっては、先読みする必要があります:

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
129
Ferdinand Beyer

「コード間の」は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 )

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

18
S.Lott

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

L[-1]

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

16
Bartek

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

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

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

11
Coffeinated

これは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
10
Andrew Dalke

アイテムが一意の場合:

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
8
Palza

入力データ上でスライディングウィンドウを使用して次の値を確認し、センチネルを使用して最後の値を検出できます。これはあらゆる反復可能オブジェクトで機能するため、事前に長さを知る必要はありません。ペアワイズ実装は itertools recipes からです。

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
4
Ants Aasma

最後の要素を除いてすべてを繰り返し、ループの外側の最後の要素を処理する可能性はありませんか?結局、ループを作成して、ループするすべての要素に似た処理を行います。ある要素に特別な何かが必要な場合、ループ内にあるべきではありません。

(この質問も参照してください: does-the-last-element-in-a-loop-deserve-a-separate-treatment

編集:質問は「中間」に関するものなので、first要素は前身がないという特別な要素であるか、 last要素は、後続要素がないという点で特別です。

3
xtofl

グーグルはこの古い質問に私を連れてきて、この問題に別のアプローチを追加できると思います。

ここでの答えのほとんどは、要求された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
Anderson Santos

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

while L:
    e = L.pop(0)
    # process element
    if not L:
        print('Last element has been detected.')

スライスと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>

Caveat emptor:これは、リスト内のすべての要素が実際に異なる(メモリ内の異なる場所にある)場合にのみ機能します。内部では、Pythonは同等の要素を検出し、それらに対して同じオブジェクトを再利用できます。たとえば、同じ値と一般的な整数の文字列の場合。

2
Roger Dahl

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
e-satis

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

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

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

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

複数の方法があります。スライスが最速になります。 .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
amolpachpute

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

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

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

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

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

0
Chris

入力をイテレータとして想定して、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
Anon

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

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

そのため、処理を1回繰り返すことにより、常に1つの項目を先読みします。最初の反復中に何かをスキップするには、単にエラーをキャッチします。

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

また、 `counstruct

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
DerWeh

アイテムを1回カウントして、残りのアイテムの数に対応します。

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

この方法では、リストの長さを一度だけ評価します。このページのソリューションの多くは、長さが事前に利用できないことを前提としているようですが、それはあなたの質問の一部ではありません。長さがある場合は、それを使用します。

0
Ethan T