'For' döngüsünde son elemanı tespit etmenin pythonic yolu nedir?


188

Bir for döngüsündeki son eleman için özel bir tedavi yapmanın en iyi yolunu (daha kompakt ve "pitonik" yol) bilmek istiyorum. Sonuncusunda bastırılan, yalnızca öğeler arasında çağrılması gereken bir kod parçası vardır .

Şu anda nasıl yaparım:

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

Daha iyi bir yol var mı?

Not: Kullanmak gibi kesmek ile yapmak istemiyorum reduce.;)


İlk önce ne olacak? Bastırılmalı mı?
Adam Matan

bize elementler arasında ne yapıldığını söyleyebilir misiniz?
SilentGhost

2
Genel bir durumun cevabını almak istiyorum, ancak buna ihtiyacım olan somut bir durum, bir akışta, aralarında ayırıcılar bulunan, tıpkı stream.write (',' .join (name_list)) gibi şeyler yazıyor, ancak dizeleri birleştirmeden bir for döngüsü içinde yapıyor, çünkü birçok yazma var ...
e.tadeu


Bu cevabın ilk üç satırı bana gerçekten yardımcı oldu, benzer bir zorluk yaşıyordu.
kakule

Yanıtlar:


151

Çoğu zaman ilk yinelemeyi sonuncusu yerine özel durum yapmak daha kolaydır (ve daha ucuzdur) :

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

    item()

Bu, herhangi bir yinelenebilir, hatta aşağıdakileri olmayanlar için işe yarar len():

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

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

Bunun dışında, ne yapmaya çalıştığınıza bağlı olarak genel olarak üstün bir çözüm olduğunu düşünmüyorum. Örneğin, listeden bir dize oluşturuyorsanız, "özel durumlu" str.join()bir fordöngü kullanmaktan doğal olarak kullanmak daha iyidir .


Aynı prensibi kullanarak daha kompakt:

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

Tanıdık geliyor, değil mi? :)


@Ofko ve gerçekten bir yinelenebilir olanın mevcut değerinin son değer olup olmadığını gerçekten öğrenmesi gereken diğerleri için len(), ileriye bakmanız gerekecek:

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

O zaman bu şekilde kullanabilirsiniz:

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

1
Doğru, bu şekilde benimkinden daha iyi görünüyor, en azından numaralandırılmış ve len kullanmaya gerek yok.
e.tadeu

Evet, ancak ifdöngü iki döngüye ayrılırsa önlenebilecek bir tane daha ekler . Ancak, bu yalnızca büyük bir veri listesi yinelendiğinde geçerlidir.
Adam Matan

İki döngüye bölünmeyle ilgili sorun, DRY'yi ihlal etmesi veya sizi yöntemleri tanımlamaya zorlamasıdır.
e.tadeu

Gerçekten son örneğinizi anlamaya çalışıyorum (bu benim kodumda kusursuz bir şekilde çalışıyor), ama nasıl çalıştığını anlamıyorum (arkasındaki fikir)
Olivier Pons

1
@OlivierPons Python'un yineleyici protokolünü anlamalısınız: Bir nesne için yineleyici alıyorum ve ile ilk değeri alıyorum next(). Sonra bir yineleyici tek başına yinelenebilir olduğunu, bu yüzden forbitkinlik, ikinci değerden son değere kadar yineleme kadar döngüde kullanabilirsiniz . Bu sırada yineleyiciden aldığım geçerli değeri yerel olarak ve yieldbunun yerine sonuncusunu tutarım . Bu şekilde, gelecek bir değer daha olduğunu biliyorum. For döngüsünden sonra, her değeri ancak sonuncusu rapor ettim.
Ferdinand Beyer

20

Bu soru oldukça eski olmasına rağmen, buraya google üzerinden geldim ve oldukça basit bir yol buldum: Liste dilimleme. Tüm liste girişleri arasına '&' koymak istediğinizi varsayalım.

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

Bu '1 & 2 & 3' değerini döndürür.


7
Birleştirme işlevini yeni uyguladınız: "" & ".join ([x içinde l için str (x)])
Bryan Oakley

dize birleştirme biraz verimsizdir. Eğer len(l)=1000000bu örnekte, program bir süre çalışacaktır. appendafaik önerilir. l=[1,2,3]; l.append(4);
plhn

18

'Arasındaki kod' Head-Tail şablonunun bir örneğidir .

Bir öğeniz var, onu (öğe arasında) çiftleri izliyor. Bunu ayrıca bir dizi (öğe, arasında) ve ardından bir öğe dizisi olarak da görüntüleyebilirsiniz. İlk öğeyi özel ve diğer öğelerini "standart" durum olarak almak genellikle daha basittir.

