Paarsgewijs cirkelvormige Python ‘for’-lus

Is er een leuke Python-manier om een ​​lijst te herhalen en een paar elementen opnieuw af te stemmen? Het laatste element moet worden gekoppeld aan het eerste.

Dus als ik bijvoorbeeld de lijst [1, 2, 3] heb, wil ik de volgende paren:

  • 1 – 2
  • 2 – 3
  • 3 – 1

Antwoord 1, autoriteit 100%

Een Pythonische manier om een ​​lijst paarsgewijs te openen is: zip(L, L[1:]). Om het laatste item met het eerste te verbinden:

>>> L = [1, 2, 3]
>>> zip(L, L[1:] + L[:1])
[(1, 2), (2, 3), (3, 1)]

Antwoord 2, autoriteit 43%

Ik zou een dequegebruiken met zipom dit te bereiken.

>>> from collections import deque
>>>
>>> l = [1,2,3]
>>> d = deque(l)
>>> d.rotate(-1)
>>> zip(l, d)
[(1, 2), (2, 3), (3, 1)]

Antwoord 3, autoriteit 41%

Ik zou een kleine wijziging aanbrengen in het pairwise-recept uit de itertoolsdocumentatie:

def pairwise_circle(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ... (s<last>,s0)"
    a, b = itertools.tee(iterable)
    first_value = next(b, None)
    return itertools.zip_longest(a, b,fillvalue=first_value)

Hiermee blijft gewoon een verwijzing naar de eerste waarde behouden en wanneer de tweede iterator is uitgeput, zal zip_longestde laatste plaats vullen met de eerste waarde.

(Houd er rekening mee dat het werkt met iterators zoals generatoren en iterables zoals lijsten/tupels.)

Merk op dat @Barry’s oplossinghier erg op lijkt, maar naar mijn mening iets gemakkelijker te begrijpen en gemakkelijker uit te breiden is voorbij één element.


Antwoord 4, autoriteit 33%

Ik zou itertools.cyclemet zip:

import itertools
def circular_pairwise(l):
    second = itertools.cycle(l)
    next(second)
    return zip(l, second)

cycleretourneert een iterabele die de waarden van zijn argument in volgorde oplevert, in een lus van de laatste waarde naar de eerste.

We slaan de eerste waarde over, dus deze begint op positie 1(in plaats van 0).

Vervolgens zipwe het met de originele, niet-gemuteerde lijst. zipis goed, omdat het stopt wanneer een van zijn argument-iterables is uitgeput.

Als je het op deze manier doet, vermijd je het maken van tussenliggende lijsten: cyclebevat een verwijzing naar het origineel, maar kopieert het niet. zipwerkt op dezelfde manier.

Het is belangrijk op te merken dat dit niet werkt als de invoer een iteratoris, zoals een file, (of een mapof zipin ), aangezien het vooruitgaan op één plaats (via next(second)) automatisch de iterator in alle andere zal bevorderen. Dit is eenvoudig op te lossen met itertools.tee, die twee onafhankelijk werkende iterators produceert over de originele iterable:

def circular_pairwise(it):
    first, snd = itertools.tee(it)
    second = itertools.cycle(snd)
    next(second)
    return zip(first, second)

teekangrote hoeveelheden extra opslagruimte gebruiken, bijvoorbeeld als een van de geretourneerde iterators is opgebruikt voordat de andere wordt aangeraakt, maar zoals we alleen ooit hebben één stap verschil, de extra opslagruimte is minimaal.


Antwoord 5, autoriteit 25%

Er zijn efficiëntere manieren (die geen tijdelijke lijsten maken), maar ik denk dat dit de meest beknopte is:

> l = [1,2,3]
> zip(l, (l+l)[1:])
[(1, 2), (2, 3), (3, 1)]

Antwoord 6, autoriteit 19%

Paarsgewijs cirkelvormige Python ‘for’-lus

Als je het geaccepteerde antwoord leuk vindt,

zip(L, L[1:] + L[:1])

je kunt veel meer geheugen gebruiken met semantisch dezelfde code met behulp van itertools:

from itertools import islice, chain #, izip as zip # uncomment if Python 2

En dit materialiseert nauwelijks iets in het geheugen buiten de originele lijst (ervan uitgaande dat de lijst relatief groot is):

zip(l, chain(islice(l, 1, None), islice(l, None, 1)))

Om te gebruiken, gewoon consumeren (bijvoorbeeld met een lijst):

>>> list(zip(l, chain(islice(l, 1, None), islice(l, None, 1))))
[(1, 2), (2, 3), (3, 1)]

Dit kan uitbreidbaar worden gemaakt tot elke breedte:

def cyclical_window(l, width=2):
    return zip(*[chain(islice(l, i, None), islice(l, None, i)) for i in range(width)])

en gebruik:

>>> l = [1, 2, 3, 4, 5]
>>> cyclical_window(l)
<itertools.izip object at 0x112E7D28>
>>> list(cyclical_window(l))
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
>>> list(cyclical_window(l, 4))
[(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 1), (4, 5, 1, 2), (5, 1, 2, 3)]

Onbeperkte generatie met itertools.teemet cycle

U kunt ook teegebruiken om te voorkomen dat u een redundant cyclusobject maakt:

from itertools import cycle, tee
ic1, ic2 = tee(cycle(l))
next(ic2)    # must still queue up the next item

en nu:

>>> [(next(ic1), next(ic2)) for _ in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

Dit is ongelooflijk efficiënt, een verwacht gebruik van itermet nexten elegant gebruik van cycle, tee, en zip.

Geef cycleniet rechtstreeks door aan list, tenzij u uw werk hebt opgeslagen en tijd hebt om uw computer stil te laten staan ​​omdat u het maximale uit het geheugen hebt gehaald – als je hebt geluk, na een tijdje zal je besturingssysteem het proces beëindigen voordat het je computer laat crashen.

Pure Python ingebouwde functies

Eindelijk, geen standaard lib-import, maar dit werkt alleen voor de lengte van de originele lijst (anders IndexError.)

>>> [(l[i], l[i - len(l) + 1]) for i in range(len(l))]
[(1, 2), (2, 3), (3, 1)]

Je kunt dit voortzetten met modulo:

>>> len_l = len(l)
>>> [(l[i % len_l], l[(i + 1) % len_l]) for i in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

Antwoord 7, autoriteit 18%

Ik zou een lijstbegrip gebruiken en profiteren van het feit dat l[-1]het laatste element is.

>>> l = [1,2,3]
>>> [(l[i-1],l[i]) for i in range(len(l))]
[(3, 1), (1, 2), (2, 3)]

Je hebt op die manier geen tijdelijke lijst nodig.


Antwoord 8, autoriteit 17%

Verbazingwekkend hoeveel verschillende manieren er zijn om dit probleem op te lossen.

Hier is er nog een. Je kunt het recept pairwisegebruiken, maar in plaats van te zippen met b, chainhet met het eerste element dat je er al uit hebt gehaald. We hoeven niet te cycleals we maar één extra waarde nodig hebben:

from itertools import chain, izip, tee
def pairwise_circle(iterable):
    a, b = tee(iterable)
    first = next(b, None)
    return izip(a, chain(b, (first,)))

Antwoord 9, autoriteit 10%

Ik hou van een oplossing die de originele lijst niet wijzigt en de lijst niet naar tijdelijke opslag kopieert:

def circular(a_list):
    for index in range(len(a_list) - 1):
        yield a_list[index], a_list[index + 1]
    yield a_list[-1], a_list[0]
for x in circular([1, 2, 3]):
    print x

Uitvoer:

(1, 2)
(2, 3)
(3, 1)

Ik kan me voorstellen dat dit wordt gebruikt voor een aantal zeer grote gegevens in het geheugen.


Antwoord 10, autoriteit 7%

Deze werkt zelfs als de lijst lhet grootste deel van het systeemgeheugen heeft verbruikt. (Als iets garandeert dat dit geval onmogelijk is, dan is zip zoals gepost door chepner prima)

l.append( l[0] )
for i in range( len(l)-1):
   pair = l[i],l[i+1]
   # stuff involving pair
del l[-1] 

of meer algemeen (werkt voor elke offset nd.w.z. l[ (i+n)%len(l) ])

for i in range( len(l)):
   pair = l[i], l[ (i+1)%len(l) ]
   # stuff

op voorwaarde dat je op een systeem zit met behoorlijk snelle modulo-deling (d.w.z. niet een of ander ingebed systeem met een erwtbrein).

Er lijkt een veelgehoorde overtuiging te zijn dat het indexeren van een lijst met een integer subscript niet-pythonisch is en het best vermeden kan worden. Waarom?


Antwoord 11, autoriteit 6%

Dit is mijn oplossing, en het ziet er volgens mij Pythonisch genoeg uit:

l = [1,2,3]
for n,v in enumerate(l):
    try:
        print(v,l[n+1])
    except IndexError:
        print(v,l[0])

afdrukken:

1 2
2 3
3 1

De generatorfunctieversie:

def f(iterable):
    for n,v in enumerate(iterable):
        try:
            yield(v,iterable[n+1])
        except IndexError:
            yield(v,iterable[0])
>>> list(f([1,2,3]))
[(1, 2), (2, 3), (3, 1)]

Antwoord 12, autoriteit 4%

Wat dacht je hiervan?

li = li+[li[0]]
pairwise = [(li[i],li[i+1]) for i in range(len(li)-1)]

Antwoord 13, autoriteit 4%

from itertools import izip, chain, islice
itr = izip(l, chain(islice(l, 1, None), islice(l, 1)))

(Zoals hierboven met @j-f-sebastian’s “zip”-antwoord, maar met itertools.)

NB: BEWERKTgegeven nuttige zetje van @200_success. was eerder:

itr = izip(l, chain(l[1:], l[:1]))

Antwoord 14, autoriteit 3%

Als je niet te veel geheugen wilt gebruiken, kun je mijn oplossing proberen:

[(l[i], l[(i+1) % len(l)]) for i, v in enumerate(l)]

Het is iets langzamer, maar verbruikt minder geheugen.


Antwoord 15, autoriteit 2%

Nog een poging

>>> L = [1,2,3]
>>> zip(L,L[1:]) + [(L[-1],L[0])]
[(1, 2), (2, 3), (3, 1)]

Antwoord 16, autoriteit 2%

Vanaf Python 3.10, de nieuwe pairwise-functie biedt een manier om glijdende paren van opeenvolgende elementen te maken:

from itertools import pairwise
# l = [1, 2, 3]
list(pairwise(l + l[:1]))
# [(1, 2), (2, 3), (3, 1)]

of gewoon pairwise(l + l[:1])als je het resultaat niet nodig hebt als list.


Merk op dat we pairwiseop de lijst toegevoegd met zijn kop (l + l[:1]), zodat rollende paren cirkelvormig zijn (dwz zodat we ook het (3, 1)paar):

list(pairwise(l)) # [(1, 2), (2, 3)]
l + l[:1] # [1, 2, 3, 1]

Antwoord 17

L = [1, 2, 3]
a = zip(L, L[1:]+L[:1])
voor ik in een:
b = lijst(i)
afdrukken b


Antwoord 18

dit lijkt erop dat combinaties het werk zouden doen.

from itertools import combinations
x=combinations([1,2,3],2)

dit zou een generator opleveren. dit kan dan als zodanig worden herhaald

for i in x:
  print i

de resultaten zouden er ongeveer zo uitzien

(1, 2)
(1, 3)
(2, 3)

Other episodes