Vòng lặp Python cũng truy cập các giá trị trước đó và tiếp theo


90

Làm cách nào để tôi có thể lặp lại danh sách các đối tượng, truy cập các mục trước đó, hiện tại và tiếp theo? Giống như mã C / C ++ này, bằng Python?

foo = somevalue;
previous = next = 0;

for (i=1; i<objects.length(); i++) {
    if (objects[i]==foo) {
        previous = objects[i-1];
        next = objects[i+1];
    }
}

Điều gì sẽ xảy ra nếu foo ở đầu hoặc cuối danh sách? Hiện tại, điều này sẽ vượt ra ngoài giới hạn của mảng của bạn.
Brian

2
nếu bạn cần lần xuất hiện đầu tiên của "foo", thì hãy thực hiện "break" khỏi khối "for" khi khớp.
van

Bạn có muốn bắt đầu lặp lại ở phần tử thứ 1 (không phải thứ 0) và kết thúc lặp lại ở phần tử cuối cùng nhưng một không?
smci

Nó có được đảm bảo rằng nó fooxuất hiện chính xác một lần trong danh sách không? Nếu nó xảy ra nhiều lần, một số cách tiếp cận ở đây sẽ không thành công hoặc chỉ tìm thấy cách đầu tiên. Và nếu nó không bao giờ xảy ra, các cách tiếp cận khác sẽ không thành công hoặc ném ra các ngoại lệ như ValueError. Cung cấp một số tủ thử nghiệm sẽ có ích.
smci

Ngoài ra, ví dụ của bạn ở đây là một chuỗi các đối tượng, cả hai đều có độ dài đã biết và có thể lập chỉ mục. Một số câu trả lời ở đây là tổng quát hóa cho các trình vòng lặp, không phải lúc nào cũng có thể lập chỉ mục, không phải lúc nào cũng có độ dài và không phải lúc nào cũng hữu hạn ..
smci

Câu trả lời:


107

Cái này cần phải dùng mẹo.

foo = somevalue
previous = next_ = None
l = len(objects)
for index, obj in enumerate(objects):
    if obj == foo:
        if index > 0:
            previous = objects[index - 1]
        if index < (l - 1):
            next_ = objects[index + 1]

Đây là tài liệu về enumeratechức năng.


17
Nhưng có lẽ cách tốt nhất là không sử dụng 'next' làm tên biến của bạn, vì nó là một hàm tích hợp sẵn.
mkosmala

1
Phiên bản đã chỉnh sửa này vẫn chưa hợp lý: Ở cuối vòng lặp objnext_sẽ là đối tượng giống nhau cho lần lặp cuối cùng, có thể có tác dụng phụ không mong muốn.
TemporalWolf

Câu hỏi cho biết rõ ràng OP muốn bắt đầu lặp lại ở phần tử thứ 1 (không phải thứ 0) và kết thúc lặp ở phần tử cuối cùng nhưng một. Vì vậy, indexnên chạy từ 1 ... (l-1), không phải 0 ... lnhư bạn có ở đây, và không cần đến các mệnh đề if có chữ cái đặc biệt. Btw, có một tham số enumerate(..., start=1)nhưng không phải cho end. Vì vậy, chúng tôi không thực sự muốn sử dụng enumerate().
smci

147

Các giải pháp cho đến bây giờ chỉ giải quyết các danh sách, và hầu hết là sao chép danh sách. Theo kinh nghiệm của tôi, rất nhiều lần điều đó là không thể.

Ngoài ra, họ không đối phó với thực tế là bạn có thể có các phần tử lặp lại trong danh sách.

Tiêu đề câu hỏi của bạn cho biết " Giá trị trước đó và giá trị tiếp theo bên trong vòng lặp ", nhưng nếu bạn chạy hầu hết các câu trả lời ở đây bên trong một vòng lặp, bạn sẽ phải lặp lại toàn bộ danh sách trên mỗi phần tử để tìm nó.

Vì vậy, tôi vừa tạo một hàm đó. bằng cách sử dụng itertoolsmô-đun, tách và cắt phần có thể lặp lại, đồng thời tạo các bộ giá trị với các phần tử trước và sau cùng nhau. Không chính xác mã của bạn làm gì, nhưng nó đáng để xem, vì nó có thể giải quyết vấn đề của bạn.

