Python: Iterate over string with while loop - python

I'm trying to delete all the characters at the end of a string following the last occurrence of a '+'. So for instance, if my string is 'Mother+why+is+the+river+laughing' I want to reduce this to 'Mother+why+is+the+river'. I don't know what the string will be in advance though.
I thought of iterating backwards over the string. Something like:
while letter in my_string[::-1] != '+':
my_string = my_string[:-1]
This won't work because letter in not pre defined.
Any thoughts?

Just use str.rsplit():
my_string = my_string.rsplit('+', 1)[0]
.rsplit() splits from the end of a string; with a limit of 1 it'll only split on the very last + in the string and [0] gives you everything before that last +.
Demo:
>>> 'Mother+why+is+the+river+laughing'.rsplit('+', 1)[0]
'Mother+why+is+the+river'
If there is no + in the string, the original string is returned:
>>> 'Mother'.rsplit('+', 1)[0]
'Mother'
As for your loop; you are testing against a reversed string and the condition returns True until the last + has been removed; you'd have to test in the loop for what character you just removed:
while True:
last = my_string[-1]
my_string = my_string[:-1]
if last == '+':
break
but this is rather inefficient compared to using str.rsplit(); creating a new string for each character removed is costly.

Related

Handling spaces differently when converting between lists and strings

I want to be able to convert some strings to lists of characters, and vice versa. However, all spaces within the strings should be represented by an empty string element in the corresponding list. For example:
typed_words = ['T', "y", "p", "e", "", "t", "h", "i", "s"]
target_text = "Type this"
I've tried using the join method to convert the list into a string, but since there is an empty element in the list, it creates a string with no spaces.
How do I allow for the special case of ''/' ' amidst the rest of the characters?
First of all, if one ignores the space-to-empty-string issue, converting a list of characters to a string and back again is as simple as:
# Converting the list to a string:
total_string = ''.join(list_of_characters)
# Converting the string to a list:
list_of_characters = list(total_string)
In your case, you need the extra step of converting between spaces and empty strings, which you can accomplish with a list comprehension.
For instance, here's a list comprehension (split onto multiple lines for extra clarity) that reproduces a list faithfully, except with empty string elements replaced with spaces:
[
' ' if char == '' else char
for char in list_of_characters
]
So your final conversions would look like this:
# Converting the list to a string:
total_string = ''.join([' ' if char == '' else char for char in list_of_characters])
# Converting the string to a list:
list_of_characters = ['' if char == ' ' else char for char in total_string]
Note that one can iterate over a string just like iterating over a list, which is why that final comprehension can simply iterate over total_string rather than having to do list(total_string).
P.S. An empty string ('') evaluates to False in boolean contexts, so you could make use of the or operator's short-circuiting behavior to use this shorter (though arguably less immediately legible) version:
# Converting the list to a string:
total_string = ''.join([char or ' ' for char in list_of_characters])
For making a list, you need to iterate every char and append it to a list.
def strToList(s):
l = []
for c in s:
l.append(c)
return l
For doing the inverse operation, python allows using the += operator on strings:
def listToStr(l):
s = ""
for c in l:
s += str(c)
return s
We can do something as simple as the following, which is the general case
from typing import List # native python pkg, no need to install
def list_to_string(lst: List[str]) -> str:
return " ".join(lst)
def string_to_list(str_: str) -> List[str]:
return str_.split("")
if we want to cater to your needs that if there is an empty string, replace it with space, we shall do the following
def list_to_string(lst: List[str]) -> str:
'''
Here we loop over the characters in a list,
check if character "c" is space, then append it
otherwise replaced with an empty string
#param list: (List) expects a list of strings
#returns String object of characters appended together
'''
return " ".join([c if c not c.isspace() else "" for c in lst])
Why am I following a one-liner approach? This is due to the fact that Pythonic approaches are better IMHO as they are more readable, compact, and neat i.e. Pythonic
Yet we can also implement the solutions using normal for loops, which is the same thing yet I prefer one-liners. And we can use string concatenation but strings are immutable objects, so every time we append to the main string we have created another string, and so on for as lengthy as the given list, which is not an optimal approach due to the increased number of variables created and memory consumption (which in your case might not be that much, but better keep an eye for that)

