2

I am coding in Python 2.7 using PyCharm on Ubuntu.

I am trying to create a function that will take a string and change each character to the character that would be next in the alphabet.

def LetterChanges(str):
    # code goes here
    import string
    ab_st = list(string.lowercase)
    str = list(str)
    new_word = []
    for letter in range(len(str)):
        if letter == "z":
            new_word.append("a")
        else:
            new_word.append(ab_st[str.index(letter) + 1])
        new_word = "".join(new_word)
    return new_word


# keep this function call here
print LetterChanges(raw_input())

When I run the code I get the following error:

/usr/bin/python2.7 /home/vito/PycharmProjects/untitled1/test.py
test
Traceback (most recent call last):
  File "/home/vito/PycharmProjects/untitled1/test.py", line 17, in <module>
    print LetterChanges(raw_input())
  File "/home/vito/PycharmProjects/untitled1/test.py", line 11, in LetterChanges
    new_word.append(ab_st[str.index(letter) + 1])
ValueError: 0 is not in list

Process finished with exit code 1

What am I doing wroing in line 11? How can I get the following character in the alphabet for each character and append it to the new list?

Many thanks.

Vito
  • 33
  • 1
  • 1
  • 5
  • Possible duplicate of [Caesar Cipher Function in Python](https://stackoverflow.com/questions/8886947/caesar-cipher-function-in-python) – Georgy Jan 02 '19 at 14:15

8 Answers8

7

I think you are making this too complicated.

Just use modulo to roll around to the beginning of the string:

from string import ascii_letters

s='abcxyz ABCXYZ'
ns=''
for c in s:
    if c in ascii_letters:
        ns=ns+ascii_letters[(ascii_letters.index(c)+1)%len(ascii_letters)]
    else:
        ns+=c

Which you can reduce to a single unreadable line if you wish:

''.join([ascii_letters[(ascii_letters.index(c)+1)%len(ascii_letters)] 
             if c in ascii_letters else c for c in s])

Either case,

Turns      abcxyz ABCXYZ
into       bcdyzA BCDYZa

If you want it to be limited to upper of lower case letters, just change the import:

from string import ascii_lowercase as letters

s='abcxyz'
ns=''
for c in s:
    if c in letters:
        ns=ns+letters[(letters.index(c)+1)%len(letters)]
    else:
        ns+=c
dawg
  • 98,345
  • 23
  • 131
  • 206
  • 2
    I much prefer this approach than my own answer. I was specifically trying to deal with the error in OP code. Upvote :) – roganjosh Jun 05 '16 at 18:29
2

Two main things. 1) Don't use the Python built-in str to define variables as it could lead to unusual behaviour. 2) for letter in range(len(str)) does not return a letter at all (hence the error stating that 0 is not in your list). Instead, it returns numbers one by one up to the length of str. Instead, you can just use for letter in my_string.

EDIT: Note that you don't need to convert the string into a list of letters. Python will automatically break the string into individual letters in for letter in strng. Updated answer based on comment from linus.

def LetterChanges(strng):
    ab_st = list(string.lowercase)
    output_string = []
    for letter in strng:
        if letter == 'z':
            output_string.append('a')
        else:
            letter_index = ab_st.index(letter) + 1
            output_string.append(ab_st[letter_index])
        new_word = "".join(output_string)

    return new_word


# keep this function call here
print LetterChanges(raw_input())
Georgy
  • 12,464
  • 7
  • 65
  • 73
roganjosh
  • 12,594
  • 4
  • 29
  • 46
1

this is my code i think it is very simple

def LetterChanges(st):
    index = 0
    new_word = ""
    alphapet = "abcdefghijklmnopqrstuvwxyzacd"

    for i in st.lower():
        if i.islower(): #check if i s letter
            index = alphapet.index(i) + 1 #get the index of the following letter
            new_word += alphapet[index]    
        else: #if not letter
            new_word += i    
    return new_word


print LetterChanges(raw_input())
1

The problem you are solving is of Ceaser cipher. you can implement the formula in your code.

E(x) = (x+n)%26 where x is your text and n will be the shift.

Below is my code. (I write the code in python 3)

import ast
n = ast.literal_eval(input())
n1 = n[0]
step = n[1]
def enc_dec(string,step):
    result = ''
    for i in string:
        temp = ''
        if i=='':
            result = result+i
        elif i.isupper():
            temp = chr((ord(i) + step - 65) % 26 + 65)
        else:
            temp = chr((ord(i) + step - 97) % 26 + 97)
        result = result + temp
    return result