from itertools import tee, islice, chain, izip

def previous_and_next(some_iterable):
    prevs, items, nexts = tee(some_iterable, 3)
    prevs = chain([None], prevs)
    nexts = chain(islice(nexts, 1, None), [None])
    return izip(prevs, items, nexts)

Sau đó, sử dụng nó trong một vòng lặp và bạn sẽ có các mục trước đó và tiếp theo trong đó:

mylist = ['banana', 'orange', 'apple', 'kiwi', 'tomato']

for previous, item, nxt in previous_and_next(mylist):
    print "Item is now", item, "next is", nxt, "previous is", previous

Kết quả:

Item is now banana next is orange previous is None
Item is now orange next is apple previous is banana
Item is now apple next is kiwi previous is orange
Item is now kiwi next is tomato previous is apple
Item is now tomato next is None previous is kiwi

Nó sẽ hoạt động với bất kỳ danh sách kích thước nào (vì nó không sao chép danh sách) và với bất kỳ danh sách có thể lặp lại nào (tệp, bộ, v.v.). Bằng cách này, bạn chỉ có thể lặp lại chuỗi và có sẵn các mục trước đó và tiếp theo bên trong vòng lặp. Không cần phải tìm kiếm lại mục trong chuỗi.

Giải thích ngắn gọn về mã:

  • tee được sử dụng để tạo hiệu quả 3 trình vòng lặp độc lập trên chuỗi đầu vào
  • chainliên kết hai chuỗi thành một; nó được sử dụng ở đây để nối chuỗi một phần tử [None]vàoprevs
  • isliceđược sử dụng để tạo một chuỗi tất cả các phần tử ngoại trừ phần tử đầu tiên, sau đó chainđược sử dụng để nối a Nonevào cuối
  • Bây giờ có 3 trình tự độc lập dựa trên some_iterableđó trông giống như:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • cuối cùng izipđược sử dụng để thay đổi 3 trình tự thành một trình tự bộ ba.

Lưu ý rằng izipdừng khi bất kỳ chuỗi đầu vào nào hết, vì vậy phần tử cuối cùng của prevssẽ bị bỏ qua, điều này đúng - không có phần tử nào mà phần tử cuối cùng là của nó prev. Chúng tôi có thể cố gắng loại bỏ các yếu tố cuối cùng khỏi prevsnhưng iziphành vi của nó khiến điều đó trở nên thừa

Cũng lưu ý rằng tee, izip, islicechainđến từ các itertoolsmô-đun; chúng hoạt động trên các trình tự đầu vào của họ một cách nhanh chóng (một cách lười biếng), điều này làm cho chúng hoạt động hiệu quả và không dẫn đến nhu cầu phải có toàn bộ chuỗi trong bộ nhớ cùng một lúc.

Trong python 3, nó sẽ hiển thị một lỗi trong khi nhập izip, bạn có thể sử dụng zipthay thế izip. Không cần nhập zip, nó được xác định trước trong python 3- nguồn


3
@becomingGuru: không cần thiết phải biến SO thành một bản sao của các tài liệu tham chiếu Python. Tất cả các chức năng này được giải thích rất độc đáo (với các ví dụ) trong tài liệu chính thức
Eli Bendersky

1
@becomingGuru: Đã thêm một liên kết đến tài liệu.
nosklo

6
@LakshmanPrasad Tôi đang ở trong tâm trạng wiki nên tôi đã thêm một số giải thích :-).
Kos

7
Nó có thể là đáng nói đến là trong Python 3 izipcó thể được thay thế bằng các built-in zipchức năng ;-)
Tomasito665

1
Đây là một giải pháp hay, nhưng nó có vẻ quá phức tạp. Xem stackoverflow.com/a/54995234/1265955 được lấy cảm hứng từ cái này.
Victoria

6

Sử dụng khả năng hiểu danh sách, trả về một bộ 3 với các phần tử hiện tại, trước đó và tiếp theo:

three_tuple = [(current, 
                my_list[idx - 1] if idx >= 1 else None, 
                my_list[idx + 1] if idx < len(my_list) - 1 else None) for idx, current in enumerate(my_list)]

