Apa cara pythonic untuk mendeteksi elemen terakhir dalam loop 'untuk'?

188

Saya ingin tahu cara terbaik (cara yang lebih ringkas dan "pythonic") untuk melakukan perawatan khusus untuk elemen terakhir dalam for for. Ada sepotong kode yang harus dipanggil hanya antara elemen, ditekan di yang terakhir.

Inilah cara saya saat ini melakukannya:

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

Apakah ada cara yang lebih baik?

Catatan: Saya tidak ingin membuatnya dengan peretasan seperti menggunakan reduce.;)

e.tadeu
sumber
Bagaimana dengan dia yang pertama? Haruskah itu ditekan juga?
Adam Matan
dapatkah Anda memberi tahu kami apa yang sedang dilakukan antar elemen?
SilentGhost
2
Saya ingin mendapatkan jawaban untuk kasus umum, tetapi kasus konkret di mana saya memerlukan ini menulis hal-hal di aliran, dengan pemisah di antaranya, seperti stream.write (',' .join (name_list)), tetapi melakukannya dalam lingkaran untuk tanpa menyatukan senar, karena ada banyak menulis ...
e.tadeu
Tiga baris pertama dari jawaban ini sangat membantu saya, mengalami tantangan yang sama.
kapulaga

Jawaban:

151

Sebagian besar waktu lebih mudah (dan lebih murah) untuk membuat iterasi pertama sebagai kasus khusus daripada yang terakhir:

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

    item()

Ini akan bekerja untuk semua yang dapat diubah, bahkan bagi mereka yang tidak memiliki len():

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

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

Terlepas dari itu, saya tidak berpikir ada solusi yang umumnya unggul karena tergantung pada apa yang Anda coba lakukan. Misalnya, jika Anda membuat string dari daftar, secara alami lebih baik menggunakan str.join()daripada menggunakan forloop "dengan case khusus".


Menggunakan prinsip yang sama tetapi lebih kompak:

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

Terlihat akrab, bukan? :)


Untuk @ofko, dan orang lain yang benar-benar perlu mencari tahu apakah nilai saat ini dari iterable without len()adalah yang terakhir, Anda perlu melihat ke depan:

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

Maka Anda dapat menggunakannya seperti ini:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False
Ferdinand Beyer
sumber
1
Benar, cara ini tampaknya lebih baik daripada milikku, setidaknya tidak perlu menggunakan enumerate dan len.
e.tadeu
Ya, tapi itu menambahkan yang lain ifyang bisa dihindari jika loop itu dibagi menjadi dua loop. Namun, ini hanya relevan ketika mengulang daftar data yang sangat besar.
Adam Matan
Masalah dengan memecah menjadi dua loop adalah bahwa itu melanggar KERING atau memaksa Anda untuk menentukan metode.
e.tadeu
Saya benar-benar mencoba untuk memahami contoh terakhir Anda (yang bekerja dengan sempurna dalam kode saya), tetapi saya tidak mengerti cara kerjanya (ide di balik)
Olivier Pons
1
@ OlivierPons Anda perlu memahami protokol iterator Python: Saya mendapatkan iterator untuk suatu objek, dan mengambil nilai pertama dengan next(). Lalu saya mengeksploitasi iterator itu iterable dengan sendirinya, jadi saya bisa menggunakannya dalam forloop sampai habis, iterasi dari yang kedua ke nilai terakhir. Selama ini saya menyimpan nilai saat ini saya mengambil dari iterator secara lokal dan yieldyang terakhir sebagai gantinya. Dengan cara ini saya tahu ada satu nilai lagi yang akan datang. Setelah for for loop, saya telah melaporkan setiap nilai selain yang terakhir.
Ferdinand Beyer
20

Meskipun pertanyaan itu sudah cukup lama, saya datang ke sini melalui google dan saya menemukan cara yang cukup sederhana: Daftar mengiris. Katakanlah Anda ingin meletakkan '&' di antara semua entri daftar.

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

Ini mengembalikan '1 & 2 & 3'.

