web-dev-qa-db-fra.com

Itérer sur une chaîne 2 (ou n) caractères à la fois en Python

Plus tôt aujourd'hui, j'avais besoin d'itérer sur une chaîne 2 caractères à la fois pour analyser une chaîne au format "+c-R+D-E" (il y a quelques lettres supplémentaires).

Je me suis retrouvé avec ceci, qui fonctionne, mais ça a l'air moche. J'ai fini par commenter ce qu'il faisait parce que ça ne semblait pas évident. Cela semble presque pythonique, mais pas tout à fait.

# Might not be exact, but you get the idea, use the step
# parameter of range() and slicing to grab 2 chars at a time
s = "+c-R+D-e"
for op, code in (s[i:i+2] for i in range(0, len(s), 2)):
  print op, code

Y a-t-il des façons meilleures/plus propres de le faire?

30
Richard Levasseur

Pas question de plus propre, mais il y a une autre alternative:

for (op, code) in Zip(s[0::2], s[1::2]):
    print op, code

Une version sans copie:

from itertools import izip, islice
for (op, code) in izip(islice(s, 0, None, 2), islice(s, 1, None, 2)):
    print op, code
44
Pavel Minaev

Peut-être que ce serait plus propre?

s = "+c-R+D-e"
for i in xrange(0, len(s), 2):
    op, code = s[i:i+2]
    print op, code

Vous pourriez peut-être écrire un générateur pour faire ce que vous voulez, peut-être que ce serait plus Pythonic :)

13
Paggas

Triptych a inspiré cette solution plus générale:

def slicen(s, n, truncate=False):
    assert n > 0
    while len(s) >= n:
        yield s[:n]
        s = s[n:]
    if len(s) and not truncate:
        yield s

for op, code in slicen("+c-R+D-e", 2):
    print op,code
5
mhawke
from itertools import izip_longest
def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return izip_longest(*args, fillvalue=fillvalue)
def main():
    s = "+c-R+D-e"
    for item in grouper(s, 2):
        print ' '.join(item)
if __== "__main__":
    main()
##output
##+ c
##- R
##+ D
##- e

izip_longest nécessite Python 2.6 (ou supérieur). Si vous utilisez Python 2.4 ou 2.5, utilisez la définition de izip_longest dans le document document ou modifiez la fonction de groupeur pour:

from itertools import izip, chain, repeat
def grouper(iterable, n, padvalue=None):
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)
4
sunqiang

Belle opportunité pour un générateur. Pour les listes plus volumineuses, cela sera beaucoup plus efficace que de compresser tous les autres éléments. Notez que cette version gère également les chaînes avec ops pendantes

def opcodes(s):
    while True:
        try:
            op   = s[0]
            code = s[1]
            s    = s[2:]
        except IndexError:
            return
        yield op,code        


for op,code in opcodes("+c-R+D-e"):
   print op,code

edit: réécriture mineure pour éviter les exceptions ValueError.

3
Triptych

Les autres réponses fonctionnent bien pour n = 2, mais dans le cas général, vous pouvez essayer ceci:

def slicen(s, n, truncate=False):
    nslices = len(s) / n
    if not truncate and (len(s) % n):
        nslices += 1
    return (s[i*n:n*(i+1)] for i in range(nslices))

>>> s = '+c-R+D-e'
>>> for op, code in slicen(s, 2):
...     print op, code
... 
+ c
- R
+ D
- e

>>> for a, b, c in slicen(s, 3):
...     print a, b, c
... 
+ c -
R + D
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: need more than 2 values to unpack

>>> for a, b, c in slicen(s,3,True):
...     print a, b, c
... 
+ c -
R + D
2
mhawke

Cette approche prend en charge un nombre arbitraire d'éléments par résultat, évalue la paresse et l'entrée itérable peut être un générateur (aucune indexation n'est tentée): 

import itertools

def groups_of_n(n, iterable):
    c = itertools.count()
    for _, gen in itertools.groupby(iterable, lambda x: c.next() / n):
        yield gen

Tous les éléments restants sont renvoyés dans une liste plus courte.

Exemple d'utilisation:

for g in groups_of_n(4, xrange(21)):
    print list(g)

[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
[12, 13, 14, 15]
[16, 17, 18, 19]
[20]
2
Tony Delroy
>>> s = "+c-R+D-e"
>>> s
'+c-R+D-e'
>>> s[::2]
'+-+-'
>>>
1
ghostdog74

Peut-être pas le plus efficace, mais si vous aimez les regex ...

import re
s = "+c-R+D-e"
for op, code in re.findall('(.)(.)', s):
    print op, code
1
epost

J'ai rencontré un problème similaire. Fini de faire quelque chose comme ça:

ops = iter("+c-R+D-e")
for op in ops
    code = ops.next()

    print op, code

Je sentais que c'était le plus lisible.

0
Xavi

Voici ma réponse, un peu plus propre à mes yeux:

for i in range(0, len(string) - 1):
    if i % 2 == 0:
        print string[i:i+2]
0
Eric Carmichael

Pensez à pip installer more_itertools , qui est déjà livré avec une implémentation de chunked avec d’autres outils utiles:

import more_itertools 

for op, code in more_itertools.chunked(s, 2):
    print(op, code)

Sortie:

+ c
- R
+ D
- e
0
pylang