4

Tôi không biết làm thế nào mà điều này vẫn chưa xuất hiện vì nó chỉ sử dụng các chức năng được tích hợp sẵn và có thể dễ dàng mở rộng sang các hiệu số khác:

values = [1, 2, 3, 4]
offsets = [None] + values[:-1], values, values[1:] + [None]
for value in list(zip(*offsets)):
    print(value) # (previous, current, next)

(None, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, None)

4

Đây là phiên bản sử dụng trình tạo không có lỗi ranh giới:

def trios(iterable):
    it = iter(iterable)
    try:
        prev, current = next(it), next(it)
    except StopIteration:
        return
    for next in it:
        yield prev, current, next
        prev, current = current, next

def find_prev_next(objects, foo):
    prev, next = 0, 0
    for temp_prev, current, temp_next in trios(objects):
        if current == foo:
            prev, next = temp_prev, temp_next
    return prev, next

print(find_prev_next(range(10), 1))
print(find_prev_next(range(10), 0))
print(find_prev_next(range(10), 10))
print(find_prev_next(range(0), 10))
print(find_prev_next(range(1), 10))
print(find_prev_next(range(2), 10))

Xin lưu ý rằng hành vi ranh giới là chúng tôi không bao giờ tìm kiếm "foo" trong phần tử đầu tiên hoặc cuối cùng, không giống như mã của bạn. Một lần nữa, ngữ nghĩa ranh giới rất lạ ... và khó hiểu từ mã của bạn :)


2

sử dụng biểu thức điều kiện để ngắn gọn cho python> = 2.5

def prenext(l,v) : 
   i=l.index(v)
   return l[i-1] if i>0 else None,l[i+1] if i<len(l)-1 else None


# example
x=range(10)
prenext(x,3)
>>> (2,4)
prenext(x,0)
>>> (None,2)
prenext(x,9)
>>> (8,None)

2

Đối với bất kỳ ai đang tìm kiếm giải pháp cho vấn đề này cũng như muốn xoay vòng các phần tử, bên dưới có thể hoạt động:

from collections import deque  

foo = ['A', 'B', 'C', 'D']

def prev_and_next(input_list):
    CURRENT = input_list
    PREV = deque(input_list)
    PREV.rotate(-1)
    PREV = list(PREV)
    NEXT = deque(input_list)
    NEXT.rotate(1)
    NEXT = list(NEXT)
    return zip(PREV, CURRENT, NEXT)

for previous_, current_, next_ in prev_and_next(foo):
    print(previous_, current_, next)

gạch dưới trong next_ cuối cùng? Không thể chỉnh sửa - "ít nhất phải là 6 ..."
Kiểm tra viên

Tại sao điều này lại được ưu tiên hơn so với một vòng lặp và truy cập đơn giản objects[i-1], objects[i], objects[i+1]? hay máy phát điện? Nó chỉ có vẻ hoàn toàn mơ hồ đối với tôi. Ngoài ra, nó không cần sử dụng bộ nhớ 3x vì PREV và NEXT tạo bản sao của dữ liệu.
smci

@smci Làm cách nào để bạn có được i+1cách tiếp cận hoạt động cho phần tử cuối cùng trong danh sách? Yếu tố tiếp theo nên là yếu tố đầu tiên. Tôi vượt ra khỏi giới hạn.
ElectRocnic

1

Sử dụng máy phát điện, nó khá đơn giản:

signal = ['→Signal value←']
def pniter( iter, signal=signal ):
    iA = iB = signal
    for iC in iter:
        if iB is signal:
            iB = iC
            continue
        else:
            yield iA, iB, iC
        iA = iB
        iB = iC
    iC = signal
    yield iA, iB, iC