Ayrıca, kodun tekrarlanmasını önlemek için, tekrarlamak istemediğiniz kodu içeren bir işlev veya başka bir nesne sağlamanız gerekir. Bir if ifadesini bir zaman dışında her zaman yanlış olan bir döngü içine gömmek saçmadır.

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 )

Bu daha güvenilirdir çünkü kanıtlamak biraz daha kolaydır, Fazladan bir veri yapısı oluşturmaz (yani listenin bir kopyası) ve bir kez dışında her zaman yanlış olan bir if koşulunun çok fazla boşa harcanmasını gerektirmez .


4
İşlev çağrıları ififadelerden çok daha yavaştır, böylece “boşa giden yürütme” argümanı tutmaz.
Ferdinand Beyer

1
İşlev çağrısı ile if-deyimi arasındaki hız farkının herhangi bir şeyle ne ilgisi olduğundan emin değilim. Mesele şu ki, bu formülasyonun her zaman yanlış olan bir if-ifadesi yoktur (bir kez hariç)
S.Lott

1
İfadenizi “… ve bir kez dışında her zaman yanlış olan bir if durumunun çok fazla boşa harcanmasını gerektirmez.”… Ve birkaç ifs kaydettiği için daha hızlıdır . Açıkçası sadece “kod temizliği” ne mi atıfta bulunuyorsunuz?
Ferdinand Beyer

ifPython topluluğu tarafından gerçekten daha temiz kabul edilen bir ifade kullanmak yerine bir işlev tanımlamak mı ?
Markus von Broady

17

Sadece içindeki son öğeyi değiştirmek data_lististiyorsanız gösterimi kullanabilirsiniz:

L[-1]

Ancak, bundan daha fazlasını yaptığınız anlaşılıyor. Yolunda gerçekten yanlış bir şey yok. Hatta şablon etiketleri için bazı Django koduna hızlı bir bakış attım ve temelde ne yaptığınızı yapıyorlar.


1
Ben değiştirmiyorum, bir şey yapmak için kullanıyorum
e.tadeu

4
@ e.tadeu Üzerinde değişiklik yapıp yapmadığınız bile önemli değil. İf ifadenizi şu şekilde değiştirmek: if data != datalist[-1]:ve diğer her şeyi aynı tutmak bence bunu kodlamanın en iyi yolu olacaktır.
spacetyper

8
@spacetyper Bu son değer benzersiz olmadığında kırılır.
Ark-kun

14

öğeler benzersiz ise:

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

diğer seçenekler:

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

Bu, Ants Aasma'nın yaklaşımına benzer, ancak itertools modülünü kullanmadan. Ayrıca, yineleyici akışında tek bir öğeyi ileriye taşıyan gecikmeli bir yineleyicidir:

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

Sonraki değere göz atmak için giriş verilerinin üzerinde kayan bir pencere kullanabilir ve son değeri tespit etmek için bir sentinel kullanabilirsiniz. Bu herhangi bir yinelenebilir üzerinde çalışır, bu yüzden önceden uzunluğu bilmenize gerek yoktur. İkili uygulama itertools tariflerinden .

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

Son öğe dışında her şeyi yineleme ve sonuncuyu döngü dışında tedavi etme olanağı yok mu? Sonuçta, döngü yaptığınız tüm öğelere benzer bir şey yapmak için bir döngü oluşturulur; bir öğenin özel bir şeye ihtiyacı varsa, döngüde olmamalıdır.

(ayrıca şu soruya bakın: bir döngü içinde son elemanı-ayrı bir muameleyi hak ediyor )

DÜZENLEME: soru daha çok "aradaki" ile ilgili olduğundan, ya ilk öğe, selefine sahip olmaması nedeniyle özeldir ya da son öğe, ardılı olmaması nedeniyle özeldir.


Ancak son eleman, listedeki diğer tüm elemanlara benzer şekilde ele alınmalıdır. Sorun sadece unsurlar arasında yapılması gereken şey .
e.tadeu

Bu durumda, birincisi selefi olmayan tek kişidir. Bunu ayırın ve liste genel kodunun geri kalanını gözden geçirin.
xtofl

3

@ Ethan-t yaklaşımını seviyorum ama while Truebenim açımdan tehlikeli.

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

Burada, data_listson eleman listenin ilk elemanına değere eşit olur. L ile değiştirilebilir data_listancak bu durumda döngüden sonra boş kalır. while Trueişlemeden önce listenin boş olmadığını veya kontrol gerekmediğini kontrol ederseniz de kullanabilirsiniz (ah!).

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

İyi tarafı hızlı olması. Kötü - yıkılabilir ( data_listboş olur).

En sezgisel çözüm:

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}')

Oh evet, zaten teklif ettin!


2

