Loop Python yang juga mengakses nilai sebelumnya dan berikutnya

90

Bagaimana cara saya mengulang daftar objek, mengakses item sebelumnya, saat ini, dan berikutnya? Seperti kode C / C ++ ini, dengan 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];
    }
}
dir01
sumber
Apa yang akan terjadi jika foo ada di awal atau akhir daftar? Saat ini, ini akan di luar batas array Anda.
Brian
2
jika Anda membutuhkan kemunculan pertama "foo", maka lakukan "break" dari blok "for" ketika cocok.
van
Apakah Anda ingin memulai iterasi pada elemen ke-1 (bukan ke-0), dan mengakhiri iterasi pada elemen terakhir-tapi-satu?
smci
Apakah dijamin itu footerjadi tepat sekali dalam daftar? Jika terjadi perkalian, beberapa pendekatan di sini akan gagal, atau hanya menemukan yang pertama. Dan jika itu tidak pernah terjadi, pendekatan lain akan gagal, atau memunculkan pengecualian seperti ValueError. Memberikan beberapa kasus uji akan membantu.
smci
Selain itu, contoh Anda di sini adalah urutan objek, yang keduanya memiliki panjang yang diketahui, dan dapat diindeks. Beberapa jawaban di sini digeneralisasikan ke iterator, yang tidak selalu dapat diindeks, tidak selalu memiliki panjang, dan tidak selalu terbatas ..
smci

Jawaban:

107

Ini seharusnya berhasil.

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]

Berikut dokumen tentang enumeratefungsinya.

Hank Gay
sumber
17
Tapi mungkin praktik terbaik untuk tidak menggunakan 'next' sebagai nama variabel Anda, karena ini adalah fungsi bawaan.
mkosmala
1
Versi yang diedit ini masih tidak terdengar logis: Di akhir loop objdan next_akan menjadi objek yang sama untuk iterasi terakhir, yang mungkin memiliki efek samping yang tidak diinginkan.
TemporalWolf
Pernyataan pertanyaan secara eksplisit mengatakan OP ingin memulai iterasi pada elemen ke-1 (bukan ke-0), dan mengakhiri iterasi pada elemen terakhir-tapi-satu. Jadi indexharus dijalankan dari 1 ... (l-1), tidak 0 ... lseperti yang Anda miliki di sini, dan tidak perlu klausa if dengan kasing khusus. Btw, ada parameter enumerate(..., start=1)tapi tidak untuk end. Jadi kami tidak benar-benar ingin menggunakan enumerate().
smci
147

Solusi sampai saat ini hanya berurusan dengan daftar, dan kebanyakan adalah menyalin daftar tersebut. Dalam pengalaman saya, banyak kali hal itu tidak mungkin.

Juga, mereka tidak berurusan dengan fakta bahwa Anda dapat memiliki elemen berulang dalam daftar.

Judul pertanyaan Anda mengatakan "Nilai sebelumnya dan berikutnya di dalam satu lingkaran ", tetapi jika Anda menjalankan sebagian besar jawaban di sini di dalam loop, Anda akan berulang kali mengulang seluruh daftar di setiap elemen untuk menemukannya.

Jadi saya baru saja membuat fungsi itu. menggunakanitertools modul, membagi dan memotong iterable, dan menghasilkan tupel dengan elemen sebelumnya dan berikutnya bersama-sama. Tidak persis seperti yang dilakukan kode Anda, tetapi patut untuk dilihat, karena mungkin dapat menyelesaikan masalah Anda.

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)

Kemudian gunakan dalam satu lingkaran, dan Anda akan memiliki item sebelumnya dan berikutnya di dalamnya:

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

Hasil:

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

Ini akan bekerja dengan daftar ukuran apa pun (karena tidak menyalin daftar), dan dengan iterable apa pun (file, set, dll). Dengan cara ini Anda bisa mengulang urutan, dan memiliki item sebelumnya dan berikutnya tersedia di dalam loop. Tidak perlu mencari lagi item tersebut secara berurutan.

Penjelasan singkat tentang kode tersebut:

  • tee digunakan untuk membuat 3 iterator independen secara efisien di atas urutan input
  • chainmenghubungkan dua urutan menjadi satu; ini digunakan di sini untuk menambahkan urutan elemen tunggal[None] keprevs
  • islice digunakan untuk membuat urutan semua elemen kecuali yang pertama, lalu chain digunakan untuk menambahkan aNone ke ujungnya
  • Sekarang ada 3 urutan independen berdasarkan some_iterable tampilan tersebut:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • akhirnya izip digunakan untuk mengubah 3 urutan menjadi satu urutan triplet.

