Önceki ve sonraki değerlere de erişen Python döngüsü


91

Önceki, mevcut ve sonraki öğelere erişerek bir nesne listesi üzerinde nasıl yineleme yapabilirim? Python'daki bu C / C ++ kodu gibi mi?


Foo listenin başında veya sonunda ise ne olmalı? Şu anda bu, dizinizin sınırlarının dışına çıkacak.
Brian

2
"foo" nun ilk oluşumuna ihtiyacınız varsa, eşleştiğinde "for" bloğundan "break" yapın.
minibüs

Yinelemeye 1'inci (0'ıncı değil) öğesinde başlayıp sonuncu ama bir öğede yinelemeyi bitirmek mi istiyorsunuz?
smci

fooListede tam olarak bir kez gerçekleşeceği garanti ediliyor mu? Çarpma meydana gelirse, buradaki bazı yaklaşımlar başarısız olur veya sadece ilkini bulur. Ve asla gerçekleşmezse, diğer yaklaşımlar başarısız olur veya ValueError gibi istisnalar atar. Bazı test senaryoları vermek yardımcı olabilirdi.
smci

Ayrıca, buradaki örneğiniz, her ikisi de bilinen bir uzunluğa sahip olan ve indekslenebilir olan bir nesne dizisidir. Buradaki yanıtlardan bazıları, her zaman indekslenemeyen, her zaman uzunlukları olmayan ve her zaman sonlu olmayan yineleyiciler için genelleme yapıyor ..
smci

Yanıtlar:


111

Bu hile yapmalı.

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]

İşte enumeratefonksiyonla ilgili dokümanlar .


18
Ancak, yerleşik bir işlev olduğu için değişken adınız olarak 'ileri' kullanmamanız muhtemelen en iyi uygulamadır.
mkosmala

1
Bunun düzenlenmiş versiyonu hala mantıksal olarak sağlam değildir: Döngünün sonunda objve next_istenmeyen yan etkilere sahip olabilecek son yineleme için aynı nesne olacaktır.
TemporalWolf

Soru ifadesi açıkça OP'nin yinelemeye 1'inci (0'ıncı değil) öğesinde başlamak istediğini ve yinelemeyi sonuncu öğede bitirmek istediğini söylüyor. Yani indexkaçması 1 ... (l-1)değil, 0 ... lburada olduğu gibi, ve gerek eğer fıkralarını special kasalı. Btw, bir parametre var enumerate(..., start=1)ama için değil end. Yani gerçekten kullanmak istemiyoruz enumerate().
smci

147

Çözümler şimdiye kadar sadece listelerle ilgileniyor ve çoğu listeyi kopyalıyor. Benim deneyimlerime göre bu pek çok kez mümkün değil.

Ayrıca, listede tekrarlanan öğelere sahip olabileceğiniz gerçeğiyle ilgilenmezler.

Sorunuzun başlığında " Bir döngü içindeki önceki ve sonraki değerler , ancak burada çoğu yanıtı bir döngü içinde çalıştırırsanız, bulmak için her öğede tüm listeyi tekrar yinelersiniz.

Bu yüzden yeni bir fonksiyon yarattım. itertoolsmodülü kullanarak yinelenebilir olanı böler ve dilimler ve önceki ve sonraki öğelerle birlikte tuples oluşturur. Kodunuzun yaptığı tam olarak bu değil, ancak bir göz atmaya değer çünkü muhtemelen sorununuzu çözebilir.

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)

Sonra bir döngüde kullanın ve içinde önceki ve sonraki öğelere sahip olacaksınız:

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

Sonuçlar:

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

Herhangi bir boyut listesiyle (çünkü listeyi kopyalamadığı için) ve herhangi bir yinelenebilir (dosyalar, kümeler, vb.) İle çalışacaktır. Bu şekilde, sırayı yineleyebilir ve önceki ve sonraki öğelerin döngü içinde kullanılabilir olmasını sağlayabilirsiniz. Sıradaki öğeyi tekrar aramaya gerek yoktur.

