web-dev-qa-db-fra.com

Itérer sur les paires dans une liste (mode circulaire) en Python

Le problème est facile, je veux parcourir chaque élément de la liste et le suivant par paires (en enveloppant le dernier avec le premier).

J'ai réfléchi à deux manières non pythoniques de le faire:

def pairs(lst):
    n = len(lst)
    for i in range(n):
        yield lst[i],lst[(i+1)%n]

et:

def pairs(lst):
    return Zip(lst,lst[1:]+[lst[:1]])

production attendue:

>>> for i in pairs(range(10)):
    print i

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
>>> 

des suggestions sur une façon plus pythonique de le faire? Peut-être existe-t-il une fonction prédéfinie dont je n'ai pas entendu parler?

une version plus générale à plis multiples (avec triplets, quatuors, etc. au lieu de paires) pourrait également être intéressante.

35
fortran

J'ai moi-même codé les versions générales de Tuple, j'aime bien la première pour sa simplicité, plus je la regarde, plus elle me semble pythonique ... après tout, quoi de plus pythonique qu'un liner avec Zip , expansion de l'argument astérisque, compréhension de la liste, découpage de la liste, concaténation de la liste et "plage"?

def ntuples(lst, n):
    return Zip(*[lst[i:]+lst[:i] for i in range(n)])

La version d'itertools devrait être assez efficace même pour les grandes listes ...

from itertools import *
def ntuples(lst, n):
    return izip(*[chain(islice(lst,i,None), islice(lst,None,i)) for i in range(n)])

Et une version pour les séquences non indexables:

from itertools import *
def ntuples(seq, n):
    iseq = iter(seq)
    curr = head = Tuple(islice(iseq, n))
    for x in chain(iseq, head):
        yield curr
        curr = curr[1:] + (x,)

Quoi qu'il en soit, merci à tous pour vos suggestions! :-)

9
fortran
def pairs(lst):
    i = iter(lst)
    first = prev = item = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield item, first

Fonctionne sur toute séquence non vide, aucune indexation requise.

27
Martin v. Löwis

Comme toujours, j'aime le tee:

from itertools import tee, izip, chain

def pairs(iterable):
    a, b = tee(iterable)
    return izip(a, chain(b, [next(b)]))
6
pillmuncher

Cela pourrait être satisfaisant:

def pairs(lst):
    for i in range(1, len(lst)):
        yield lst[i-1], lst[i]
    yield lst[-1], lst[0]

>>> a = list(range(5))
>>> for a1, a2 in pairs(a):
...     print a1, a2
...
0 1
1 2
2 3
3 4
4 0

Si vous aimez ce genre de choses, consultez les articles en python sur wordaligned.org . L'auteur a un amour particulier pour les générateurs en python.

5
hughdbrown

Je le ferais comme ceci (principalement parce que je peux lire ceci):

class Pairs(object):
    def __init__(self, start):
        self.i = start
    def next(self):
        p, p1 = self.i, self.i + 1
        self.i = p1
        return p, p1
    def __iter__(self):
        return self

if __== "__main__":
    x = Pairs(0)
    y = 1
    while y < 20:
        print x.next()
        y += 1

donne:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
2
DrBloodmoney
[(i,(i+1)%len(range(10))) for i in range(10)]

remplacez la plage (10) par la liste souhaitée.

En général, "l'indexation circulaire" est assez facile en python; juste utiliser: 

a[i%len(a)] 
1