Perhatikan bahwa izipberhenti ketika urutan input apa pun habis, jadi elemen terakhir prevsakan diabaikan, yang benar - tidak ada elemen sehingga elemen terakhir akan menjadi miliknya prev. Kami dapat mencoba untuk melepaskan elemen terakhir dari prevstapiizip perilaku membuatnya menjadi berlebihan

Juga mencatat bahwa tee, izip, islicedanchain berasal dari itertoolsmodul; mereka beroperasi pada urutan masukan mereka secara on-the-fly (malas), yang membuatnya efisien dan tidak memperkenalkan kebutuhan untuk memiliki seluruh urutan dalam memori sekaligus setiap saat.

Dalam python 3, ia akan menampilkan kesalahan saat mengimpor izip, Anda dapat menggunakan zipbukan izip. Tidak perlu impor zip, itu adalah standar di python 3- sumber

nosklo.dll
sumber
3
@becomingGuru: tidak perlu mengubah SO menjadi cermin dari dokumen referensi Python. Semua fungsi ini dijelaskan dengan sangat baik (dengan contoh) dalam dokumentasi resmi
Eli Bendersky
1
@becomingGuru: Menambahkan tautan ke dokumentasi.
nosklo
6
@LakshmanPrasad Saya sedang dalam mood wiki jadi saya telah menambahkan beberapa penjelasan :-).
Kos
7
Mungkin perlu disebutkan bahwa di Python 3 izipdapat diganti dengan zipfungsi
bawaan
1
Ini adalah solusi yang bagus, tetapi tampaknya terlalu rumit. Lihat stackoverflow.com/a/54995234/1265955 yang terinspirasi oleh yang satu ini.
Victoria
6

Menggunakan pemahaman daftar, kembalikan 3-tupel dengan elemen saat ini, sebelumnya dan berikutnya:

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)]
RYS
sumber
4

Saya tidak tahu bagaimana ini belum muncul karena hanya menggunakan fungsi bawaan dan dengan mudah dapat diperpanjang ke offset lain:

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)
Eric Ceko
sumber
4

Berikut adalah versi yang menggunakan generator tanpa kesalahan batas:

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

Harap perhatikan bahwa perilaku batasannya adalah kami tidak pernah mencari "foo" di elemen pertama atau terakhir, tidak seperti kode Anda. Sekali lagi, semantik batasnya aneh ... dan sulit dipahami dari kode Anda :)

moshez
sumber
2

menggunakan ekspresi kondisional untuk ringkasnya 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)
makapuf
sumber
2

Bagi siapa pun yang mencari solusi untuk ini dengan juga ingin memutar elemen, di bawah ini mungkin berhasil -

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)
skr47ch.dll
sumber
garis bawah di next_ terakhir? Tidak dapat mengedit - "minimal harus 6 ..."
Xpector
Mengapa ini lebih disukai daripada pengulangan dan pengaksesan sederhana objects[i-1], objects[i], objects[i+1]? atau generator? Sepertinya itu sangat kabur bagi saya. Juga tidak perlu menggunakan memori 3x karena PREV dan NEXT membuat salinan data.
smci
@smci Bagaimana Anda mendapatkan i+1pendekatan yang berfungsi untuk elemen terakhir dalam daftar? Elemen selanjutnya harus menjadi yang pertama. Saya keluar dari batas.
ElectRocnic
1

Menggunakan generator, ini cukup sederhana:

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 )

Perhatikan bahwa pengujian yang menyertakan Tidak Ada dan nilai yang sama dengan nilai sinyal tetap berfungsi, karena pemeriksaan nilai sinyal menggunakan "adalah" dan sinyal tersebut adalah nilai yang tidak dimiliki Python. Setiap nilai penanda tunggal dapat digunakan sebagai sinyal, yang mungkin menyederhanakan kode pengguna dalam beberapa keadaan.