Kodun kısa bir açıklaması:

  • tee giriş dizisi üzerinde 3 bağımsız yineleyici verimli bir şekilde oluşturmak için kullanılır
  • chainiki diziyi bir diziye bağlar; tek unsur sırası eklemek için kullanılan [None]içinprevs
  • isliceilki dışındaki tüm öğelerin bir sırasını oluşturmak chainiçin kullanılır , daha sonra birNone sonuna kadar
  • Şimdi buna benzer 3 bağımsız sekans var some_iterable:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • son izipolarak 3 diziyi bir üçlü diziye dönüştürmek için kullanılır.

Not izipherhangi bir giriş sekansı bitkin aldığında son elemanı bu yüzden, durur prevsson öğe onun olacağını böyle öğe bulunsa - doğrudur göz ardı edilecektir, prev. Son öğeleri çıkarmaya çalışabiliriz, prevsancak izipdavranışları bu gereksiz

Ayrıca unutmayın tee, izip, isliceve chaingelen itertoolsmodül; Girdi dizileri üzerinde anında (tembel olarak) çalışırlar, bu da onları verimli kılar ve tüm dizinin herhangi bir zamanda bir kerede bellekte olması gerekliliğini doğurmaz.

İçinde python 3, içe aktarırken bir hata gösterecek izip, zipyerine kullanabilirsiniz izip. İthalat gerek yok zip, bu önceden tanımlanmıştır python 3- kaynağını


3
@becomingGuru: SO'yu Python referans belgelerinin aynasına dönüştürmek gerekli değildir. Tüm bu işlevler resmi belgelerde çok güzel bir şekilde açıklanmıştır (örneklerle)
Eli Bendersky

1
@becomingGuru: Belgelere bir bağlantı eklendi.
nosklo

6
@LakshmanPrasad Wiki havasındayım, bu yüzden bazı açıklamalar ekledim :-).
Kos

7
Python 3'ün izipyerleşik zipişlevle değiştirilebileceğini belirtmekte fayda var ;-)
Tomasito665

1
Bu güzel bir çözüm, ancak fazlasıyla karmaşık görünüyor. Bundan ilham alan stackoverflow.com/a/54995234/1265955 sayfasına bakın .
Victoria

6

Bir liste anlayışı kullanarak, şu anki, önceki ve sonraki öğeleri içeren 3'lü bir demet döndür:

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

Sadece yerleşik işlevleri kullandığı ve diğer ofsetlere kolayca genişletilebildiği için bunun nasıl ortaya çıkmadığını henüz bilmiyorum:

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

Sınır hatası olmayan jeneratörlerin kullanıldığı bir sürümü burada bulabilirsiniz:

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

Lütfen sınır davranışının, kodunuzun aksine, ilk veya son öğede asla "foo" aramamamız olduğuna dikkat edin. Yine, sınır semantikleri garip ... ve kodunuzdan anlaşılması zor :)


2

python> = 2.5 için kısalık için koşullu ifadeler kullanma

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

Buna bir çözüm arayan ve aynı zamanda öğeleri döngüye sokmak isteyen herkes için, aşağıda işe yarayabilir -

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)

son sonraki içinde alt çizgi Düzenlenemiyor - "en az 6 olmalı ..."
Xpector

Bu, neden herhangi bir şekilde basit bir döngü ve erişime tercih edilir objects[i-1], objects[i], objects[i+1]? veya bir jeneratör? Bana tamamen belirsiz görünüyor. Ayrıca PREV ve NEXT verilerin kopyalarını çıkardığı için gereksiz yere 3x bellek kullanır.
smci

@smci Listedeki i+1son öğe için çalışan bir yaklaşımı nasıl elde edersiniz ? O zaman bir sonraki unsur ilk olmalıdır. Sınırların dışına çıkıyorum.
ElectRocnic

1

Jeneratör kullanmak oldukça basittir:

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 )

Yok ve sinyal değeriyle aynı değeri içeren testlerin hala çalıştığını unutmayın, çünkü sinyal değerinin kontrolü "eşittir" kullanır ve sinyal Python'un intern yapmadığı bir değerdir. Bununla birlikte, herhangi bir tekil işaretleyici değeri bir sinyal olarak kullanılabilir, bu da bazı durumlarda kullanıcı kodunu basitleştirebilir.


