Cách pythonic để phát hiện phần tử cuối cùng trong vòng lặp 'for' là gì?


186

Tôi muốn biết cách tốt nhất (nhỏ gọn hơn và "pythonic") để thực hiện một điều trị đặc biệt cho phần tử cuối cùng trong vòng lặp for. Có một đoạn mã chỉ được gọi giữa các phần tử, bị loại bỏ trong phần cuối cùng.

Đây là cách tôi hiện đang là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

Có cách nào tốt hơn không?

Lưu ý: Tôi không muốn thực hiện bằng các bản hack như sử dụng reduce.;)


Còn anh thì sao? Có nên đàn áp quá không?
Adam Matan

bạn có thể cho chúng tôi biết những gì nó đang được thực hiện giữa các yếu tố?
SilentGhost

2
Tôi muốn nhận được câu trả lời cho một trường hợp chung, nhưng một trường hợp cụ thể mà tôi cần điều này là viết mọi thứ trên một luồng, với các dấu phân cách ở giữa chúng, giống như stream.write (',' .join (name_list)), nhưng thực hiện nó trong một vòng lặp for mà không nối các chuỗi, bởi vì có nhiều ghi ...
e.tadeu


Ba dòng đầu tiên của câu trả lời này thực sự giúp tôi, đã có một thử thách tương tự.
thảo quả

Câu trả lời:


151

Hầu hết các lần dễ dàng hơn (và rẻ hơn) để thực hiện lần lặp đầu tiên thành trường hợp đặc biệt thay vì lần cuối cùng:

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

    item()

Điều này sẽ làm việc cho bất kỳ lặp đi lặp lại, ngay cả đối với những người không có len():

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

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

Ngoài ra, tôi không nghĩ có một giải pháp chung vượt trội vì nó phụ thuộc vào những gì bạn đang cố gắng làm. Ví dụ: nếu bạn đang xây dựng một chuỗi từ một danh sách, thì sử str.join()dụng một cách tự nhiên tốt hơn là sử dụng một forvòng lặp với trường hợp đặc biệt.


Sử dụng cùng một nguyên tắc nhưng nhỏ gọn hơn:

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

Trông quen quá phải không? :)


Đối với @ofko và những người khác thực sự cần tìm hiểu xem giá trị hiện tại của một lần lặp mà không có giá trị len()cuối cùng, bạn sẽ cần nhìn về phía trước:

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

Sau đó, bạn có thể sử dụng nó như thế này:

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

1
Đúng, cách này có vẻ tốt hơn của tôi, ít nhất là nó không cần sử dụng liệt kê và len.
e.tadeu

Có, nhưng nó thêm một ifcái khác có thể tránh được nếu vòng lặp được chia thành hai vòng. Tuy nhiên, điều này chỉ có liên quan khi lặp lại một danh sách dữ liệu khổng lồ.
Adam Matan

Vấn đề với việc chia thành hai vòng là nó vi phạm DRY hoặc nó buộc bạn phải xác định các phương thức.
e.tadeu

Tôi thực sự cố gắng để hiểu ví dụ cuối cùng của bạn (hoạt động hoàn hảo trong mã của tôi), nhưng tôi không hiểu cách thức hoạt động của nó (ý tưởng đằng sau)
Olivier Pons

1
@OlivierPons Bạn cần hiểu giao thức iterator của Python: Tôi nhận được một iterator cho một đối tượng và lấy giá trị đầu tiên bằng next(). Sau đó, tôi khai thác rằng một trình vòng lặp có thể lặp lại được, vì vậy tôi có thể sử dụng nó trong forvòng lặp cho đến khi cạn kiệt, lặp lại từ lần thứ hai đến giá trị cuối cùng. Trong thời gian này, tôi giữ giá trị hiện tại mà tôi đã lấy từ iterator cục bộ và yieldgiá trị cuối cùng thay thế. Bằng cách này, tôi biết có một giá trị nữa sẽ đến. Sau vòng lặp for, tôi đã báo cáo mọi giá trị trừ giá trị cuối cùng.
Ferdinand Beyer