print(enc_dec(n1,step))
0

There's two issues with the code. Instead of looping letters you're looping over numbers since you're calling range(len(str)). The second issue is that within the loop you assign a string to new_word which will cause the next iteration to fail since string doesn't have method append. If you make the following changes it should work:

for letter in str: # for letter in range(len(str)):
    if letter == "z":
        new_word.append("a")
    else:
        new_word.append(ab_st[str.index(letter) + 1])
    # new_word = "".join(new_word)
new_word = "".join(new_word)
niemmi
  • 17,113
  • 7
  • 35
  • 42
0

Look at this for ideas to simplify your code.

def LetterChanges(word):
    zabc  = 'abcdefghijklmonpqrstuvwxyzabc'
    ab_st = list(zabc)
    new_word = []    
    for letter in list(word.lower().strip()):        
        new_word.append(ab_st[zabc.index(letter) + 1])
    new_word = "".join(new_word)
    return new_word          


LetterChanges("  Chicago ")
Merlin
  • 24,552
  • 41
  • 131
  • 206
  • How does `zabc` make this easier than `string.lowercase`? – roganjosh Jun 05 '16 at 20:47
  • You have added 'a' after 'z', so you remove the if "z" statement, You dont need to import "string". string.lowercase makes very little sense.. Can you read the rest of the code? What is "string". Try to be more descriptive when defining variables, avoid python key words – Merlin Jun 05 '16 at 21:06
  • I am not the OP, I think you have us mistaken? Typing out the alphabet surely isn't a solution, especially if it can be arbitrary length. – roganjosh Jun 05 '16 at 21:12
  • Thats not what the OP wanted. And, use strings as such in RegEx also. – Merlin Jun 05 '16 at 21:23
  • I'm very confused. I'm not sure how typing out the alphabet provides a better solution than what the OP was using. I was just curious about why you made that substitution. – roganjosh Jun 05 '16 at 21:27
0

Here is a more general approach where user can choose how many characters back or forth they want to shift the string, and which alphabets they want to use:

from string import (ascii_lowercase,
                    ascii_uppercase)
from typing import Sequence


def shift(string: str,
          *,
          alphabets: Sequence[str] = (ascii_lowercase, ascii_uppercase),
          step: int = 1) -> str:
    """Shifts `string` by `step` in `alphabets`"""

    def shift_char(char):
        for alphabet in alphabets:
            try:
                return alphabet[(alphabet.index(char) + step) % len(alphabet)]
            except ValueError:
                pass
        return char

    return ''.join(map(shift_char, string))

Examples of usage:

# default parameters
>>> shift('abcxyz, ABCXYZ')
'bcdyza, BCDYZA'

# negative shift
>>> shift('abcxyz, ABCXYZ',
         step=-1)
'zabwxy, ZABWXY'

# using other alphabets
>>> russian_alphabet = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'
>>> shift('уегнп гбзмюсзм',
          alphabets=[russian_alphabet])
'фёдор двинятин'

Note on performance:

Note that alphabet[(alphabet.index(char) + step) % len(alphabet)] is O(n) due to searching of an index of an element in a string. While for small strings it's ok, for large strings it would make sense to have a dictionary mapping each character in an alphabet to its index, like:

mapping = dict(map(reversed, enumerate(alphabet)))
Georgy
  • 12,464
  • 7
  • 65
  • 73
0

This can be done much easier and efficiently by using a look-up instead of using index for each letter.

look_up = dict(zip(string.ascii_lowercase, string.ascii_lowercase[1:]+'a'))

The zip above creates tuples of the form ('a', 'b')... including ('z', 'a'). Feeding that into the dict constructor makes a dict of the same form.

So now the code can be simply:

def LetterChanges(s):
    from string import ascii_lowercase
    
    look_up = dict(zip(ascii_lowercase, ascii_lowercase[1:]+'a'))
    new_s = ''
    for letter in s:
        new_s += look_up[letter]

    return new_s

Even this dict creation and the loop can be saved by using the str.maketrans method and feeding its result to str.translate:

def LetterChanges(s):
    from string import ascii_lowercase

    return s.translate(str.maketrans(ascii_lowercase, ascii_lowercase[1:]+'a'))
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61