if __name__ == '__main__':
    print('test 1:')
    for a, b, c in pniter( range( 10 )):
        print( a, b, c )
    print('\ntest 2:')
    for a, b, c in pniter([ 20, 30, 40, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 3:')
    cam = { 1: 30, 2: 40, 10: 9, -5: 36 }
    for a, b, c in pniter( cam ):
        print( a, b, c )
    for a, b, c in pniter( cam ):
        print( a, a if a is signal else cam[ a ], b, b if b is signal else cam[ b ], c, c if c is signal else cam[ c ])
    print('\ntest 4:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 5:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ], ['sig']):
        print( a, b, c )
    print('\ntest 6:')
    for a, b, c in pniter([ 20, ['→Signal value←'], None, '→Signal value←', 60, 70, 80 ], signal ):
        print( a, b, c )

Lưu ý rằng các bài kiểm tra bao gồm Không có và cùng giá trị với giá trị tín hiệu vẫn hoạt động, vì việc kiểm tra giá trị tín hiệu sử dụng "is" và tín hiệu là một giá trị mà Python không thực tập. Tuy nhiên, bất kỳ giá trị mã đánh dấu singleton nào cũng có thể được sử dụng làm tín hiệu, điều này có thể đơn giản hóa mã người dùng trong một số trường hợp.


8
"Nó khá đơn giản"
Miguel Stevens

Đừng nói 'signal' khi bạn muốn nói là 'lính gác'. Ngoài ra, đừng bao giờ sử dụng if iB is signalđể so sánh các đối tượng cho bằng nhau, trừ khi signal = None, trong trường hợp đó chỉ cần viết trực tiếp là Noneđã có. Không sử dụng iterlàm tên đối số vì điều đó làm đổ bóng nội trang iter(). Ditto next. Dù sao thì cách tiếp cận máy phát điện có thể đơn giảnyield prev, curr, next_
smci

@smci Có thể từ điển của bạn có các định nghĩa khác với tôi, liên quan đến tín hiệu và lính canh. Tôi đã đặc biệt sử dụng "is" vì tôi muốn kiểm tra mục cụ thể, không phải đối với các mục khác có giá trị tương đương, "is" là toán tử chính xác cho kiểm tra đó. Chỉ sử dụng iter và bóng tiếp theo những thứ không được tham chiếu khác, vì vậy không phải là vấn đề, nhưng đã được đồng ý, không phải là phương pháp hay nhất. Bạn cần hiển thị thêm mã để cung cấp ngữ cảnh cho yêu cầu cuối cùng của mình.
Victoria

@Victoria: 'sentinel [value]' là thuật ngữ phần mềm được xác định rõ ràng, còn 'signal' thì không (không nói về xử lý tín hiệu hoặc tín hiệu hạt nhân). Như tới [so sánh điều trong Python với isthay vì ==], đó là một cái bẫy nổi tiếng, ở đây nhiều lý do tại sao: bạn có thể nhận được ngay với nó cho các chuỗi, bởi vì bạn đang dựa vào CPython thực tập chuỗi, nhưng thậm chí sau đó v1 = 'monkey'; v2 = 'mon'; v3 = 'key, sau đó v1 is (v2 + v3)cung cấp cho False. Và nếu mã của bạn chuyển sang sử dụng các đối tượng thay vì ints / string, việc sử dụng issẽ bị hỏng. Vì vậy, nói chung bạn nên sử dụng ==để so sánh bình đẳng.
smci

@smci Vấn đề khó nhất trong phần mềm máy tính là liên lạc, mạng không hoạt động, do các nhóm người khác nhau sử dụng các thuật ngữ khác nhau. Như người ta đã nói, các tiêu chuẩn rất tuyệt vời, ai cũng có một. Tôi hoàn toàn hiểu sự khác biệt giữa các toán tử Python == và is, và đó chính là lý do tại sao tôi chọn sử dụng. Nếu bạn xem qua "thuật ngữ và quy tắc" đã định kiến ​​trước của mình, bạn sẽ nhận ra rằng == sẽ cho phép bất kỳ mục nào so sánh bằng để kết thúc chuỗi, trong khi việc sử dụng là sẽ chỉ chấm dứt ở đối tượng Spetific đang được sử dụng làm tín hiệu (hoặc lính canh nếu bạn thích).
Victoria

1

Hai giải pháp đơn giản:

  1. Nếu các biến cho cả giá trị trước đó và giá trị tiếp theo phải được xác định:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = alist[0]
curr = alist[1]

for nxt in alist[2:]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[1]:
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
  1. Nếu tất cả các giá trị trong danh sách phải được chuyển qua bởi biến giá trị hiện tại:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = None
curr = alist[0]

for nxt in alist[1:] + [None]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[2]:
prev: None, curr: Zero, next: One
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
prev: Four, curr: Five, next: None

0

Bạn chỉ có thể sử dụng indextrên danh sách để tìm vị trí somevaluevà sau đó lấy phần trước và phần tiếp theo khi cần:


def find_prev_next(elem, elements):
    previous, next = None, None
    index = elements.index(elem)
    if index > 0:
        previous = elements[index -1]
    if index < (len(elements)-1):
        next = elements[index +1]
    return previous, next


foo = 'three'
list = ['one','two','three', 'four', 'five']

previous, next = find_prev_next(foo, list)

print previous # should print 'two'
print next # should print 'four'



0

AFAIK điều này sẽ khá nhanh, nhưng tôi đã không kiểm tra nó:

def iterate_prv_nxt(my_list):
    prv, cur, nxt = None, iter(my_list), iter(my_list)
    next(nxt, None)

    while True:
        try:
            if prv:
                yield next(prv), next(cur), next(nxt, None)
            else:
                yield None, next(cur), next(nxt, None)
                prv = iter(my_list)
        except StopIteration:
            break

Ví dụ sử dụng:

>>> my_list = ['a', 'b', 'c']
>>> for prv, cur, nxt in iterate_prv_nxt(my_list):
...    print prv, cur, nxt
... 
None a b
a b c
b c None

0

Tôi nghĩ điều này hoạt động và không phức tạp

array= [1,5,6,6,3,2]
for i in range(0,len(array)):
    Current = array[i]
    Next = array[i+1]
    Prev = array[i-1]

Tôi ít biết rằng python hỗ trợ các chỉ số âm trong mảng, cảm ơn bạn!
ElectRocnic

1
Mặc dù vậy, nó sẽ thất bại vào cuối cùng :(
ElectRocnic

0

Giải pháp phong cách rất C / C ++:

    foo = 5
    objectsList = [3, 6, 5, 9, 10]
    prev = nex = 0
    
    currentIndex = 0
    indexHigher = len(objectsList)-1 #control the higher limit of list
    
    found = False
    prevFound = False
    nexFound = False
    
    #main logic:
    for currentValue in objectsList: #getting each value of list
        if currentValue == foo:
            found = True
            if currentIndex > 0: #check if target value is in the first position   
                prevFound = True
                prev = objectsList[currentIndex-1]
            if currentIndex < indexHigher: #check if target value is in the last position
                nexFound = True
                nex = objectsList[currentIndex+1]
            break #I am considering that target value only exist 1 time in the list
        currentIndex+=1
    
    if found:
        print("Value %s found" % foo)
        if prevFound:
            print("Previous Value: ", prev)
        else:
            print("Previous Value: Target value is in the first position of list.")
        if nexFound:
            print("Next Value: ", nex)
        else:
            print("Next Value: Target value is in the last position of list.")
    else:
        print("Target value does not exist in the list.")

-1

Pythonic và cách thanh lịch:

objects = [1, 2, 3, 4, 5]
value = 3
if value in objects:
   index = objects.index(value)
   previous_value = objects[index-1]
   next_value = objects[index+1] if index + 1 < len(objects) else None

1
Nó sẽ thất bại nếu valueở cuối. Ngoài ra, trả về phần tử cuối cùng như previous_valuethể valuelà phần tử đầu tiên.
Trang Oul

Nó phụ thuộc vào yêu cầu của bạn. Chỉ mục phủ định từ previous_value sẽ trả về phần tử cuối cùng từ danh sách và next_valuesẽ tăng lên IndexErrorvà đó là lỗi
ImportError

Tôi đã tìm kiếm phương pháp này khá lâu rồi..tôi hiểu rồi..cảm ơn @ImportError. Bây giờ tôi có thể mở rộng tập lệnh của mình một cách độc đáo ..
Azam

Giới hạn: valuecó thể xảy ra nhiều hơn một lần objects, nhưng việc sử dụng .index()sẽ chỉ tìm thấy lần xuất hiện đầu tiên của nó (hoặc ValueError nếu nó không xảy ra).
smci
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.