BeckmaR
sumber
7
Anda baru saja mengimplementasikan kembali fungsi join: `" & ".join ([str (x) for x in l])
Bryan Oakley
penggabungan string agak tidak efisien. Jika len(l)=1000000dalam contoh ini, program akan berjalan untuk sementara waktu. appenddianjurkan afaik. l=[1,2,3]; l.append(4);
plhn
18

'Kode antar' adalah contoh pola Kepala-Ekor .

Anda memiliki item, yang diikuti oleh urutan pasangan (antara, item). Anda juga dapat melihat ini sebagai urutan (item, di antara) pasangan yang diikuti oleh item. Secara umum lebih mudah untuk mengambil elemen pertama sebagai spesial dan yang lainnya sebagai case "standar".

Selanjutnya, untuk menghindari kode berulang, Anda harus menyediakan fungsi atau objek lain untuk mengandung kode yang tidak ingin Anda ulangi. Menanamkan pernyataan if dalam satu lingkaran yang selalu salah kecuali satu kali agak konyol.

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 )

Ini lebih dapat diandalkan karena sedikit lebih mudah untuk dibuktikan, itu tidak membuat struktur data tambahan (yaitu, salinan daftar) dan tidak memerlukan banyak pemborosan pelaksanaan kondisi jika yang selalu salah kecuali sekali.

S.Lott
sumber
4
Panggilan fungsi jauh lebih lambat daripada ifpernyataan sehingga argumen "eksekusi sia-sia" tidak berlaku.
Ferdinand Beyer
1
Saya tidak yakin apa perbedaan kecepatan antara panggilan fungsi dan jika-pernyataan ada hubungannya dengan apa pun. Intinya adalah bahwa formulasi ini tidak memiliki pernyataan if yang selalu salah (kecuali sekali.)
S.Lott
1
Saya menafsirkan pernyataan Anda "... dan tidak memerlukan banyak eksekusi yang sia-sia dari kondisi if yang selalu salah kecuali sekali" sebagai "... dan lebih cepat karena menghemat beberapa ifs". Jelas Anda hanya merujuk pada "kebersihan kode"?
Ferdinand Beyer
Apakah mendefinisikan fungsi alih-alih menggunakan ifpernyataan benar-benar dianggap bersih oleh komunitas Python?
Markus von Broady
17

Jika Anda hanya ingin memodifikasi elemen terakhir data_listmaka Anda dapat menggunakan notasi:

L[-1]

Namun, sepertinya Anda melakukan lebih dari itu. Tidak ada yang salah dengan caramu. Saya bahkan melihat sekilas beberapa kode Django untuk tag templat mereka dan mereka pada dasarnya melakukan apa yang Anda lakukan.

Bartek
sumber
1
Saya tidak memodifikasinya, saya menggunakannya untuk melakukan sesuatu
e.tadeu
4
@ e.tadeu bahkan tidak masalah apakah Anda memodifikasinya atau tidak. Mengubah pernyataan if Anda ke: if data != datalist[-1]:dan menjaga yang lainnya tetap sama akan menjadi cara terbaik untuk mengkode ini menurut saya.
spacetyper
8
@spacetyper Ini rusak ketika nilai terakhir tidak unik.
Ark-kun
14

jika barangnya unik:

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

pilihan lain:

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
Palza
sumber
10

Ini mirip dengan pendekatan Ants Aasma tetapi tanpa menggunakan modul itertools. Ini juga merupakan iterator lagging yang memandang ke depan elemen tunggal dalam aliran iterator:

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
Andrew Dalke
sumber
4

Anda dapat menggunakan jendela geser di atas data input untuk mengintip nilai berikutnya dan menggunakan sentinel untuk mendeteksi nilai terakhir. Ini berfungsi pada semua iterable, jadi Anda tidak perlu tahu panjang sebelumnya. Implementasi berpasangan adalah dari resep 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
Semut Aasma
sumber
3

Apakah tidak ada kemungkinan untuk beralih pada semua-kecuali elemen terakhir, dan memperlakukan yang terakhir di luar loop? Lagi pula, sebuah loop dibuat untuk melakukan sesuatu yang mirip dengan semua elemen yang Anda loop; jika satu elemen membutuhkan sesuatu yang istimewa, itu seharusnya tidak ada di loop.

(lihat juga pertanyaan ini: apakah-elemen-terakhir-dalam-loop-berhak-perlakuan-terpisah )

EDIT: karena pertanyaannya lebih tentang "di antara", baik elemen pertama adalah yang istimewa karena tidak memiliki pendahulunya, atau elemen terakhir adalah khusus karena tidak memiliki penggantinya.

xtofl
sumber
Tetapi elemen terakhir harus diperlakukan sama dengan setiap elemen lain dalam daftar. Masalahnya adalah hal yang harus dilakukan hanya antar elemen.
e.tadeu
Dalam hal itu, yang pertama adalah satu-satunya tanpa pendahulu. Pisahkan yang satu itu, dan lewati kode umum daftar lainnya.
xtofl
3

Saya suka pendekatan @ ethan-t, tetapi while Trueberbahaya dari sudut pandang saya.

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

Di sini, data_listadalah agar elemen terakhir sama dengan nilai dengan yang pertama dari daftar. L dapat ditukar dengan data_listtetapi dalam kasus ini hasilnya kosong setelah loop. while Truejuga dimungkinkan untuk digunakan jika Anda memeriksa daftar itu tidak kosong sebelum pemrosesan atau pemeriksaan tidak diperlukan (aduh!).

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

Bagian yang baik adalah cepat. Yang buruk - itu bisa dirusak ( data_listmenjadi kosong).

Solusi paling intuitif:

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 ya, Anda sudah mengusulkannya!

Aliaksandr Klimovich
sumber
2

Tidak ada yang salah dengan cara Anda, kecuali jika Anda akan memiliki 100.000 loop dan ingin menyimpan 100.000 pernyataan "jika". Dalam hal ini, Anda dapat melakukannya:

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

Output:

1
2
3
3

Tapi sungguh, dalam kasusmu aku merasa itu berlebihan.

Bagaimanapun, Anda mungkin akan lebih beruntung dengan mengiris:

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

#outputs
1
2
last : 3

atau hanya :

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

#outputs
1
2
3
last : 3

Akhirnya, cara KISS untuk melakukan hal-hal Anda, dan itu akan bekerja dengan iterable, termasuk yang tanpa __len__:

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

Ouputs:

1
2
3
3

Jika saya merasa akan melakukannya dengan cara itu, tampaknya sederhana bagi saya.

e-satis
sumber
2
Tetapi perhatikan bahwa iterable [-1] tidak akan bekerja untuk semua iterables (seperti generator yang tidak memiliki len )
e.tadeu
Jika semua yang Anda inginkan adalah mengakses item terakhir setelah loop, cukup gunakan itemalih-alih menghitung ulang menggunakan list[-1]. Namun demikian: Saya kira ini bukan yang diminta OP, bukan?
Ferdinand Beyer
Re: iterable.__iter__() - tolong jangan panggil __fungsi secara langsung. Seharusnya iter(iterable).
PaulMcG
2

Gunakan irisan dan is untuk memeriksa elemen terakhir:

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 : Ini hanya berfungsi jika semua elemen dalam daftar sebenarnya berbeda (memiliki lokasi yang berbeda dalam memori). Di bawah tenda, Python dapat mendeteksi elemen yang sama dan menggunakan kembali objek yang sama untuk mereka. Misalnya, untuk string dengan nilai yang sama dan bilangan bulat umum.

Roger Dahl
sumber
2

jika Anda membaca daftar, bagi saya ini juga berfungsi:

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

Google membawa saya ke pertanyaan lama ini dan saya pikir saya bisa menambahkan pendekatan yang berbeda untuk masalah ini.

Sebagian besar jawaban di sini akan berurusan dengan perlakuan yang tepat untuk kontrol loop seperti yang ditanyakan, tetapi jika data_list dirusak, saya akan menyarankan agar Anda membuka item dari daftar sampai Anda berakhir dengan daftar kosong:

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

Anda bahkan dapat menggunakan saat len ​​(element_list) jika Anda tidak perlu melakukan apa pun dengan elemen terakhir. Saya menemukan solusi ini lebih elegan daripada berurusan dengan next ().

Anderson Santos
sumber
2

Bagi saya cara paling sederhana dan pythonic untuk menangani kasus khusus di akhir daftar adalah:

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

Tentu saja ini juga dapat digunakan untuk memperlakukan elemen pertama dengan cara khusus.

Chris
sumber
2

Alih-alih menghitung, Anda juga dapat menghitung mundur:

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

Tunda penanganan khusus item terakhir hingga setelah putaran.

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

Mungkin ada beberapa cara. mengiris akan menjadi yang tercepat. Menambahkan satu lagi yang menggunakan metode .index ():

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

Mengasumsikan input sebagai iterator, inilah cara menggunakan tee dan izip dari itertools:

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
>>>
Segera
sumber
0

Solusi paling sederhana yang muncul di pikiran saya adalah:

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

Jadi kami selalu melihat ke depan satu item dengan menunda pemrosesan satu iterasi. Untuk melewatkan melakukan sesuatu selama iterasi pertama saya cukup menangkap kesalahan.

Tentu saja Anda perlu berpikir sedikit, agar NameError dapat dinaikkan ketika Anda menginginkannya.

Juga pertahankan counstruct

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

Ini bergantung pada nama baru yang sebelumnya tidak didefinisikan. Jika Anda paranoid, Anda dapat memastikan bahwa newtidak ada menggunakan:

try:
    del new
except NameError:
    pass

Atau Anda tentu saja dapat juga menggunakan pernyataan if ( if notfirst: print(new) else: notfirst = True). Tapi sejauh yang saya tahu biaya overhead lebih besar.


Using `timeit` yields:

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

jadi saya berharap overhead tidak bisa dipilih.

DerWeh
sumber
0

Hitung item satu kali dan teruskan dengan jumlah item yang tersisa:

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

Dengan cara ini Anda hanya mengevaluasi panjang daftar sekali. Banyak solusi di halaman ini tampaknya menganggap panjangnya tidak tersedia sebelumnya, tetapi itu bukan bagian dari pertanyaan Anda. Jika Anda memiliki panjangnya, gunakan itu.

Ethan T
sumber
0

Salah satu solusi sederhana yang muncul dalam pikiran adalah:

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

Solusi kedua yang sama-sama sederhana dapat dicapai dengan menggunakan penghitung:

# 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
Semuanya baik-baik saja
sumber