Tom Melo
Tom Melo

Reputation: 1499

How to create a cyclic iterator over the alphabet in Python?

Given the following scenario:

import string

UPPERCASE_ALPHABET = list(string.ascii_uppercase)
LOWERCASE_ALPHABET = list(string.ascii_lowercase)

How to create a cyclic loop over the alphabet jumping N positions?


Example 1:

letter = a, jump = 5

Result: f


Example 2:

letter = z, jump = 5

Result: e


So far, I got:

import string

UPPERCASE_ALPHABET = list(string.ascii_uppercase)
LOWERCASE_ALPHABET = list(string.ascii_lowercase)

def forward(letter, jump):
    alphabet = LOWERCASE_ALPHABET if letter.islower() else UPPERCASE_ALPHABET
    index = alphabet.index(letter)
    count = 0
    while True:
        if count == jump:
            return alphabet[index]

        if index == len(alphabet):            
            index = 0

        index += 1
        count += 1

print forward('a', 5)
print forward('z', 5)

But it doesn't look Pythonic at all...

Is there a better and Pythonic way of doing this? Maybe using chr(ord('N') + position) ?

Upvotes: 2

Views: 2044

Answers (3)

Yilmaz
Yilmaz

Reputation: 49190

class CyclicIterator:
    def __init__(self,lst):
        self.lst=lst
        self.i=0
    def __iter__(self):
        return self
    def __next__(self):
        result=self.lst[self.i % len(self.lst)]
        self.i+=3   #increasing by 3
        return result

class that has iter() and next() meets iterator protocol. creating an instance of this iterator class

iter_cycle=CyclicIterator('abcdefghiijklmnnoprstuvyz')

numbers=range(1,27,3) # 26 letters increases by 3
list(zip(list(numbers),iter_cycle))

enter image description here

Upvotes: 0

user3657941
user3657941

Reputation:

I think you had the right idea with ord and chr:

import string

def forward(letter, jump):
    if letter.islower():
        start_character = ord('a')
    else:
        start_character = ord('A')
    start = ord(letter) - start_character
    offset = ((start + jump) % 26) + start_character
    result = chr(offset)
    return result

print forward('a', 5)
print forward('z', 5)
print forward('z', 1)
print forward('a', 26)
print forward('A', 5)
print forward('Z', 5)
print forward('Z', 1)
print forward('A', 26)

Output

f
e
a
a
F
E
A
A

Upvotes: 1

zwer
zwer

Reputation: 25779

I'd write a custom iterator class to encapsulate itertools.cycle() and provide a skip() functionality, e.g.:

import itertools

class CyclicSkipIterator(object):

    def __init__(self, iterable):
        self._iterator = itertools.cycle(iterable)

    def __iter__(self):
        return self

    def next(self):  # use __next__ on Python 3.x
        return next(self._iterator)

    def skip(self, number=1):
        for i in xrange(number):  # use range() on Python 3.x
            next(self._iterator)

Then you can do exactly what you wanted with it:

import string

LOWERCASE_ALPHABET = list(string.ascii_lowercase)

lower_iter = CyclicSkipIterator(LOWERCASE_ALPHABET)
print(next(lower_iter))  # a
lower_iter.skip(4)  # skip next 4 letters: b, c, d, e
print(next(lower_iter))  # f
lower_iter.skip(19)  # skip another 19 letters to arrive at z
print(next(lower_iter))  # z
lower_iter.skip(4)  # skip next 4 letters: a, b, c, d
print(next(lower_iter))  # e

You can add even more functionality if you wanted to, like reversing, switching iterables mid-iteration etc.

UPDATE: If you want to jump to a specific element in the list, you can add a method for that to the CyclicSkipIterator:

class CyclicSkipIterator(object):

    def __init__(self, iterable):
        self._iterator = itertools.cycle(iterable)

    def __iter__(self):
        return self

    def __next__(self):  # use __next__ on Python 3.x
        return next(self._iterator)

    def skip(self, number=1):
        for _ in range(number):  # use range() on Python 3.x
            next(self._iterator)

    def skip_to(self, element, max_count=100):  # max_count protects against endless cycling
        max_count = max(1, max_count)  # ensure at least one iteration
        for _ in range(max_count):  # use range() on Python 3.x
            e = next(self._iterator)
            if element == e:
                break

Then you can skip_to whatever letter you want:

import string

LOWERCASE_ALPHABET = list(string.ascii_lowercase)

lower_iter = CyclicSkipIterator(LOWERCASE_ALPHABET)
print(next(lower_iter))  # a
lower_iter.skip(4)  # skip 4 letters: b, c, d, e
print(next(lower_iter))  # f
lower_iter.skip_to("y")  # skip all letters up to y
print(next(lower_iter))  # z
lower_iter.skip(4)  # skip 4 letters: a, b, c, d
print(next(lower_iter))  # e

Upvotes: 1

Related Questions