20

Mặc dù câu hỏi đó khá cũ, tôi đã đến đây qua google và tôi đã tìm thấy một cách khá đơn giản: Liệt kê danh sách. Giả sử bạn muốn đặt '&' giữa tất cả các mục trong danh sách.

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

Điều này trả về '1 & 2 & 3'.


7
Bạn vừa thực hiện lại chức năng tham gia: `" & ".join ([str (x) cho x in l])
Bryan Oakley

nối chuỗi có phần không hiệu quả. Nếu len(l)=1000000trong ví dụ này, chương trình sẽ chạy trong một thời gian. appendđược khuyến nghị afaik. l=[1,2,3]; l.append(4);
plhn

18

'Mã giữa' là một ví dụ về mẫu Head-Tail .

Bạn có một mục, theo sau là một chuỗi các cặp (giữa, mục). Bạn cũng có thể xem đây là một chuỗi các cặp (mục, giữa) theo sau là một mục. Nói chung đơn giản hơn là lấy phần tử đầu tiên là đặc biệt và tất cả các phần tử khác làm trường hợp "tiêu chuẩn".

Hơn nữa, để tránh lặp lại mã, bạn phải cung cấp một hàm hoặc đối tượng khác để chứa mã bạn không muốn lặp lại. Nhúng một nếu tuyên bố trong một vòng lặp mà luôn luôn là sai trừ một thời gian là loại ngớ ngẩn.

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 )

Điều này đáng tin cậy hơn vì dễ chứng minh hơn một chút. Nó không tạo ra cấu trúc dữ liệu bổ sung (nghĩa là bản sao của danh sách) và không yêu cầu thực thi lãng phí một điều kiện nếu luôn luôn sai trừ một lần.


4
Các lệnh gọi hàm được thực hiện chậm hơn sau đó các ifcâu lệnh để đối số thực hiện lãng phí của đối thủ trực tiếp không bị giữ.
Ferdinand Beyer

1
Tôi không chắc sự khác biệt về tốc độ giữa lệnh gọi hàm và câu lệnh if có liên quan gì với bất cứ điều gì. Vấn đề là công thức này không có câu lệnh if luôn luôn sai (trừ một lần.)
S.Lott

1
Tôi đã giải thích câu lệnh của bạn và không yêu cầu nhiều sự lãng phí khi thực hiện một điều kiện if luôn luôn sai ngoại trừ một lần là một trò chơi và nhanh hơn vì nó tiết kiệm được một vài ifgiây. Rõ ràng là bạn chỉ đang đề cập đến sự sạch sẽ của mã Code?
Ferdinand Beyer

Là việc xác định một hàm thay vì sử dụng một ifcâu lệnh thực sự được coi là sạch hơn bởi cộng đồng Python?
Markus von Broady

17

Nếu bạn chỉ đơn giản là tìm cách sửa đổi phần tử cuối cùng data_listthì bạn có thể chỉ cần sử dụng ký hiệu:

L[-1]

Tuy nhiên, có vẻ như bạn đang làm nhiều hơn thế. Không có gì thực sự sai với cách của bạn. Tôi thậm chí đã lướt qua một số mã Django cho các thẻ mẫu của họ và về cơ bản họ đang làm gì.


1
Tôi không sửa đổi nó, tôi đang sử dụng nó để làm một cái gì đó
e.tadeu

4
@ e.tadeu thậm chí không quan trọng nếu bạn sửa đổi nó hay không. Thay đổi câu lệnh if của bạn thành: if data != datalist[-1]:và giữ mọi thứ khác giống nhau sẽ là cách tốt nhất để viết mã này theo ý kiến ​​của tôi.
spacetyper

7
@spacetyper Điều này phá vỡ khi giá trị cuối cùng là không duy nhất.
Ark-kun

14

nếu các mục là duy nhất:

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

sự lựa chọn khác:

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

Cách này tương tự như cách tiếp cận của Kiến Aasma nhưng không sử dụng mô đun itertools. Nó cũng là một trình lặp lặp chậm, trông giống một phần tử duy nhất trong luồng lặp:

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

Bạn có thể sử dụng cửa sổ trượt trên dữ liệu đầu vào để xem nhanh giá trị tiếp theo và sử dụng một thông báo để phát hiện giá trị cuối cùng. Điều này hoạt động trên bất kỳ lần lặp nào, vì vậy bạn không cần phải biết trước độ dài. Việc thực hiện theo cặp là từ công thức nấu ăn 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

Không có khả năng lặp lại trên tất cả - nhưng phần tử cuối cùng và xử lý phần tử cuối cùng bên ngoài vòng lặp? Rốt cuộc, một vòng lặp được tạo ra để làm một cái gì đó tương tự như tất cả các yếu tố bạn lặp lại; nếu một yếu tố cần một cái gì đó đặc biệt, thì nó không nên nằm trong vòng lặp.

(xem thêm câu hỏi này: does-the-last-Element-in-a-loop-xứng đáng-a-tách-điều trị )

EDIT: vì câu hỏi liên quan nhiều hơn đến "ở giữa", nên yếu tố đầu tiên là yếu tố đặc biệt ở chỗ nó không có tiền thân hoặc yếu tố cuối cùng đặc biệt ở chỗ nó không có người kế nhiệm.


Nhưng phần tử cuối cùng nên được xử lý tương tự như mọi phần tử khác trong danh sách. Vấn đề là điều chỉ nên được thực hiện giữa các yếu tố.
e.tadeu

Trong trường hợp đó, người đầu tiên là người duy nhất không có người tiền nhiệm. Hãy tách nó ra và lặp lại phần còn lại của danh sách mã chung.
xtofl

3

Tôi thích cách tiếp cận của @ ethan-t, nhưng while Truenguy hiểm theo quan điểm của tôi.

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

Ở đây, data_listlà để phần tử cuối cùng bằng giá trị của phần tử đầu tiên trong danh sách. L có thể được trao đổi với data_listnhưng trong trường hợp này kết quả là trống sau vòng lặp. while Truecũng có thể sử dụng nếu bạn kiểm tra danh sách đó không trống trước khi xử lý hoặc không cần kiểm tra (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')

Phần tốt là nó nhanh. Cái xấu - nó có thể bị phá hủy ( data_listtrở nên trống rỗng).

Giải pháp trực quan nhất:

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

Ồ vâng, bạn đã đề xuất nó!


2

Không có gì sai với cách của bạn, trừ khi bạn sẽ có 100 000 vòng lặp và muốn lưu 100 000 câu lệnh "nếu". Trong trường hợp đó, bạn có thể đi theo cách đó:

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

Đầu ra:

1
2
3
3

Nhưng thực sự, trong trường hợp của bạn, tôi cảm thấy như nó quá mức cần thiết.

Trong mọi trường hợp, bạn có thể sẽ may mắn hơn với việc cắt lát:

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

#outputs
1
2
last : 3

hoặc chỉ:

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

#outputs
1
2
3
last : 3

Cuối cùng, một cách KISS để làm bạn nhồi nhét, và nó sẽ hoạt động với bất kỳ lần lặp nào, kể cả những cách không có __len__:

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

Ouputs:

1
2
3
3

Nếu cảm thấy như tôi sẽ làm theo cách đó, có vẻ đơn giản với tôi.


2
Nhưng lưu ý rằng iterable [-1] sẽ không hoạt động với tất cả các lần lặp (chẳng hạn như trình tạo không có len )
e.tadeu

Nếu tất cả những gì bạn muốn là truy cập vào mục cuối cùng sau vòng lặp, chỉ cần sử dụng itemthay vì tính lại nó bằng cách sử dụng list[-1]. Nhưng tuy nhiên: Tôi không nghĩ đây là những gì OP yêu cầu, phải không?
Ferdinand Beyer

Re: iterable.__iter__() - xin vui lòng không gọi __chức năng trực tiếp. Nên iter(iterable).
PaulMcG

2

Sử dụng cắt và isđể kiểm tra phần tử cuối cùng:

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 : Điều này chỉ hoạt động nếu tất cả các thành phần trong danh sách thực sự khác nhau (có các vị trí khác nhau trong bộ nhớ). Dưới mui xe, Python có thể phát hiện các phần tử bằng nhau và sử dụng lại các đối tượng tương tự cho chúng. Chẳng hạn, đối với các chuỗi có cùng giá trị và các số nguyên chung.


2

nếu bạn đang đi qua danh sách, đối với tôi điều này cũng hoạt động:

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

2

Google đưa tôi đến câu hỏi cũ này và tôi nghĩ rằng tôi có thể thêm một cách tiếp cận khác cho vấn đề này.

Hầu hết các câu trả lời ở đây sẽ xử lý một cách xử lý thích hợp đối với điều khiển vòng lặp như đã hỏi, nhưng nếu data_list bị phá hủy, tôi sẽ đề nghị bạn bật các mục từ danh sách cho đến khi bạn kết thúc với một danh sách trống:

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

bạn thậm chí có thể sử dụng trong khi len (Element_list) nếu bạn không cần làm gì với phần tử cuối cùng. Tôi thấy giải pháp này thanh lịch hơn sau đó xử lý tiếp theo ().


2

Đối với tôi cách đơn giản và pythonic nhất để xử lý một trường hợp đặc biệt ở cuối danh sách là:

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

Tất nhiên điều này cũng có thể được sử dụng để xử lý yếu tố đầu tiên theo một cách đặc biệt.


2

Thay vì đếm ngược, bạn cũng có thể đếm ngược:

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

1

Trì hoãn việc xử lý đặc biệt của mục cuối cùng cho đến sau vòng lặp.

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

1

Có thể có nhiều cách. cắt lát sẽ nhanh nhất. Thêm một cái nữa sử dụng phương thức .index ():

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

0

Giả sử đầu vào là một trình vòng lặp, đây là cách sử dụng tee và izip từ 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.

Bản giới thiệu:

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

Giải pháp đơn giản nhất đến với tôi là:

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

Vì vậy, chúng tôi luôn nhìn về phía trước một mục bằng cách trì hoãn việc lặp một lần xử lý. Để bỏ qua việc làm một cái gì đó trong lần lặp đầu tiên, tôi chỉ cần bắt lỗi.

Tất nhiên bạn cần phải suy nghĩ một chút, NameErrorđể được nâng lên khi bạn muốn nó.

Cũng giữ nguyên `counstruct

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

Điều này phụ thuộc vào tên mới chưa được xác định trước đó. Nếu bạn bị hoang tưởng, bạn có thể đảm bảo rằng newkhông tồn tại bằng cách sử dụng:

try:
    del new
except NameError:
    pass

Ngoài ra, tất nhiên bạn cũng có thể sử dụng một câu lệnh if ( if notfirst: print(new) else: notfirst = True). Nhưng theo tôi biết thì chi phí lớn hơn.


Using `timeit` yields:

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

Vì vậy, tôi hy vọng chi phí không thể lựa chọn.


0

Đếm các mục một lần và theo kịp số lượng các mục còn lại:

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

Bằng cách này bạn chỉ đánh giá độ dài của danh sách một lần. Nhiều giải pháp trên trang này dường như cho rằng độ dài không có sẵn trước, nhưng đó không phải là một phần câu hỏi của bạn. Nếu bạn có chiều dài, sử dụng nó.


0

Một giải pháp đơn giản mà bạn nghĩ đến là:

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

Một giải pháp đơn giản không kém thứ hai có thể đạt được bằng cách sử dụng bộ đếm:

# 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.