'Reverse string without non letters symbol

I need to reverse only letters in each word. The order of the words and non-letter symbols must remain in their positions. I have a function, but it changes the position of the word.

def reverse_string(st):
    stack = []
    for el in st:
        if el.isalpha():
            stack.append(el)
    result = ''
    for el in st:
        if el.isalpha():
            result += stack.pop()
        else:
            result += el
    return result

Input string:

b3ghcd hg#tyj%h

Expected output string:

d3chgb hj#ytg%h


Solution 1:[1]

You were really close! Your code is not working because you are applying the reversion to the whole string instead of doing it one word at a time. But just adding an extra step that splits the input string at white spaces (I assume white spaces is what defines words in your input) you'll get your desired result:

def reverse_string(st):
    return ' '.join(reverse_word(word) for word in st.split())

def reverse_word(st):
    stack = []
    for el in st:
        if el.isalpha():
            stack.append(el)
    result = ''
    for el in st:
        if el.isalpha():
            result += stack.pop()
        else:
            result += el
    return result

instr = 'b3ghcd hg#tyj%h'

print(reverse_string(instr)) # prints 'd3chgb hj#ytg%h'

NOTE: You can pythonify your code a bit using some built in functions of list comprehension. In this case you'd replace the building of your stack.

stack = []
    for el in st:
        if el.isalpha():
            stack.append(el)

for one of the following:

stack = [el for el in st if el.isalpha()] or stack = list(filter(str.isalpha, st))

Solution 2:[2]

Using the function you provided, one way of doing it is:

import re

def reverse_word(st):
    stack = []
    for el in st:
        if el.isalpha():
            stack.append(el)
    result = ''
    for el in st:
        if el.isalpha():
            result += stack.pop()
        else:
            result += el
    return result

def reverse_string(st):
    return re.sub(r'\S+', lambda match: reverse_word(match.group()), st)

print(reverse_string("b3ghcd hg#tyj%h"))
# "d3chgb hj#ytg%h"

Not thoroughly tested, and you might want to use for example [^\s,.?!;] or similar instead of \S, depending on what kind of 'sentences' you are dealing with.

Solution 3:[3]

Alternatively, you could try the following, which creates a reversed list of the alpha characters only, then inserts the non-alpha characters into their respective positions, and returns the sequence as a string.

def rev_str(s):
    x = list(filter(str.isalpha, s[::-1]))
    for i,s_ in enumerate(s):
        if not s_.isalpha():
            x.insert(i, s_)
    return ''.join(x)

instr = 'b3ghcd hg#tyj%h'
outstr = ' '.join(rev_str(s) for s in instr.split())

print(outstr)
d3chgb hj#ytg%h

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 MikeM
Solution 3 acrobat