100 000 döngüye sahip olmayacak ve 100 000 "if" ifadesi kaydetmek istemediğiniz sürece yolunuzla ilgili yanlış bir şey yoktur. Bu durumda, şu şekilde gidebilirsiniz:

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

Çıktılar :

1
2
3
3

Ama gerçekten, sizin durumunuzda aşırıya kaçmış gibi hissediyorum.

Her durumda, muhtemelen dilimleme ile daha şanslı olacaksınız:

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

#outputs
1
2
last : 3

ya da sadece :

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

#outputs
1
2
3
last : 3

Sonunda, bir şeyler yapmanın KISS yolu ve bu, aşağıdakiler olmadan da dahil olmak üzere herhangi bir yinelenebilirle işe yarayacaktır __len__:

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

Çıkışlardan:

1
2
3
3

Eğer bu şekilde yaparsam, benim için basit görünüyor.


2
Ama dikkat bu iterable [-1] için çalışmaz tüm (sahip olmayan bu tür jeneratör gibi Iterables len )
e.tadeu

İstediğiniz tek şey döngüden sonraki son öğeye erişmekse, itemkullanarak yeniden hesaplamak yerine sadece öğesini kullanın list[-1]. Ama yine de: OP'nin istediği şey bu değil mi?
Ferdinand Beyer

Re: iterable.__iter__() - __doğrudan fonksiyonları çağırmayın lütfen . Olmalı iter(iterable).
PaulMcG

2

Dilimleme ve isson öğeyi kontrol etmek için kullanın :

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>

Uyarı emptoru : Bu yalnızca listedeki tüm öğeler aslında farklıysa (bellekte farklı konumlara sahipse) çalışır. Kaputun altında, Python eşit öğeleri algılayabilir ve aynı nesneleri onlar için yeniden kullanabilir. Örneğin, aynı değere sahip dizeler ve ortak tamsayılar için.


2

Eğer listeden geçiyorsanız benim için bu da işe yaradı:

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

2

Google beni bu eski soruya getirdi ve bence bu soruna farklı bir yaklaşım ekleyebilirim.

Buradaki cevapların çoğu, istendiği gibi bir for döngüsü denetiminin uygun bir şekilde ele alınmasını ele alacaktı, ancak data_list'i imha edilebilir ise, boş bir listeyle bitene kadar öğeleri listeden çıkarmanızı öneririm:

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

Hatta kullanabilirsiniz iken len (element_list) son elemanı ile bir şey yapmanıza gerek yoksa. Bu çözümü next () ile uğraşırken daha şık buluyorum.


2

Benim için, bir listenin sonunda özel bir vakayı ele almanın en basit ve pitonik yolu:

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

Tabii ki bu, ilk elemanı özel bir şekilde işlemek için de kullanılabilir.


2

Geri sayım yapmak yerine, geri sayım da yapabilirsiniz:

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

1

Son öğenin özel kullanımını döngüden sonrasına erteleyin.

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

1

Birden çok yol olabilir. dilimleme en hızlı olacaktır. .İndex () yöntemini kullanan bir tane daha ekleme:

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

0

Bir yineleyici olarak girdi varsayarsak, itertools'tan tee ve izip kullanmanın bir yolu:

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.

Demo:

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

Aklıma gelen en basit çözüm:

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

Bu nedenle, işleme tek bir yinelemeyi geciktirerek her zaman bir öğeye bakıyoruz. İlk yineleme sırasında bir şey yapmayı atlamak için hatayı yakalarım.

Tabii ki biraz düşünmek gerekir, NameErroristediğiniz zaman yükseltilmesi için.

Ayrıca `` ülkeleri koru ''

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

Bu, yeni adın daha önce tanımlanmadığına bağlıdır. Paranoyaksanız, aşağıdakileri newkullanarak var olmadığından emin olabilirsiniz :

try:
    del new
except NameError:
    pass

Alternatif olarak elbette bir if ifadesi ( if notfirst: print(new) else: notfirst = True) de kullanabilirsiniz . Ama bildiğim kadarıyla ek yük daha büyük.


Using `timeit` yields:

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

bu yüzden ek yükün seçilemez olmasını bekliyorum.


0

Öğeleri bir kez sayın ve kalan öğe sayısını takip edin:

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

Bu şekilde listenin uzunluğunu yalnızca bir kez değerlendirirsiniz. Bu sayfadaki çözümlerin çoğu, uzunluğun önceden mevcut olmadığını varsayar, ancak bu sorunuzun bir parçası değildir. Uzunluğa sahipseniz, kullanın.


0

Akla gelen basit bir çözüm:

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

Bir sayaç kullanılarak ikinci derecede eşit derecede basit bir çözüm elde edilebilir:

# 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.