Manipulating string to repeat based on the length of another string - python

I am working on a python project, where I am required to include an input, and another value (which will be manipulated).
For example,
If I enter the string 'StackOverflow', and a value to be manipulated of 'test', the program will make the manipulatable variable equal to the number of characters, by repeating and trimming the string. This means that 'StackOverflow' and 'test' would output 'testtesttestt'.
This is the code I have so far:
originalinput = input("Please enter an input: ")
manipulateinput = input("Please enter an input to be manipulated: ")
while len(manipulateinput) < len(originalinput):
And I was thinking of including a for loop to continue the rest, but am not sure how I would use this to effectively manipulate the string. Any help would be appreciated, Thanks.

An itertools.cycle approach:
from itertools import cycle
s1 = 'Test'
s2 = 'StackOverflow'
result = ''.join(a for a, b in zip(cycle(s1), s2))
Given you mention plaintext - a is your key and b will be the character in the plaintext - so you can use this to also handily manipuate the pairing...
I'm taking a guess you're going to end up with something like:
result = ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(cycle(s1), s2))
# '\x07\x11\x12\x17?*\x05\x11&\x03\x1f\x1b#'
original = ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(cycle(s1), result))
# StackOverflow

There are some good, Pythonic solutions here... but if your goal is to understand while loops rather than the itertools module, they won't help. In that case, perhaps you just need to consider how to grow a string with the + operator and trim it with a slice:
originalinput = input("Please enter an input: ")
manipulateinput = input("Please enter an input to be manipulated: ")
output = ''
while len(output) < len(originalinput):
output += manipulateinput
output = output[:len(originalinput)]
(Note that this sort of string manipulation is generally frowned upon in real Python code, and you should probably use one of the others (for example, Reut Sharabani's answer).

Try something like this:
def trim_to_fit(to_trim, to_fit):
# calculate how many times the string needs
# to be self - concatenated
times_to_concatenate = len(to_fit) // len(to_trim) + 1
# slice the string to fit the target
return (to_trim * times_to_concatenate)[:len(to_fit)]
It uses slicing, and the fact that a multiplication of a X and a string in python concatenates the string X times.
Output:
>>> trim_to_fit('test', 'stackoverflow')
'testtesttestt'
You can also create an endless circular generator over the string:
# improved by Rick Teachey
def circular_gen(txt):
while True:
for c in txt:
yield c
And to use it:
>>> gen = circular_gen('test')
>>> gen_it = [next(gen) for _ in range(len('stackoverflow'))]
>>> ''.join(gen_it)
'testtesttestt'

What you need is a way to get each character out of your manipulateinput string over and over again, and so that you don't run out of characters.
You can do this by multiplying the string so it is repeated as many times as you need:
mystring = 'string'
assert 2 * mystring == 'stringstring'
But how many times to repeat it? Well, you get the length of a string using len:
assert len(mystring) == 6
So to make sure your string is at least as long as the other string, you can do this:
import math.ceil # the ceiling function
timestorepeat = ceil(len(originalinput)/len(manipulateinput))
newmanipulateinput = timestorepeat * manipulateinput
Another way to do it would be using int division, or //:
timestorepeat = len(originalinput)//len(manipulateinput) + 1
newmanipulateinput = timestorepeat * manipulateinput
Now you can use a for loop without running out of characters:
result = '' # start your result with an empty string
for character in newmanipulateinput:
# test to see if you've reached the target length yet
if len(result) == len(originalinput):
break
# update your result with the next character
result += character
# note you can concatenate strings in python with a + operator
print(result)

Related

Repeat string with a specific pattern without slicing

I'm working on a pretty annoying Python assignment and I'm lost. These are the requirements:
Ask for input.
For each character in the saved string I need to output said string with a certain modification. For example, if the input is abcd the output looks like this:
abcd
bcda
cdab
dabc
I.e. there are len(input) lines, each line begins with the next input[i] character and repeats to the length of the original input.
I should not use slicing, it's loop practice (T_T). No functions or packages. Loops only.
I made a working script that looks like this:
w = input('Type a word:')
w2 = ''
for i, char in enumerate(w):
w2 = w[i:]+w[:i]
print(w2)
It's neat and short. But it will be marked down for slicing. Can Python loop gurus please help me remake it into loops? Thanks so much in advance!
You can use indexing into the original string using a modulo on itself:
w = "aword"
lw = len(w)
for offset in range(lw):
for character in range(lw):
print(w[(offset+character) % lw], end="")
print()
Output:
aword
worda
ordaw
rdawo
dawor
If your sum of offset and character overshoots the amount of characters the modulo operation wraps it around.
If you can't slice strings, you can append and pop lists. So, convert the string to a list and work with list methods.
>>> test = "abcd"
>>> l = list(test)
>>> for _ in range(len(l)):
... print("".join(l))
... l.append(l.pop(0))
...
abcd
bcda
cdab
dabc
Just for fun another one:
s = 'aword'
ss = s * 2 # 'awordaword'
for i in range(len(s)):
for j in range(len(s)):
print(ss[i+j], end='')
print()
Output:
aword
worda
ordaw
rdawo
dawor

Return Alternating Letters With the Same Length From two Strings

there was a similar question asked on here but they wanted the remaining letters returned if one word was longer. I'm trying to return the same number of characters for both strings.
Here's my code:
def one_each(st, dum):
total = ""
for i in (st, dm):
total += i
return total
x = one_each("bofa", "BOFAAAA")
print(x)
It doesn't work but I'm trying to get this desired output:
>>>bBoOfFaA
How would I go about solving this? Thank you!
str.join with zip is possible, since zip only iterates pairwise up to the shortest iterable. You can combine with itertools.chain to flatten an iterable of tuples:
from itertools import chain
def one_each(st, dum):
return ''.join(chain.from_iterable(zip(st, dum)))
x = one_each("bofa", "BOFAAAA")
print(x)
bBoOfFaA
I'd probably do something like this
s1 = "abc"
s2 = "123"
ret = "".join(a+b for a,b in zip(s1, s2))
print (ret)
Here's a short way of doing it.
def one_each(short, long):
if len(short) > len(long):
short, long = long, short # Swap if the input is in incorrect order
index = 0
new_string = ""
for character in short:
new_string += character + long[index]
index += 1
return new_string
x = one_each("bofa", "BOFAAAA") # returns bBoOfFaA
print(x)
It might show wrong results when you enter x = one_each("abcdefghij", "ABCD") i.e when the small letters are longer than capital letters, but that can be easily fixed if you alter the case of each letter of the output.

Decrypting a string into a number

Last week, I was assigned to encrypt numeric PINs into pronounceable strings (made up of vowel-consonant pairs). This went well.
This week, I've been assigned to decrypt the strings my function produced back into their original PIN form. I'm trying to reverse-engineer my code, but I don't know where to start.
Global variables:
CONSONANTS = "bcdfghjklmnpqrstvwyz"
VOWELS = "aeiou"
Encryption code:
def alphapinEncode(pin):
'''(num) -> string
Takes user input (pin) and converts it into a pronounceable string.
Returns the string (codedPin)
Examples:
>>> alphainEncode(4327)
'lohi'
>>> alphainEncode(3463470)
'bomejusa'
'''
codedPin = ""
while pin > 0:
last_two_digits = pin % 100
codedPin = VOWELS[last_two_digits % 5] + codedPin
codedPin = CONSONANTS[last_two_digits // 5] + codedPin
pin = pin // 100
return codedPin
Decryption code:
def alphapinDecode(codedPin):
'''(string) -> num
DOCSTRING PLACEHOLDER
'''
#This while loop checks validity of input string (string).
testPin = codedPin
while len(testPin) > 0:
if testPin[-1] in VOWELS and testPin[-2] in CONSONANTS:
testPin = testPin[:-2]
else:
print ("Your string is incorrectly formatted. Please use consonant-vowel pairs.")
return None
#Decryption code goes here!
return #pin
Problem with your spec: What happens when the string to be decoded is not even in length?
Ignoring that case, your approach should resemble this:
Split the codedPin into groups of 2, or take 2 characters at a time from the input. Keep these values so we can decode the current group of 2.
Reverse the algorithm you used to cypher the pins. Many comments are saying you cannot reverse a modulo operation - which may be true in the generic case, but since we are only dealing with positive integers, we can certainly reverse the value. Here's a hint: find the index of each character in the original CONSONANTS and VOWELS string to give yourself a number to start from. If you get stuck with the math, try manually decoding one of the examples on a piece of paper by hand. Pay close attention to the relationship between the index of the characters and the original PIN number.
Store the values for each pair of numbers until the end of the string has been reached.
Return or output the complete value.
I will not code the answer for you because I believe it would be best for you to come up with something on your own. Take these steps as a pointer and start to code! See what you come up with and come back, and you will have some code that you can use to ask an actual question.
That's an interesting problem. I think this works:
pin = 123456789
c = alphapinEncode(pin)
c
'begomariwu'
sum([n*100**(len(c)/2-i-1) for i,n in enumerate([CONSONANTS.index(p[0])*5 + VOWELS.index(p[1]) for p in [c[i:i+2] for i in range(0, len(c), 2)]])])
123456789
Or, with thanks to #mata for suggesting reduce, this improved one-line version:
reduce(lambda a,b:100*a + b, [CONSONANTS.index(consonant)*5 + VOWELS.index(vowel) for consonant, vowel in [c[i:i+2] for i in range(0, len(c), 2)]], 0)
Now to get serious. One-liners can make for interesting puzzles, but real code should be readable. Here's my real answer:
def alphapinDecode(codedPin):
pin = 0
pairs = [codedPin[i:i+2] for i in range(0, len(codedPin), 2)]
for consonant, vowel in pairs:
pin = pin*100 + CONSONANTS.index(consonant)*5 + VOWELS.index(vowel)
return pin
I think this is reasonably clear without comments. As always, good variable names help a lot.
Your implementation of alphapinEncode is good, but still I rewrote it mostly with style changes, but also to use divmod:
def alphapinEncode(pin):
codedPin = ''
while 0 < pin:
pin, current_digits = divmod(pin, 100)
codedPin = (
CONSONANTS[current_digits // 5] +
VOWELS[current_digits % 5] +
codedPin
)
return codedPin

For loops python (beginner): Don't know why this won't work

So I'm trying to create a program that takes an input such as "34123+74321" and outputs the answer using a for loop. I'm stuck and don't know why my following code won't work:
S = str(input())
for char in range(0, len(S)):
x = S[1:char]
p = int(char)+1
z = S[p:]
if chr(char) == "+":
print (int(z)+int(x))
It would be much easier to do something like this:
>>> s = input()
34123+74321
>>> print(sum(int(x) for x in s.split('+')))
108444
Broken down:
Makes a list of number-strings, by splitting the string into parts with '+' as the delimiter.
for each value in that list, converts it to an integer.
Find the total or sum of those integers.
print out that value to the screen for the user to see.
You could also try:
>>> import ast
>>> s = input()
34123+74321
>>> ast.literal_eval(s)
108444
char gets assigned the values (0, 1, ..., len(S)-1) and therefore chr(char) will never be equal to '+'.
Probably you mean if s[char] == "+":.
In general, you should try to use "speaking" variable names.
E. g., instead of char, it would better to use idx or something.
string = str(input())
for idx in range(0, len(string)):
if string[idx] == "+":
part1 = string[1:idx]
part2 = string[idx+1:]
print (int(part1) + int(part2))
You are using a loop to find the position of the '+' character right? Then the code should read:
for char in range(len(S)):
if S[char] == '+': pos = char
Then you can go ahead and take the lengths:
z = S[pos+1:]
x = S[:pos]
print int(x) + int(z)
However, note that this is not a very pythonic way of doing things. In Python, there is a string method index which already finds the position you are looking for:
pos = '1234+5678'.index('+')
An easier way (and more Pythonic) of doing this would be:
x, z = '1234+5678'.split('+')
Of course, you could also do:
print sum(map(int, S.split('+')))
Which would also work if you have a number of items to add.
If you are starting to learn Python, it would be better if you understand that everything is an object and has its own methods. The more you learn about the methods, the better you will be at Python. It is a higher level program than your traditional languages, so try not to be limited by the same algorithmic structures which you are used to.
Cheers, and happy programming!

String manipulation weirdness when incrementing trailing digit

I got this code:
myString = 'blabla123_01_version6688_01_01Long_stringWithNumbers'
versionSplit = re.findall(r'-?\d+|[a-zA-Z!##$%^&*()_+.,<>{}]+|\W+?', myString)
for i in reversed(versionSplit):
id = versionSplit.index(i)
if i.isdigit():
digit = '%0'+str(len(i))+'d'
i = int(i) + 1
i = digit % i
versionSplit[id]=str(i)
break
final = ''
myString = final.join(versionSplit)
print myString
Which suppose to increase ONLY the last digit from the string given. But if you run that code you will see that if there is the same digit in the string as the last one it will increase it one after the other if you keep running the script. Can anyone help me find out why?
Thank you in advance for any help
Is there a reason why you aren't doing something like this instead:
prefix, version = re.match(r"(.*[^\d]+)([\d]+)$", myString).groups()
newstring = prefix + str(int(version)+1).rjust(len(version), '0')
Notes:
This will actually "carry over" the version numbers properly: ("09" -> "10") and ("99" -> "100")
This regex assumes at least one non-numeric character before the final version substring at the end. If this is not matched, it will throw an AttributeError. You could restructure it to throw a more suitable or specific exception (e.g. if re.match(...) returns None; see comments below for more info).
Adjust accordingly.
The issue is the use of the list.index() function on line 5. This returns the index of the first occurrence of a value in a list, from left to right, but the code is iterating over the reversed list (right to left). There are lots of ways to straighten this out, but here's one that makes the fewest changes to your existing code: Iterate over indices in reverse (avoids reversing the list).
for idx in range(len(versionSplit)-1, -1, -1):
i = versionSplit[idx]
if chunk.isdigit():
digit = '%0'+str(len(i))+'d'
i = int(i) + 1
i = digit % i
versionSplit[idx]=str(i)
break
myString = 'blabla123_01_version6688_01_01veryLong_stringWithNumbers01'
versionSplit = re.findall(r'-?\d+|[^\-\d]+', myString)
for i in xrange(len(versionSplit) - 1, -1, -1):
s = versionSplit[i]
if s.isdigit():
n = int(s) + 1
versionSplit[i] = "%0*d" % (len(s), n)
break
myString = ''.join(versionSplit)
print myString
Notes:
It is silly to use the .index() method to try to find the string. Just use a decrementing index to try each part of versionSplit. This was where your problem was, as commented above by #David Robinson.
Don't use id as a variable name; you are covering up the built-in function id().
This code is using the * in a format template, which will accept an integer and set the width.
I simplified the pattern: either you are matching a digit (with optional leading minus sign) or else you are matching non-digits.
I tested this and it seems to work.
First, three notes:
id is a reserved python word;
For joining, a more pythonic idiom is ''.join(), using a literal empty string
reversed() returns an iterator, not a list. That's why I use list(reversed()), in order to do rev.index(i) later.
Corrected code:
import re
myString = 'blabla123_01_version6688_01_01veryLong_stringWithNumbers01'
print myString
versionSplit = re.findall(r'-?\d+|[a-zA-Z!##$%^&*()_+.,<>{}]+|\W+?', myString)
rev = list(reversed(versionSplit)) # create a reversed list to work with from now on
for i in rev:
idd = rev.index(i)
if i.isdigit():
digit = '%0'+str(len(i))+'d'
i = int(i) + 1
i = digit % i
rev[idd]=str(i)
break
myString = ''.join(reversed(rev)) # reverse again only just before joining
print myString

Categories