Why the output is two string when I pass a one character string to a python function

I've created a function called swap for an experiment. The function is as below:
def swap(x):
return x[-1:] + x[1:-1] + x[0:1]
When I try to pass a string "a", the output is "aa". I am not sure how's that happen. Thanks in advance if someone knows the reason.
Your function returns a new string based on:
The last letter of your string (a)
everything after the first letter but before the last letter (empty in your case)
and the first letter of your string (a)
So because your last letter is your first letter, you get them twice.
To swap them correctly, you'll have to test for string length:
def swap(x):
if len(x) < 2:
return x
return x[-1] + x[1:-1] + x[0]
Now that you know that in the last line x is at least two characters long, you also don't need slicing for the first/last character and can use direct element access (x[0]/x[-1]).
Actually this is ok.
x[-1:] will return the last character, becuase -1 starts from the end
x[0:1] will return the first character from beginning
x[1:-1] is nothing in this case because you don't have the second character
Btw for swap use return x.reverse() or return x[::-1]

How would I put back together a string after iterating it?

As the title says, I am confused on how to put back the string into a line after iterating through it.
Right now I have this:
for element in want_cyphered:
element = ord(element)
element = element + 13
element = chr(element)
print(element)
This iterates through the want_cyphered string and prints it. This is a function I'm using for a cipher and decipherer. What I was wondering is how to compile the iteration into an actual line again? Right now, my print is one character per line, but I want it to be all on one line. Any help would be appreciated! Using python.
The other answers aren't wrong, but since you're using Python, you may as well use what it's good at (and your code is likely to be faster as well):
want_cyphered = 'Some text'
cyphered = ''.join(chr(ord(ch) + 13) for ch in want_cyphered)
print(cyphered)
decyphered = ''.join(chr(ord(ch) - 13) for ch in cyphered)
print(decyphered )
To break that down (assuming you're new at Python): ''.join(list_of_parts) takes a list of string parts (characters or strings) and joins them together into a single string, using whatever the string at the start is - an empty string in this case.
You can generate that list of parts using a generator expression (a very well performing way of iterating over something iterable) like [ch for ch in some_str], which would get you a list of characters in a string.
I've put the generator in square brackets just now so that it would become an actual list, but when you only write a generator to use as the input to some function, you can just pass the generator itself, without the brackets like ''.join(ch for ch in some_str) - which basically does nothing. It takes the string apart and puts it back together again.
But you can apply operations to the elements of that generator as well, so instead of just ch, you could fill the list with chr(ord(ch) + 13) which is the cypher you were looking to apply.
Put all that together and you get:
cyphered = ''.join(chr(ord(ch) + 13) for ch in want_cyphered)
Create a variable and append your element to the variable inside the loop. When the loop ends print the result.
result = ""
for element in want_cyphered:
element = ord(element)
element = element + 13
element = chr(element)
result += element
print(result)
Or if you want to get really fancy...
print("".join([chr(ord(e) + 13) for e in want_cyphered]))
just do
for element in want_cyphered:
element = ord(element)
element = element + 13
element = chr(element)
print(element,end="")
the end="" part tells Python to add a empty string at the end. The end paramter defaults to a new line so it prints each character on one line. Setting end="" makes it all print on one line.
Just make an empty string and on each iteration, add the element to it!
want_cyphered = "Hello, World!"
cyphered = ""
for element in want_cyphered:
element = ord(element)
element = element + 13
element = chr(element)
# Add the element to string
cyphered += element
Here is an approach that uses the translate, make_trans functions.
from string import ascii_lowercase as lc, ascii_uppercase as uc
def rot_13(s):
return s.translate(str.maketrans(lc+uc, lc[13:]+lc[:13]+uc[13:]+uc[:13]))
s = 'Hello World!'
print(rot_13(s))
Prints
Uryyb Jbeyq!

Replace substring of given indices range

I am new in Python programming. I am stuck at one point. Let's say I have string "hello-world". I want to replace all the characters of this string with "*" except first & last. so the result will be "h***-****d".
One way to do this as below:
In [1]: s = "hello-world"
In [2]: s[0] + "*"*(len(s)-2) + s[-1]
Out[2]: 'h*********d'
If I want to replace all characters with "*" except first & last 2 characters
In [3]: s[:2] + "*"*(len(s)-4) + s[-2:]
Out[3]: 'he*******ld'
Is there any pretty way to handle these type of problems. Any help would be appreciated. Thanks.
I think what you want to do is this:
def obscure(string, n):
characters = list(string)
characters[n:-n] = '*' * len(characters[n:-n])
obscured = ''.join(characters)
return obscured
Turn the string into a list of characters. Replace the ones you want to obscure. Then join the list back into a string.
You can use str.join (and the string module to check against letters):
s[0] + ''.join(['*' if i in string.ascii_letters else i
for i in s[1:-1]]) + s[-1]
Since you said you wanted h****-****d where the hyphen isn't replaced, you would need to test whether the characters are letters or not. You could change string.ascii_letters to:
chars = 'abcdefghijklmnopqrstuvwxyz'
chars = chars + chars.upper() + '0123456789' # + 'some_other_chars'
...if you want to include other characters like numbers or punctuation. Or you can write out the letters you want to replace manually.
You may also want to perform a check to see whether the string is 3 characters or more so that no errors are raised.
You could define a function to not repeat yourself:
def replace(s, n):
if len(s) > n*2:
return s[:n] + '*'*(len(s)-n*2) + s[-n:]
return s
print(replace('hello-world', 1)) # h*********d
print(replace('hello-world', 2)) # he*******ld
print(replace('hello', 2)) # he*lo
print(replace('hello', 3)) # hello
You can also use some kind of string formatting instead of concatenation (which should be more efficient), e.g. f-strings available in 3.6+:
def replace(s, n):
if len(s) > n*2:
return f"{ s[:n] }{ '*'*(len(s)-n*2) }{ s[-n:] }"
return s
You can try this.
s="hello-world"
for i in s[1:-1]:
if i.isalpha():
s=s.replace(i,"*")

For loop or while loop?

Which function definition is more efficient in Python, even though they do the same task? I.e when should we use a for loop and when should we use a while loop?
def count_to_first_vowel(s):
''' (str) -> str
Return the substring of s up to but not including the first vowel in s. If no vowel
is present, return s.
>>> count_to_first_vowel('hello')
'h'
>>> count_to_first_vowel('cherry')
'ch'
>>> count_to_first_vowel('xyz')
xyz
'''
substring = ''
for char in s:
if char in 'aeiouAEIOU':
return substring
substring = substring + char
return substring
or
def count_to_first_vowel(s):
''' (str) -> str
Return the substring of s up to but not including the first vowel in s. If no vowel
is present, return s.
>>> count_to_first_vowel('hello')
'h'
>>> count_to_first_vowel('cherry')
'ch'
>>> count_to_first_vowel('xyz')
xyz
'''
substring = ''
i = 0
while i < len(s) and not s[i] in 'aeiouAEIOU':
substring = substring + s
i = i + 1
return substring
The for loop evaluates the length once, and operates knowing that. The while loop will have to evaluate len(s) each loop. There may be more overhead in accessing the individual index of the string each time the while statement gets evaluated too.
If the while loop is recalculating something like len() each time, I think it would be more efficient to use for. Both of them have to test at least one condition each loop though.
Rewriting the while loop to use a save variable like len = len(s) may remove that extra bit and make them extremely close. This is more true when you consider that your for loop is executing a second internal loop.

Categories

Resources