Voici une version qui prend en charge un index de démarrage facultatif (par exemple, pour renvoyer (4, 0) en tant que première paire, utilisez start = -1:

import itertools

def iterrot(lst, start = 0):

    if start == 0:
        i = iter(lst)
    Elif start > 0:
        i1 = itertools.islice(lst, start, None)
        i2 = itertools.islice(lst, None, start)
        i = itertools.chain(i1, i2)
    else:
        # islice doesn't support negative slice indices so...
        lenl = len(lst)
        i1 = itertools.islice(lst, lenl + start, None)
        i2 = itertools.islice(lst, None, lenl + start)
        i = itertools.chain(i1, i2)
    return i


def iterpairs(lst, start = 0):

    i = iterrot(lst, start)     

    first = prev = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield prev, first


def itertrios(lst, start = 0):

    i = iterrot(lst, start)     

    first = prevprev = i.next()
    second = prev = i.next()
    for item in i:
        yield prevprev, prev, item
        prevprev, prev = prev, item

    yield prevprev, prev, first
    yield prev, first, second
0
Jay Billfinger

Pour répondre à votre question sur la résolution du problème général:

import itertools

def pair(series, n):
    s = list(itertools.tee(series, n))
    try:
        [ s[i].next() for i in range(1, n) for j in range(i)]
    except StopIteration:
        pass
    while True:
        result = []
        try:
            for j, ss in enumerate(s):
                result.append(ss.next())
        except StopIteration:
            if j == 0:
                break
            else:
                s[j] = iter(series)
                for ss in s[j:]:
                    result.append(ss.next())
        yield result

La sortie est comme ça:

>>> for a in pair(range(10), 2):
...     print a
...
[0, 1]
[1, 2]
[2, 3]
[3, 4]
[4, 5]
[5, 6]
[6, 7]
[7, 8]
[8, 9]
[9, 0]
>>> for a in pair(range(10), 3):
...     print a
...
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 0]
[9, 0, 1]
0
hughdbrown

Version encore plus courte de la solution Zip * de Fortran (avec lambda cette fois;):

group = lambda t, n: Zip(*[t[i::n] for i in range(n)])

group([1, 2, 3, 3], 2)

donne:

[(1, 2), (3, 4)]
0
MKTech

Bien sûr, vous pouvez toujours utiliser un deque :

from collections import deque
from itertools import *

def pairs(lst, n=2):
    itlst = iter(lst)
    start = list(islice(itlst, 0, n-1))
    deq = deque(start, n)
    for elt in chain(itlst, start):
        deq.append(elt)
        yield list(deq)
0
user3552819
def pairs(ex_list):
    for i, v in enumerate(ex_list):
        if i < len(list) - 1:
            print v, ex_list[i+1]
        else:
            print v, ex_list[0]

Enumerate renvoie un tuple avec le numéro d'index et la valeur. J'imprime la valeur et l'élément suivant de la liste ex_list[i+1]. Le if i < len(list) - 1 signifie que si v est not le dernier membre de la liste. Si c'est le cas: print v et le premier élément de la liste print v, ex_list[0].

Modifier:

Vous pouvez le faire retourner une liste. Ajoutez simplement les n-uplets imprimés à une liste et renvoyez-la.

def pairs(ex_list):
    result = []
    for i, v in enumerate(ex_list):
        if i < len(list) - 1:
            result.append((v, ex_list[i+1]))
        else:
            result.append((v, ex_list[0]))
    return result
0
alexpinho98

Cet infini cycle, en bien ou en mal, mais est très clair algorithmiquement. 

from itertools import tee, cycle

def nextn(iterable,n=2):
    ''' generator that yields a Tuple of the next n items in iterable.
    This generator cycles infinitely '''
    cycled = cycle(iterable)
    gens = tee(cycled,n)

    # advance the iterators, this is O(n^2)
    for (ii,g) in Zip(xrange(n),gens):
        for jj in xrange(ii):
            gens[ii].next()

    while True:
        yield Tuple([x.next() for x in gens])


def test():
    data = ((range(10),2),
        (range(5),3),
        (list("abcdef"),4),)
    for (iterable, n) in data:
        gen = nextn(iterable,n)
        for j in range(len(iterable)+n):
            print gen.next()            


test()

donne:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
(0, 1)
(1, 2)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 0)
(4, 0, 1)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
('e', 'f', 'a', 'b')
('f', 'a', 'b', 'c')
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
0
Gregg Lind