9
"Oldukça basit"
Miguel Stevens

'Gözcü' demek istediğinizde 'sinyal' deme. Ayrıca, if iB is signalsinyal = Yok olmadıkça, nesneleri eşitlik açısından karşılaştırmak için asla kullanmayın , bu durumda doğrudan yazın None. iterYerleşikleri gölgelediği için argüman adı olarak kullanmayın iter(). Aynen next. Her neyse, jeneratör yaklaşımı basitçe olabiliryield prev, curr, next_
smci

@smci Belki de sözlüğünüz sinyal ve sentinel ile ilgili benimkinden farklı tanımlara sahiptir. Eşit değere sahip diğer öğeler için değil, belirli öğe için test etmek istediğim için özellikle "is" kullandım, "eşittir" bu test için doğru operatördür. Yinelemenin ve sonraki gölgenin kullanımı yalnızca başka şekilde referans verilmeyen, bu nedenle sorun olmayan, ancak kabul edilen, en iyi uygulama olmayan şeylerdir. Son talebinize bağlam sağlamak için daha fazla kod göstermeniz gerekir.
Victoria

@Victoria: 'sentinel [değer]' iyi tanımlanmış yazılım terimidir, 'sinyal' değildir (sinyal işleme veya çekirdek sinyallerinden bahsetmez). [Python'daki şeyleri ileis yerine ==sen dizeleri interning CPython dayanarak konum çünkü dizeleri bunun yanına, ama o zaman bile: neden], bu bilinen bir çukur var, burada birden çok nedeni vardır v1 = 'monkey'; v2 = 'mon'; v3 = 'key, o v1 is (v2 + v3)verir False. Ve eğer kodunuz ints / dizeler yerine nesneleri kullanmaya geçerse, kullanmak isbozulacaktır. Yani genel olarak ==eşitliği karşılaştırmak için kullanmalısınız .
smci

@smci Bilgisayar yazılımındaki en zor sorun, farklı terimler kullanan farklı insan grupları nedeniyle iletişim, ağın çalışmamasıdır. Söylendiği gibi, standartlar harika, herkesin bir tane var. Python == ile operatörler arasındaki farkı tam olarak anlıyorum ve tam da bu yüzden kullanmayı seçtim. Eğer önyargılı "terminoloji ve kurallarınızın" ötesine bakarsanız, == 'un diziyi sonlandırmaya eşit olan herhangi bir öğeye izin vereceğini, ancak kullanımının yalnızca sinyal olarak kullanılan belirli nesnede sona ereceğini fark edersiniz. (veya isterseniz nöbetçi).
Victoria

1

İki basit çözüm:

  1. Hem önceki hem de sonraki değerler için değişkenlerin tanımlanması gerekiyorsa:
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. Listedeki tüm değerlerin geçerli değer değişkeni tarafından çevrilmesi gerekiyorsa:
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

indexListede nerede somevalueolduğunu bulmak için kullanabilirsiniz ve ardından gerektiğinde öncekini ve sonrakini alabilirsiniz:


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 bu oldukça hızlı olmalı, ancak ben test etmedim:

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

Örnek kullanım:

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

Bunun işe yaradığını düşünüyorum ve karmaşık değil

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]

Python'un dizilerde negatif indeksleri desteklediğini çok az biliyor muydum, teşekkür ederim!
ElectRocnic

1
Yine de sonunda başarısız olacak :(
ElectRocnic

0

Çok C / C ++ tarzı çözüm:

    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 ve zarif yol:

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
Sonunda olursa başarısız olur value. Ayrıca geçen elemanını döndürür previous_valueeğer valueilk biridir.
Trang Oul

Gereksinimlerinize bağlıdır. Negatif indeks previous_value listesinden son öğe dönecek ve next_valueyükseltecektir IndexErrorve şu hata
ImportError

Bazen bu yöntemi arıyordum .. şimdi anladım .. teşekkürler @ImportError. Artık senaryomu güzelce genişletebilirim ..
Azam

Sınırlama: valuebirden fazla kez meydana gelebilir objects, ancak .index()kullanımı yalnızca ilk oluşumunu bulur (veya oluşmazsa ValueError).
smci
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.