Victoria
sumber
8
"Ini cukup sederhana"
Miguel Stevens
Jangan ucapkan 'sinyal' saat yang Anda maksud adalah 'sentinel'. Juga, jangan pernah gunakan if iB is signaluntuk membandingkan objek untuk persamaan, kecuali sinyal = Tidak ada, dalam hal ini langsung saja tulis None. Jangan gunakan itersebagai nama argumen karena itu membayangi bawaan iter(). Ditto next. Pokoknya pendekatan generator dapat dengan mudahyield prev, curr, next_
smci
@smci Mungkin kamus Anda memiliki definisi yang berbeda menurut saya, mengenai sinyal dan sentinel. Saya secara khusus menggunakan "adalah" karena saya ingin menguji item tertentu, bukan untuk item lain dengan nilai yang sama, "adalah" adalah operator yang benar untuk pengujian itu. Penggunaan iter dan next shadow hanya hal-hal yang tidak direferensikan sebaliknya, jadi bukan masalah, tapi disepakati, bukan best practice. Anda perlu menunjukkan lebih banyak kode untuk memberikan konteks untuk klaim terakhir Anda.
Victoria
@ Victoria: 'sentinel [nilai]' adalah istilah perangkat lunak yang terdefinisi dengan baik, 'sinyal' tidak (tidak berbicara tentang pemrosesan sinyal, atau sinyal kernel). Mengenai [membandingkan hal-hal dengan Python dengan isalih - alih ==], ini adalah perangkap yang terkenal, berikut adalah beberapa alasan mengapa: Anda dapat menggunakannya untuk string, karena Anda mengandalkan string interning cPython, tetapi bahkan kemudian v1 = 'monkey'; v2 = 'mon'; v3 = 'key, kemudian v1 is (v2 + v3)memberikan False. Dan jika kode Anda pernah beralih menggunakan objek alih-alih int / string, penggunaan isakan rusak. Jadi secara umum Anda harus menggunakan ==untuk membandingkan kesetaraan.
smci
@smci Masalah tersulit dalam perangkat lunak komputer adalah komunikasi, jaringan tidak berfungsi, karena kelompok orang yang berbeda menggunakan istilah yang berbeda. Seperti kata pepatah, standar itu bagus, setiap orang memilikinya. Saya sepenuhnya memahami perbedaan antara Python == and is operator, dan itulah mengapa saya memilih untuk menggunakan is. Jika Anda melihat melewati "terminoologi dan aturan" yang terbentuk sebelumnya, Anda akan menyadari bahwa == akan memungkinkan item apa pun yang membandingkan sama untuk menghentikan urutan, sedangkan penggunaan is hanya akan berakhir pada objek spesifik yang digunakan sebagai sinyal (atau sentinel jika Anda mau).
Victoria
1

Dua solusi sederhana:

  1. Jika variabel untuk nilai sebelumnya dan selanjutnya harus ditentukan:
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. Jika semua nilai dalam daftar harus dilintasi oleh variabel nilai saat ini:
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
Serge Tochilov
sumber
0

Anda bisa menggunakan indexdi daftar untuk menemukan di mana somevaluedan kemudian mendapatkan sebelumnya dan berikutnya sesuai kebutuhan:


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'


John Montgomery
sumber
0

AFAIK ini seharusnya cukup cepat, tetapi saya tidak mengujinya:

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

Contoh penggunaan:

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

Saya pikir ini berhasil dan tidak rumit

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]
Soda Fries
sumber
Sedikit yang saya tahu bahwa python mendukung indeks negatif dalam array, terima kasih!
ElectRocnic
1
Ini akan gagal pada akhirnya :(
ElectRocnic
0

Solusi gaya Very 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.")
Marcelo Azambuja
sumber
-1

Cara pythonic dan elegan:

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
ImportError
sumber
1
Itu akan gagal jika valuedi akhir. Juga, kembalikan elemen terakhir seolah- previous_valueolah valueadalah yang pertama.
Trang Oul
Itu tergantung pada kebutuhan Anda. Indeks negatif dari previous_value akan mengembalikan elemen terakhir dari daftar dan next_valueakan memunculkan IndexErrordan itu kesalahan
ImportError
Saya telah mencari metode ini cukup sering..sekarang saya mengerti .. terima kasih @ImportError. Sekarang saya dapat mengembangkan skrip saya dengan baik ..
Azam
Batasan: valuebisa terjadi lebih dari sekali objects, tetapi menggunakan .index()hanya akan menemukan kemunculan pertama (atau ValueError jika tidak terjadi).
smci